Skip to content
This repository has been archived by the owner on Jan 25, 2022. It is now read-only.

Evaluate using :: instead of # as "sigil" #216

Closed
ems-ja opened this issue Jan 21, 2019 · 8 comments
Closed

Evaluate using :: instead of # as "sigil" #216

ems-ja opened this issue Jan 21, 2019 · 8 comments

Comments

@ems-ja
Copy link

ems-ja commented Jan 21, 2019

VERY IMPORTANT NOTE: Anyone who would like to comment here should first read the PRIVATE SYNTAX FAQ. This will save everyone time. Thank you.

The available space of ASCII symbols was exhaustively searched for possible sigil characters. Only the two characters @ and # were free. However @ had already been used extensively in the community (Babel, TypeScript) to denote decorators like in Java, Python and other languages.

While technically an obvious choice given the constraints, the visual appearance of # has resulted in unnecessary initial objections by JS developers for the looks alone (see issues here, on reddit, the implementor's issue trackers, …). While technical considerations should have priority, both elegance as well as developer mind share do count.

By allowing two character instead of just one, the space of symbols can be widened. This way the double colon :: becomes available. In Rust, C++ and others, :: is used for namespacing, which is a different but related concept, making mental associations easy. At the same time :: has a cleaner look, that's also more aligned to the overall visual identity of JavaScript:

class Counter extends HTMLElement {
  ::x = 0;

  clicked() {
    this::x++;
    window.requestAnimationFrame(this.render.bind(this));
  }

  constructor() {
    super();
    this.onclick = this.clicked.bind(this);
  }

  connectedCallback() { this.render(); }

  render() {
    this.textContent = this::x.toString();
  }
}
window.customElements.define('num-counter', Counter);

Therefore it could be wise to re-evaluate using :: as a sigil to denote private varialbes and their access, keeping everything else the same.

@ccjmne
Copy link

ccjmne commented Jan 21, 2019

I don't adore the # sigil, but as you explained, it does make sense, and I'm not averse to having it be used exactly as this proposal suggests.

You made a good point though, we could definitely consider using 2 characters and as far as these go, :: does have an uncluttered look, is easy to type and just feels... good.

The one downside I immediately thought of when I came across this is that I wanted the This-Binding operator proposal to go through, and it was set on using :: (just like in Java).

Apparently that other proposal may not really be going anywhere anytime soon, though; and the Smart Pipelines, which I am particularly fond of, would:

[...] subsumes the ECMAScript function binding proposal in the first use case (prefix ::) [...]

As a side note, apparently that other proposal did receive some pushback on the grounds that:

The prefix :: is just kinda weird looking.

All in all, it seems to me like "stealing" the :: for class fields wouldn't be so detrimental. So my comment is merely here for the sake of discussion and sharing thoughts 🙂.

@tjcrowder
Copy link
Contributor

Strongly prefer keeping #/.# rather than using ::. I was initially put off by # because of its comment meaning in shell scripts, but it takes roughly 30 minutes of coding to get used to and feels quite natural now. And I haven't even used it that much.

In Rust, C++ and others, :: is used for namespacing, which is a different but related concept, making mental associations easy.

All due respect, I have to strongly disagree with that characterization. Namespacing is very different from private fields/methods.

Three off-the-cuff reasons not to use :: instead of # for private:

1. Since :: has a meaning in Java similar to its meaning in the bind operator proposal, using it for a completely different meaning in JavaScript seems like a trip hazard (given the syntactic similarity of the languages).

2. Smart Pipelines only subsume one of the three uses of :: in the bind operator proposal. There remain the other two. The more complete quote from @ccjmne 's link is:

The smart-pipelines Core Proposal + Additional Feature PF subsumes the ECMAScript function binding proposal in the first use case (prefix ::). But the other two use cases (infix ::) are not addressed by smart pipelines. Smart pipelines and infix function binding :: can and should coexist.

(their emphasis)

Using it for private instead effectively closes off the bind operator proposal (which still could progress in some form), since using something else for it given the prior art in Java seems unlikely.

3. # is thoroughly researched and even somewhat field-tested at this point. Switching to any other sigil risks further delaying the proposal. It's time to move on.

I recommend moving forward with #. It really isn't that bad. The whole discussion around it seems to have much more heat than light, IMHO.

@Padreramnt
Copy link

class A {
  static staticProperty = 'static Property';
  static name = 'i can declare any name for static property/method';
  static apply() {
    /**
     * current implementation for static properties 
     * and methods (TS, Babel) are broken
     * coz it will be compilled into something like this:
     * const A = (constructor => {
     *  constructor.staticProperty = 'static Property';
     *  THROW ERROR, name property has not setter!
     *  constructor.name = 'i can declare any name for static property/method';
     *  seriously? =/
     *  constructor.apply = function apply() { ... };
     *  return constructor;
     * })(class A {})
     * 
     * it will be better to use # for static properties
     * 
     * A#staticProperty
     * A#name
     * A#apply
     * 
     * static properties cannot dynamic accessed
     * A#['invalid syntax']
     * 
     * static properties constant 
     * 
     * A#staticProperty = 'must throw error';
     * 
     * coz it is same as:
     * import * as all from 'module';
     * all.importedVariable = 'new value';
     * 
     * // dot access to usual properties
     * A.name === 'A';
     */
  }
  //=>private = 'incorrect syntax, private methods/properties allowed only with this keyword
  //->protected = 'incorrect syntax, protected methods/propertirs allowed only with this or super keyword
  constructor(init) {
    this.public = init;
    // fat arrow for private access
    this=>a = 'private variable A';
    this=>method = () => {
      // use arrow function for creating private method
      // this context never changed
      console.log(this);
    }
    // thik arrow for protected access
    // same name allowed
    this->a = 'protected variable A';
    this->method = () => {
      // use arrow function for creating protected method
      // this context never changed
      console.log(this);
    }
  }

  set privateA(value) {
    this=>a = value;
  }

  get privateA() {
    return this=>a;
  }

  set protectedA(value) {
    this->a = value;
  }

  get protectedA() {
    return this->a;
  }
}

class B extends A {
  static staticProperty = 'new value for static property, other properties should be extended';
  /**
   * console.log(B#staticProperty === 'new value for static property, other properties should be extended'); // true
   * 
   * console.log(B#staticProperty === A#staticProperty); // false
   * console.log(B#name === A#name); // true
   */

  constructor(init) {
    super(init);
    this.public = 'new value';
    this=>a = 'private variable B';
    console.log(this.privateA) // 'private variable A'
    this.privateA = 'set the next value';
    console.log(this.privateA) // 'set a next value'
    // this=>method();
    // throws undefined is not a function, coz of this=>method not defined in this context

    console.log(this.protectedA) // 'protected variable A';
    this->a = 'set the next value';
    console.log(this.protectedA) // 'set the next value';
    this->method() // B { public: 'new value' }
    this->method = (value) => {
      // use super keyword for base class protected properties an methods
      super->a = value
      super->method();
    }
    
    this->method('set another value to protected a') // B { public: 'new value' }
    console.log(this.protectedA); // 'set another value to protected a'
  }
}

class C extends A {
  constructor(init) {
    super(init);
    this=>a = 'ok, lets another try';
    console.log(this.privateA) // 'ok, lets another try'
    this.privateA = 'and another try';
    console.log(this.privateA) // 'ok, lets another try'; coz of privateA setter used from A;
  }

  get privateA() {
    return this=>a;
  }
}

const a = new A('new A');

a['=>a'] = 'try to access private property';
console.log(a); // A { public: 'new A', '=>a': 'try to access private property'}
const tryToGetPrivateProperty = a=>a // is a function coz it is valid arrow function syntax
// console.log(a->a);
// syntax error, thik arrow syntax allowed only with this an super keyword

@tjcrowder
Copy link
Contributor

current implementation for static properties
and methods (TS, Babel) are broken
coz it will be compilled into something like this

If you're saying that being able to (re)define name is a problem, note that it's already possible to redefine name (https://jsfiddle.net/tjcrowder/1q9p4L8z/):

 "use strict";
class Example { }
console.log(Object.getOwnPropertyDescriptor(Example, "name"));
Object.defineProperty(Example, "name", {
    value: "foo"
});
console.log(Example.name); // "foo"

it will be better to use # for static properties

# is already used for private fields.

"Static" fields being properties of the constructor function is very well established in existing code, there's no need to come up with new syntax for it.

@tjcrowder
Copy link
Contributor

Having said that, I could see an argument for disallowing using the new syntax to create a static property called name (or length) on the basis that casual, unthinking use could be a footgun (and if you really want to do it, you can do it via defineProperty). (But I could see a counter-argument as well.)

@tjcrowder
Copy link
Contributor

@nicolo-ribaudo I think he/she is using a slightly outdated version of the plugin. As you can see in the posted code, it's using assignment to create the name property rather than _defineField. (The current repl does too.) The up-to-date stuff uses _defineField, though, so it looks right to me.

@littledan
Copy link
Member

The rationale is summed up well in #216 (comment) . Thanks for the discussion.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants