Skip to content

Instantly share code, notes, and snippets.

@boopathi
Last active March 13, 2018 14:23
Show Gist options
  • Save boopathi/0d026f42cae93cdb84ab49eda43d546c to your computer and use it in GitHub Desktop.
Save boopathi/0d026f42cae93cdb84ab49eda43d546c to your computer and use it in GitHub Desktop.

Motivation -

  1. use private keyword
  2. Use class private and not instance private.
  3. Statically analysable.

Based on this issue - tc39/proposal-private-fields#14, there is a lot of interest in using the private keyword and no sigils, and sigils cannot be avoided for multiple reasons specified in the FAQ.

Addressing some of the comments by littledan,

(Scroll to last for example)

Declaration

class A {
  private a, b;
}

Accessing

Operator: -> (thin arrow?)

class A {
  private a;

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

Accessing incoming instance's private field

Class Private and not Instance private

Operator = ->

class A {
  private a;
  equals(other) {
    return this->a === other->a;
  }
}

What about ASI hazards?

At the accessing location -

class X {
  private y = () => {}
  z() {
    w()
    this->y() // <-- would be parsed as w();this->y();
  }
}

At the declaration -

class Person {
  properties = {}
  private x
  // would parse as how you'd expect
  // because of the private keyword
}

Nested classes

class A {
  private x;
  foo() {
    log(this->x); // x<A>

    const thisA = this; // to access inside class B

    class B {
      private x;
      constructor(a /* instance of class A */, b /* instance of class B */) {
        // access private of A
        thisA->x;
        // access private of B
        this->x;
        // access private of incoming instance of A
        a->x;
        // access private of incoming instance of B
        b->x;
      }
    }
    
    new B(new A(), new B());
  }
}

No conflict of Private field & Public field with same name

Can have a private field named x and also a public field with the same name x.

class A {
  x = 0;
  private x = 1;
  constructor(x) {
    this.x = x;
    this->x = x;
  }
}

this binding

// example from the original proposal
function getThis() { return this; }

class Test {
  #fn;
  constructor() {
    fn = getThis;
  }
  fn1() {
    return #fn();
  }
  fn2() {
    return this.#fn();
  }
}

const t = new Test();
t.fn1(); // ?
t.fn2(); // t

In this proposal,

function getThis() { return this; }
class Test {
  private fn;
  constructor() {
    this->fn = getThis;
  }
  fn1() {
    return this->fn();
  }
  fn2() {
    return this->fn();
  }
}
const t = new Test();
t.fn1(); // t
t.fn2(); // t

Possibilities of future additions

Private methods

class A {
  private a = 2;
  a = 1;

  private b() {
    return this.a; // access public a
  }
  b() {
    return this->a; // access private a
  }

  c() {
    return this->b() + this.b();
  }

  d(other) {
    return this->a + this.a + other->b() + other.b();
  }
}

const

class A {
  private const x = 1; // private constant value
  const y = 2; // public constant value
}

Not sure how constructor passed arguments can be used to assign a const. So I'll leave it for later.

Is this easy to understand?

  • . is for accessing public fields on any object
  • -> for accessing private fields on this and any instance of the same class

Example:

class Point2D {
  private x, y;
  constructor(x, y) {
    this->x = x;
    this->y = y;
  }
  equals(p) {
    return this->x === p->x && this->y === p->y;
  }
}
Click here for viewing old proposal with `#`

Old proposal - the new proposal addresses some comments about clarity that it's a private field

Declaration

class A {
  private a, b;
}

Accessing

class A {
  private a;

  get a() {
    // just use it like a normal variable
    return a;
  }
}

Accessing incoming instance's private field

Class Private and not Instance private

Operator = #

class A {
  private a;
  equals(other) {
    return a === other#a;
  }
}

What about ASI hazards?

At the accessing location -

class X {
  private y
  z() {
    w()
    y() // <-- would be parsed as w();y();
  }
}

At the declaration -

class Person {
  properties = {}
  private x
  // would parse as how you'd expect
  // because of the private keyword
}

Nested classes

Lexically closest name

class A {
  private x;
  foo() {
    log(x); // x<A>
    class B {
      private x;
      bar() {
        log(x); // x<B> : lexically closest
      }
    }
  }
}

No conflict of Private field & Public field with same name

Can have a private field named x and also a public field with the same name x.

class A {
  x = 0;
  private x = 1;
  constructor(_x) { // because x refers to private field
    this.x = _x;
    x = _x;
  }
}

Are these lexically scoped fields?

No. You're able to access incoming instance's private field inside the class. These are Class Privates

Is this#x allowed ?

  • Should this#x be allowed?
  • If so, should this mean anything other than using just x?
class A {
  private x = 10;
  foo() {
    log(this#x);
    log(x);
  }
}

I'm not sure. I don't see any reason to use this#x.

function getThis() { return this; }

class Test {
  private fn;
  constructor() {
    fn = getThis;
  }
  fn1() {
    return fn();
  }
  fn2() {
    return this#fn();
  }
}

const t = new Test();
t.fn1();
t.fn2();

Possibilities of future additions

Private methods

class A {
  private a = 2;
  a = 1;
  
  private b() {
    return this.a; // access public a
  }
  b() {
    return a; // access private a
  }

  c() {
    return b() + this.b();
  }
  
  d(other) {
    return a + this.a + other#b() + other.b();
  }
}

const

class A {
  private const x = 1; // private constant value
  const y = 2; // public constant value
}

Not sure how constructor passed arguments can be used to assign a const. So I'll leave it for later.

Is this easy to understand?

  • Using # for accessing private fields on instances other than this, and accessing private fields on this like any other variable is confusing.

I don't know.

Example:

class Point2D {
  private x, y;
  constructor(_x, _y) {
    x = _x;
    y = _y;
  }
  equals(p) {
    return x === p#x && y === p#y;
  }
}

Inside the class x and y are used like normal identifiers but are private fields.

@littledan
Copy link

A couple points on this proposal:

  • Using # in both definitions and usages is intended to draw a connection between the two names, to make it easier to understand that they are referring to the same thing.
  • Using .# rather than simply # will let us keep open the possibility of using the #x shorthand in the future without opening up an ASI hazard
  • Even if we don't use the private keyword now, we can still use the const keyword in the future.
  • It's not clear how private const fields can be initialized with, for example, arguments of the constructor. We need to work that sort of basic use case out before adding the feature.
  • Lexical-looking constructs like let and const might seem to imply a single static private lexically-scoped value (as Allen proposed), which this is not.

@littledan
Copy link

Using the syntax of a bare a for this#a raises further hazards of creating the intuition of lexical scoping. If the semantics of a are based on this, rather than normal capture of variables in closures, it could be hard to debug why callbacks don't do the right thing when you don't call or bind them to the appropriate receiver.

@littledan
Copy link

littledan commented Jul 21, 2017

The new version of the proposal seems pretty consistent. However, I'd be worried about users still getting confused. What would stop them from doing this?

class C {
  private x;
  constructor() {
    this.x = /* super secret data*/;
  }
  method() { doSomethingWith(this.x); }
}

The code would simply work; the only problem is that this.x is obviously public, and that nobody writes to the private field.

Using # in declarations and usages may be a bit heavy-handed, but it seems to avoid this hazard.

@boopathi
Copy link
Author

Thanks @littledan.

I'm stuck. I'm not able to figure out something that could help solve this -

class C {
  private x;
  constructor() { this.x = "secret" } // used this.x by mistake instead of this->x
}

and there isn't a way to help users prevent this mistake. It would be a weak argument to say that this work could be delegated to a linter. There will be false positives.

What do you think? Do you see any changes or possibilities in this proposal to fix the above issue?

@boopathi
Copy link
Author

Had some more thoughts on this, but basically didn't get anywhere further.

Maybe, Disallow public and private fields to have same name. But there are strong arguments against that I suppose.

class A {
  private x;
  x = 1; // runtime error
}

Computed properties

const a = "x";
class A {
  private x;
  [a] = 1; // runtime error
}

class B {
  private x;
  foo() {
    this.x; // runtime error
    this[a]; // runtime error
  }
}

Even outside the class, it should still be a runtime error, and not undefined.

class C {
  private x = 4;
}

const c = new C();
log(c.x); // ? undefined or error
// but when we bring assignment into this,
c.x = 5; // it would be wrong to allow such assignments outside the class, but not inside. So it should be an error.

Arguments against this -

@littledan
Copy link

Aside from the programming language design issues described in the FAQ, this prohibition could significantly complicate the runtime model of the language.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment