[Proposal] New syntax for lazy getters

# Aadit M Shah (7 days ago)

Hello TC39,

I recently opened an issue[1] in the tc39/ecma262[2] repository, proposing a new syntax for lazy getters, and I was directed to the CONTRIBUTING[3] page which stated that I should start a conversation on this mailing list. So, my feature proposal is to have syntactic sugar for creating lazy getters[4]. To summarize my original proposal (which you can read by following the very first link above), I find that creating lazy getters is very verbose. For example, consider: const take = (n, xs) => n === ? null : xs && { head: xs.head, get tail() { delete this.tail; return this.tail = take(n - 1, xs.tail); } };

My proposed solution is to add a new keyword lazy to the language. This keyword can only be used as a prefix to longhand property names in object initializers, and it defers the execution of the value expression until the property is accessed. In short, it's just syntactic sugar for lazy getters: const take = (n, xs) => n === ? null : xs && { head: xs.head, lazy tail: take(n - 1, xs.tail) };

This is purely syntactic sugar. The semantics of this new syntax would remain the same as that of the desugared syntax. In particular, calling Object.getOwnPropertyDescriptor(list, "tail") would return an accessor descriptor before accessing list.tail and a data descriptor afterwards. Furthermore, there are other advantages of having this syntactic sugar. For example, creating cyclic data structures becomes much easier. Examples are provided in my original proposal which is linked above. Hope to hear your thoughts on this. , Aadit M Shah

Links:

  1. tc39/ecma262#1223
  2. tc39/ecma262
  3. tc39/ecma262/blob/master/CONTRIBUTING.md
  4. developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get#Smart_self
# Jordan Harband (7 days ago)

How would this work when the getter lives on a prototype? (like most builtin getters) Would it install the memoized value on this, or on the object that contains the getter? Would it use [[Set]] or [[Define]]?

# Andrea Giammarchi (7 days ago)

My 2 cents, I use lazy getters since about ever and I'd love to have such syntax in place but I think there is room for some improvement / simplification in terms of syntax.

## Keep it getish

From parsing perspective, introducing lazy tail() seems way simpler than

introducing lazy tail: for the simple reason that everything that can parse get tail() and set tail() is in place already in every engine. I don't write them but I'm sure having an extra keyboard to catch shouldn't be crazy complicated.

## class compatible

because you used delete this.tail and mentioned functional programming, I'd like to underline ES doesn't force anyone to one programming style or another. That means new syntax should play nicely with classes too, and in this case the proposal doesn't seem to address that because of the direct value mutation, as generic property, and the removal of that property from the object, something not needed if inherited.

My variant would do the same, except it would keep the value an accessor:

const take = (n, xs) => n === 0 ? null : xs && {
    head: xs.head,
    lazy tail() {
      return Object.defineProperty(this, 'tail', {
        configurable: false,
        get: (value =>
          // still a getter
          () => value
        )(
          // executed once
          take(n - 1, xs.tail)
        )
      }).tail;
    }
};

This would keep initial accessor configuration, in terms of enumerability, but it will freeze its value forever and, on top of that, this will play already well with current valid ES2015 classes syntax.

I also believe myself proposed something similar a while ago (or somebody else and I agreed with that proposal) but for some reason it never landed.

Hopefully this time the outcome would be different.

Best

# Andrea Giammarchi (7 days ago)
  • having an extra keyword ...
# Andrea Giammarchi (7 days ago)

in case the counter proposal is not clear enough (apologies for that), this should read simpler than that

const take = (n, xs) => n === 0 ? null : xs && {
    head: xs.head,
    lazy tail() {
      const value = take(n - 1, xs.tail);
      Object.defineProperty(this, 'tail', {
        configurable: false,
        get: () => value
      });
      return value;
    }
};

Hope it's clear what I'd change.

Best

# Aadit M Shah (7 days ago)

When the getter lives on a prototype it would install the memoized value on this using [[Define]]. This is the same way it would work in regular lazy getters: const defclass = prototype => { const constructor = prototype.constructor; constructor.prototype = prototype; return constructor; };

const Person = defclass({ constructor: function Person(firstname, lastname) { this.firstname = firstname; this.lastname = lastname; }, lazy fullname: this.firstname + " " + this.lastname });

const john = new Person("John", "Doe"); const jane = new Person("Jane", "Doe");

console.log(john.fullname); // John Doe console.log(jane.fullname); // Jane Doe

Note that the this within the lazy property value expression (i.e. within the expression this.firstname + " " + this.lastname) refers to the instance (i.e. either john or jane). This can be confusing because without the lazy keyword, this would refer to the lexical context. Although I'm comfortable with it yet I'm open to other suggestions.

On Tue, Jun 12, 2018, at 3:31 AM, Jordan Harband wrote:

How would this work when the getter lives on a prototype? (like most builtin getters) Would it install the memoized value on this, or on the object that contains the getter? Would it use [[Set]] or [[Define]]?> On Tue, Jun 12, 2018 at 12:13 AM, Aadit M Shah <aaditmshah at fastmail.fm> wrote:>> __

Hello TC39,

I recently opened an issue[1] in the tc39/ecma262[2] repository, proposing a new syntax for lazy getters, and I was directed to the CONTRIBUTING[3] page which stated that I should start a conversation on this mailing list.>> So, my feature proposal is to have syntactic sugar for creating lazy getters[4]. To summarize my original proposal (which you can read by following the very first link above), I find that creating lazy getters is very verbose. For example, consider:>> const take = (n, xs) => n === ? null : xs && { head: xs.head, get tail() { delete this.tail; return this.tail = take(n - 1, xs.tail); } };

My proposed solution is to add a new keyword lazy to the language. This keyword can only be used as a prefix to longhand property names in object initializers, and it defers the execution of the value expression until the property is accessed. In short, it's just syntactic sugar for lazy getters:>> const take = (n, xs) => n === ? null : xs && { head: xs.head, lazy tail: take(n - 1, xs.tail) };

This is purely syntactic sugar. The semantics of this new syntax would remain the same as that of the desugared syntax. In particular, calling Object.getOwnPropertyDescriptor(list, "tail") would return an accessor descriptor before accessing list.tail and a data descriptor afterwards.>> Furthermore, there are other advantages of having this syntactic sugar. For example, creating cyclic data structures becomes much easier. Examples are provided in my original proposal which is linked above. Hope to hear your thoughts on this.>> , Aadit M Shah


es-discuss mailing list es-discuss at mozilla.org, mail.mozilla.org/listinfo/es-discuss

Links:

  1. tc39/ecma262#1223
  2. tc39/ecma262
  3. tc39/ecma262/blob/master/CONTRIBUTING.md
  4. developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get#Smart_self
# Andrea Giammarchi (7 days ago)

That is why I'm proposing to use a getter, you don't have to reason about the context in lazy fullname() { return this.firstName + ' ' + this.lastName; } and it still works with de-contextualized invokes such lazy random: () => Math.random() as long as you transpile that as getter

calling the right context (when/if needed).

Both cases can de-sugar to same redefinition of a getter.

# Aadit M Shah (7 days ago)

Actually, from a parsing perspective I believe it shouldn't be too difficult to implement the lazy name: expression syntax. In addition, I'm not too keen on your lazy name() { return expression; } syntax because:

  1. It's more verbose.
  2. It seems to me that it's no different than creating a regular getter:

const take = (n, xs) => n === ? null : xs && { head: xs.head, get

tail() { const value = take(n - 1, xs.tail); Object.defineProperty(this, "tail", { configurable: false, get: () => value }); return value; } };

Regarding the second bullet point, I've probably misunderstood what you were trying to convey. Perhaps you could elucidate. Anyway, making the property non-configurable after accessing it seems like a reasonable thing to do.

On Tue, Jun 12, 2018, at 3:44 AM, Andrea Giammarchi wrote:

My 2 cents, I use lazy getters since about ever and I'd love to have such syntax in place but I think there is room for some improvement / simplification in terms of syntax.> ## Keep it getish**

From parsing perspective, introducing lazy tail() seems way simpler than introducing lazy tail: for the simple reason that everything that can parse get tail() and set tail() is in place already in every engine. I don't write them but I'm sure having an extra keyboard to catch shouldn't be crazy complicated.> ## class compatible

because you used delete this.tail and mentioned functional programming, I'd like to underline ES doesn't force anyone to one programming style or another. That means new syntax should play nicely with classes too, and in this case the proposal doesn't seem to address that because of the direct value mutation, as generic property, and the removal of that property from the object, something not needed if inherited.> My variant would do the same, except it would keep the value an accessor:>

const take = (n, xs) => n === 0 ? null : xs && {
    head: xs.head,
    lazy tail() {
      return Object.defineProperty(this, 'tail', {
        configurable: false,
        get: (value =>
          // still a getter
          () => value
        )(
          // executed once
          take(n - 1, xs.tail)
        )
      }).tail;
    }
};

This would keep initial accessor configuration, in terms of enumerability, but it will freeze its value forever and, on top of that, this will play already well with current valid ES2015 classes syntax.> I also believe myself proposed something similar a while ago (or somebody else and I agreed with that proposal) but for some reason it never landed.> Hopefully this time the outcome would be different.

Best

On Tue, Jun 12, 2018 at 9:13 AM, Aadit M Shah <aaditmshah at fastmail.fm> wrote:>> __

Hello TC39,

I recently opened an issue[1] in the tc39/ecma262[2] repository, proposing a new syntax for lazy getters, and I was directed to the CONTRIBUTING[3] page which stated that I should start a conversation on this mailing list.>> So, my feature proposal is to have syntactic sugar for creating lazy getters[4]. To summarize my original proposal (which you can read by following the very first link above), I find that creating lazy getters is very verbose. For example, consider:>> const take = (n, xs) => n === ? null : xs && { head: xs.head, get tail() { delete this.tail; return this.tail = take(n - 1, xs.tail); } };

My proposed solution is to add a new keyword lazy to the language. This keyword can only be used as a prefix to longhand property names in object initializers, and it defers the execution of the value expression until the property is accessed. In short, it's just syntactic sugar for lazy getters:>> const take = (n, xs) => n === ? null : xs && { head: xs.head, lazy tail: take(n - 1, xs.tail) };

This is purely syntactic sugar. The semantics of this new syntax would remain the same as that of the desugared syntax. In particular, calling Object.getOwnPropertyDescriptor(list, "tail") would return an accessor descriptor before accessing list.tail and a data descriptor afterwards.>> Furthermore, there are other advantages of having this syntactic sugar. For example, creating cyclic data structures becomes much easier. Examples are provided in my original proposal which is linked above. Hope to hear your thoughts on this.>> , Aadit M Shah


es-discuss mailing list es-discuss at mozilla.org, mail.mozilla.org/listinfo/es-discuss

Links:

  1. tc39/ecma262#1223
  2. tc39/ecma262
  3. tc39/ecma262/blob/master/CONTRIBUTING.md
  4. developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get#Smart_self
# Andrea Giammarchi (7 days ago)

it's not about being difficult, it's about compatibility with classes, where getters are OK since ever. However, since classes fields are in Stage 3 [1] maybe it's OK to have:

class Test {
  x = 123;
  lazy random = () => this.x + Math.random();
}

However, like you've noticed, while it's easy to reason about the context within a class declaration, it's easy to create a footgun outside that.

const test = {
  x: 123,
  lazy random: () => this.x + Math.random()
};

That is a whole new meaning of arrow function I'd rather like not to ever encounter.

So what about extra new syntax?

class Test {
  x = 123;
  lazy random() => this.x + Math.random();
}

const test = {
  x: 123,
  lazy random() => this.x + Math.random()
};

But I'm pretty sure at methods shorthand discussions that came up and for some reason didn't work.

[1] tc39/proposal-class-fields#field

# Andrea Giammarchi (7 days ago)

Actually, never mind. I've just realized this would work as well, and it's clean enough.

class Test {
  x = 123;
  lazy random = this.x + Math.random();
}

const test = {
  x: 123,
  lazy random: this.x + Math.random()
};

My only concern is if that class method is created N times per each instance (one of the reasons I use lazy getters in classes prototypes instead of per object) as it is AFAIK for current class fields if you attach an arrow function, instead of once per class, which seems to be preferable in a class context.

Otherwise, I'd be happy to see that happen.

Best

# herby at mailbox.sk (7 days ago)

On June 12, 2018 9:44:31 AM GMT+02:00, Andrea Giammarchi <andrea.giammarchi at gmail.com> wrote:

My 2 cents, I use lazy getters since about ever and I'd love to have such syntax in place but I think there is room for some improvement / simplification in terms of syntax.

Yeah I find this better.

Also fixes the this questions by unambiguously setting it to the instance.

## Keep it getish

From parsing perspective, introducing lazy tail() seems way simpler than introducing lazy tail: for the simple reason that everything that can parse get tail() and set tail() is in place already in every engine. I don't write them but I'm sure having an extra keyboard to catch shouldn't be crazy complicated.

## class compatible

because you used delete this.tail and mentioned functional programming, I'd like to underline ES doesn't force anyone to one programming style or another. That means new syntax should play nicely with classes too, and in this case the proposal doesn't seem to address that because of the direct value mutation, as generic property, and the removal of that property from the object, something not needed if inherited.

My variant would do the same, except it would keep the value an accessor:

const take = (n, xs) => n === 0 ? null : xs && {
   head: xs.head,
   lazy tail() {
     return Object.defineProperty(this, 'tail', {
       configurable: false,

Why enforcing configurable false? When you use get x() / set x() syntax, it leaves the thing configurable true. I feel it is more consistent to have the replaced getter copy configurable from existing status quo, that is, what is actual configurable of this.tail.

And of course, Object.defineProperty family needs a way to create this, I think {get: getterFn, lazy: true, …} could work.

# Aadit M Shah (7 days ago)

Classes in JavaScript don't allow assignments within the class body. Hence, the following code is invalid: class Test { x = 123; // This throws an error. lazy random = this.x + Math.random(); // Similarly, this should be invalid.}

Hence, the more I think about it the more it makes sense to use the following syntax instead: const zeros = { head: , lazy tail() { return this; } };

class Random { lazy random() { return Math.random(); } }

const x = new Random; const y = new Random;

console.log(x.value === x.value); // true console.log(x.value === y.value); // false (in all probability)

It's more verbose than my initial proposal. However, as Andrea mentioned it's consistent with the class method syntax and the this keyword can be used within the lazy getter without causing confusion. Finally, I think that lazy <name>() { <body> } should be syntactic

sugar for: get name() { return Object.defineProperty(this, "<name>", { value: (function () { <body> }()), configurable: false // Making it non-configurable is debatable. }).<name>; }

Using Object.defineProperty instead of simple assignment will ensure that it works in strict mode. In addition, it'll make the property non- writable which is the behavior we want because it's non-writable before memoization too, since it doesn't have a setter.

On Tue, Jun 12, 2018, at 5:52 AM, Andrea Giammarchi wrote:

Actually, never mind. I've just realized this would work as well, and it's clean enough.>

class Test {
  x = 123;
  lazy random = this.x + Math.random();
}

const test = {
  x: 123,
  lazy random: this.x + Math.random()
};

My only concern is if that class method is created N times per each instance (one of the reasons I use lazy getters in classes prototypes instead of per object) as it is AFAIK for current class fields if you attach an arrow function, instead of once per class, which seems to be preferable in a class context.> Otherwise, I'd be happy to see that happen.

Best

On Tue, Jun 12, 2018 at 11:44 AM, Andrea Giammarchi <andrea.giammarchi at gmail.com> wrote:>> it's not about being difficult, it's about compatibility with

classes, where getters are OK since ever. However, since classes fields are in Stage 3 [1] maybe it's OK to have:>>

class Test {
  x = 123;
  lazy random = () => this.x + Math.random();
}

However, like you've noticed, while it's easy to reason about the context within a class declaration, it's easy to create a footgun outside that.>>

const test = {
  x: 123,
  lazy random: () => this.x + Math.random()
};

That is a whole new meaning of arrow function I'd rather like not to ever encounter.>> So what about extra new syntax?

class Test {
  x = 123;
  lazy random() => this.x + Math.random();
}

const test = {
  x: 123,
  lazy random() => this.x + Math.random()
};

But I'm pretty sure at methods shorthand discussions that came up and for some reason didn't work.>>

[1] tc39/proposal-class-fields#field-declarations>> On Tue, Jun 12, 2018 at 11:32 AM, Aadit M Shah <aaditmshah at fastmail.fm> wrote:>>> __

Actually, from a parsing perspective I believe it shouldn't be too difficult to implement the lazy name: expression syntax. In addition, I'm not too keen on your lazy name() { return expression; } syntax because:>>>

  1. It's more verbose.
  2. It seems to me that it's no different than creating a regular getter: const take = (n, xs) => n === ? null : xs && { head: xs.head, get tail() { const value = take(n - 1, xs.tail); Object.defineProperty(this, "tail", { configurable: false, get: () => value }); return value; } };>>> Regarding the second bullet point, I've probably misunderstood what you were trying to convey. Perhaps you could elucidate.>>> Anyway, making the property non-configurable after accessing it seems like a reasonable thing to do.>>>

On Tue, Jun 12, 2018, at 3:44 AM, Andrea Giammarchi wrote:

My 2 cents, I use lazy getters since about ever and I'd love to have such syntax in place but I think there is room for some improvement / simplification in terms of syntax.>>>> ## Keep it getish**

From parsing perspective, introducing lazy tail() seems way simpler than introducing lazy tail: for the simple reason that everything that can parse get tail() and set tail() is in place already in every engine. I don't write them but I'm sure having an extra keyboard to catch shouldn't be crazy complicated.>>>> ## class compatible

because you used delete this.tail and mentioned functional programming, I'd like to underline ES doesn't force anyone to one programming style or another. That means new syntax should play nicely with classes too, and in this case the proposal doesn't seem to address that because of the direct value mutation, as generic property, and the removal of that property from the object, something not needed if inherited.>>>> My variant would do the same, except it would keep the value an accessor:>>>>

const take = (n, xs) => n === 0 ? null : xs && {
    head: xs.head,
    lazy tail() {
      return Object.defineProperty(this, 'tail', {
        configurable: false,
        get: (value =>
          // still a getter
          () => value
        )(
          // executed once
          take(n - 1, xs.tail)
        )
      }).tail;
    }
};

This would keep initial accessor configuration, in terms of enumerability, but it will freeze its value forever and, on top of that, this will play already well with current valid ES2015 classes syntax.>>>> I also believe myself proposed something similar a while ago (or somebody else and I agreed with that proposal) but for some reason it never landed.>>>> Hopefully this time the outcome would be different.

Best

On Tue, Jun 12, 2018 at 9:13 AM, Aadit M Shah <aaditmshah at fastmail.fm> wrote:>>>>> __

Hello TC39,

I recently opened an issue[1] in the tc39/ecma262[2] repository, proposing a new syntax for lazy getters, and I was directed to the CONTRIBUTING[3] page which stated that I should start a conversation on this mailing list.>>>>> So, my feature proposal is to have syntactic sugar for creating lazy getters[4]. To summarize my original proposal (which you can read by following the very first link above), I find that creating lazy getters is very verbose. For example, consider:>>>>> const take = (n, xs) => n === ? null : xs && { head: xs.head, get tail() { delete this.tail; return this.tail = take(n - 1, xs.tail); } };

My proposed solution is to add a new keyword lazy to the language. This keyword can only be used as a prefix to longhand property names in object initializers, and it defers the execution of the value expression until the property is accessed. In short, it's just syntactic sugar for lazy getters:>>>>> const take = (n, xs) => n === ? null : xs && { head: xs.head, lazy tail: take(n - 1, xs.tail) };

This is purely syntactic sugar. The semantics of this new syntax would remain the same as that of the desugared syntax. In particular, calling Object.getOwnPropertyDescriptor(list, "tail") would return an accessor descriptor before accessing list.tail and a data descriptor afterwards.>>>>> Furthermore, there are other advantages of having this syntactic sugar. For example, creating cyclic data structures becomes much easier. Examples are provided in my original proposal which is linked above. Hope to hear your thoughts on this.>>>>> , Aadit M Shah


es-discuss mailing list es-discuss at mozilla.org, mail.mozilla.org/listinfo/es-discuss

Links:

  1. tc39/ecma262#1223
  2. tc39/ecma262
  3. tc39/ecma262/blob/master/CONTRIBUTING.md
  4. developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get#Smart_self
# herby at mailbox.sk (7 days ago)

On June 12, 2018 11:32:22 AM GMT+02:00, Aadit M Shah <aaditmshah at fastmail.fm> wrote:

Actually, from a parsing perspective I believe it shouldn't be too difficult to implement the lazy name: expression syntax. In addition, I'm not too keen on your lazy name() { return expression; } syntax because:

  1. It's more verbose.
  2. It seems to me that it's no different than creating a regular getter:

const take = (n, xs) => n === ? null : xs && { head: xs.head,
get tail() { const value = take(n - 1, xs.tail); Object.defineProperty(this, "tail", { configurable: false, get: () => value }); return value; } };

I am pretty sure Andrea mixed syntax of lazy getter with its implementation for brevity, and the actual lazy getter would look like:

lazy tail() { return take(n - 1, xs.tail); }

Regarding the second bullet point, I've probably misunderstood what you were trying to convey. Perhaps you could elucidate. Anyway, making the property non-configurable after accessing it seems like a reasonable thing to do.

Here I disagree. No syntax construct so far forces immutability. The get x() / set x() ones are configurable. If you defined lazy getter so far by get(), you could have changed it using Object.defineProperties if there was some strange need for it. You had to explicitly freeze etc. or defineProperty with configurable false if you wanted to make it so.

This autofreezing if the value sticks out out this philosophy of " malleable unless specified otherwise".

# Andrea Giammarchi (7 days ago)

Why enforcing configurable false?

The fact the lazy getter is called getter, and the fact that a getter cannot be assigned or the engine throws errors, as opposite of a non configurable property that will silently ignore the assignment, makes me think substituting a lazy getter with a fixed getter is the way to.

But I also think being lazy, usually meaning with expensive computations needed once only, it's a nice to have, and clean guard, to fix that getter forever.

However, that creates observability of the getter, through its own property descriptor, but also, as a getter, it might signal side effects, while a fixed property on the object set as value would indicate there are, probably, no side effects in accessing it.

TL;DR I'm not fully sure about the getter should be defined, if through its inherited definition but with a different get callback that returns the computed value, and only when it comes to classes, or through its own property accessor, when it comes to objects.

Maybe this is the best way to go though, so that the following code:

const test = {
  x: 123,
  lazy random() {
    return this.x + Math.random();
  }
};

class Test {
  x = 123;
  lazy random() {
    return this.x + Math.random();
  }
}

would de-sugar in something like this:

const getDescriptor = (o, k) => (
  o ? (
    Object.getOwnPropertyDescriptor(o, k) ||
    getDescriptor(Object.getPrototypeOf(o), k)
  ) : o
);

const test = {
  x: 123,
  get random() {
    const desc = getDescriptor(this, 'random');
    const value = (function () {
      return this.x + Math.random();
    }).call(this);
    desc.get = () => value;
    Object.defineProperty(this, 'random', desc);
    return value;
  }
};

class Test {
  x = 123;
  get random() {
    const desc = getDescriptor(this, 'random');
    const value = (function () {
      return this.x + Math.random();
    }).call(this);
    desc.get = () => value;
    Object.defineProperty(this, 'random', desc);
    return value;
  }
}

preserving whatever behavior previously defined.

# herby at mailbox.sk (7 days ago)

On June 12, 2018 1:32:04 PM GMT+02:00, herby at mailbox.sk wrote:

This autofreezing if the value sticks out out this philosophy of "

of the value

# Aadit M Shah (7 days ago)

Okay, so my previous statement about field declarations in classes being invalid was incorrect. I didn't see Andrea's link to the class field declarations proposal[1]. Hence, from what I understand we're considering the following syntax: const zeros = { head: , lazy tail: this };

class Random { lazy value = Math.random(); }

As for semantics, Herby's philosophy of "malleable unless specified otherwise" makes sense. Hence, the above code would be transpiled to: const zeros = { head: , get tail() { return Object.defineProperty(this, "tail", { value: this }).tail; } };

class Random { get value() { return Object.defineProperty(this, "value", { value: Math.random() }).value; } }

I guess we'd also be adopting the syntax for private fields and static fields? For example, lazy #value and lazy static #value?

On Tue, Jun 12, 2018, at 7:32 AM, herby at mailbox.sk wrote:

On June 12, 2018 11:32:22 AM GMT+02:00, Aadit M Shah <aaditmshah at fastmail.fm> wrote:>> Actually, from a parsing perspective I believe it shouldn't be too

difficult to implement the lazy name: expression syntax. In addition, I'm not too keen on your lazy name() { return expression;>> } syntax because:

  1. It's more verbose.
  2. It seems to me that it's no different than creating a regular getter:

const take = (n, xs) => n === ? null : xs && { head: xs.head,
get tail() { const value = take(n - 1, xs.tail); Object.defineProperty(this, "tail", { configurable: false,>> get: () => value }); return value; } };

I am pretty sure Andrea mixed syntax of lazy getter with its implementation for brevity, and the actual lazy getter would look like:> lazy tail() { return take(n - 1, xs.tail); }

Regarding the second bullet point, I've probably misunderstood what you>> were trying to convey. Perhaps you could elucidate. Anyway, making the property non-configurable after accessing it seems>> like a reasonable thing to do.

Here I disagree. No syntax construct so far forces immutability. The get x() / set x() ones are configurable. If you defined lazy getter so far by get(), you could have changed it using Object.defineProperties if there was some strange need for it. You had to explicitly freeze etc. or defineProperty with configurable false if you wanted to make it so.> This autofreezing if the value sticks out out this philosophy of " malleable unless specified otherwise".>

On Tue, Jun 12, 2018, at 3:44 AM, Andrea Giammarchi wrote:

My 2 cents, I use lazy getters since about ever and I'd love to have such syntax in place but I think there is room for some improvement / simplification in terms of syntax.> ## Keep it getish**

From parsing perspective, introducing lazy tail() seems way simpler>>> than introducing lazy tail: for the simple reason that everything>>> that can parse get tail() and set tail() is in place already in>>> every engine. I don't write them but I'm sure having an extra keyboard to catch shouldn't be crazy complicated.> ## class compatible

because you used delete this.tail and mentioned functional programming, I'd like to underline ES doesn't force anyone to one programming style or another. That means new syntax should play nicely with classes too, and in this case the proposal doesn't seem to address that because of the direct value mutation, as generic property, and the removal of that property from the object, something>>> not needed if inherited.> My variant would do the same, except it would keep the value an accessor:>

const take = (n, xs) => n === 0 ? null : xs && {
  head: xs.head,
  lazy tail() {
    return Object.defineProperty(this, 'tail', {
      configurable: false,
      get: (value =>
        // still a getter
        () => value
      )(
        // executed once
        take(n - 1, xs.tail)
      )
    }).tail;
  }
};

This would keep initial accessor configuration, in terms of enumerability, but it will freeze its value forever and, on top of that, this will play already well with current valid ES2015 classes syntax.> I also believe myself proposed something similar a while ago (or somebody else and I agreed with that proposal) but for some reason it>>> never landed.> Hopefully this time the outcome would be different.

Best

On Tue, Jun 12, 2018 at 9:13 AM, Aadit M Shah <aaditmshah at fastmail.fm> wrote:>> __

Hello TC39,

I recently opened an issue[1] in the tc39/ecma262[2] repository, proposing a new syntax for lazy getters, and I was directed to the>>>> CONTRIBUTING[3] page which stated that I should start a conversation>>>> on this mailing list.>> So, my feature proposal is to have syntactic sugar for creating lazy>>>> getters[4]. To summarize my original proposal (which you can read by>>>> following the very first link above), I find that creating lazy getters is very verbose. For example, consider:>> const take = (n, xs) => n === ? null : xs && { head: xs.head, get tail() { delete this.tail; return this.tail = take(n - 1, xs.tail); } };

My proposed solution is to add a new keyword lazy to the language.>>>> This keyword can only be used as a prefix to longhand property names>>>> in object initializers, and it defers the execution of the value expression until the property is accessed. In short, it's just syntactic sugar for lazy getters:>> const take = (n, xs) => n === ? null : xs && { head: xs.head, lazy tail: take(n - 1, xs.tail) };

This is purely syntactic sugar. The semantics of this new syntax would remain the same as that of the desugared syntax. In particular,

calling Object.getOwnPropertyDescriptor(list, "tail") would return>> an accessor descriptor before accessing list.tail and a data descriptor>>>> afterwards.>> Furthermore, there are other advantages of having this syntactic sugar. For example, creating cyclic data structures becomes much easier. Examples are provided in my original proposal which is linked

above. Hope to hear your thoughts on this.>> , Aadit M Shah


es-discuss mailing list es-discuss at mozilla.org, mail.mozilla.org/listinfo/es-discuss

Links:

  1. tc39/ecma262#1223
  2. tc39/ecma262
  3. tc39/ecma262/blob/master/CONTRIBUTING.md

developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get#Smart_self-overwriting_lazy_getters

Links:

  1. tc39/proposal-class-fields#field
# T.J. Crowder (7 days ago)

On Tue, Jun 12, 2018 at 12:31 PM, Aadit M Shah <aaditmshah at fastmail.fm>

wrote:

Classes in JavaScript don't allow assignments within the class body.

Hence,

the following code is invalid:

class Test { x = 123; // This throws an error. lazy random = this.x + Math.random(); // Similarly, this should be invalid. }

Andrea was using syntax from the class fields proposal, currently at Stage 3 and fairly commonly used via transpilation (he said "class fields" later in the post, but didn't emphasize it).

-- T.J. Crowder

# Andrea Giammarchi (7 days ago)

forgot about this:

I think {get: getterFn, lazy: true, …} could work.

sort of, because descriptors can be only for properties or accessors, but lazy + set, as accessor might look weird.

However, if I can dare bringing in another use case I have daily in my code, I have a case for having a setter that defines the getter if explicit, so that by default I have a lazy getter, but if before it gets used somebody sets the property, it overrides the lazy getter.

This might happen, as example, during constructor time, as opposite of after initialization.

Example:

class Weirdo {
  constructor(theThing = null) {
    if (theThing != null) {
      this.theThing = theThing;
    }
  }
  get theThing() {
    const value = doVeryExpensiveThing();
    Object.defineProperty(this, 'theThing', {
      get: () => value
    });
    return value;
  }
  set theThing(value) {
    Object.defineProperty(this, 'theThing', {
      get: () => value
    });
  }
}

Honestly, I don't see why this would create any issue with the accessor descriptor, so that I can lazy theThing() { return doVeryExpensiveThing(); } and eventually provide a set theThing() {} to do whatever I want to do when, and if, needed.

So yes, if lazy: true plays well with accessors, and it's to define the special get behavior only, then it's a valid welcome, and nice to have addition.

Object.defineProperty(
  test,
  'random',
  {
    configurable: false, // by default
    enumerable: false, // by default
    lazy: false, // by default
    get() { return this.x + Math.random(); },
    set() { /* optional, do whatever */ }
  }
);

My last question / concern at this point, in case descriptors should be part of this proposal, is how to retrieve the initial lazy descriptor from an object which lazy getter overwrote such value, but I also think this should not be a real-world concern or, eventually, a must know caveat for functional programmers that play procedurally with accessors.

I think I've exposed all my thoughts on this issue, I'll hope for some extra outcome.

Best

# herby at mailbox.sk (7 days ago)

Actually, by malleable I meant only configurable:true, so one can change it via Object.defineProp… api, I did not mean necessarily to define it as value.

I have no strong opinion on what should be there after the first access, but it boils down on how will it be exposed via Object.defineProperty, really, because as little as possible should be changed, IOW as much as possible retained.

So on case things are defined as (only pondering the property descriptor here, the call is obvious):

{ lazy: true, get: () => Math.random() } … [1]

or, bigger example:

{ lazy: { configurable: false }, enumerable: false, get: () => foos.length, set: x => console.log(set ${x}) } … [2]

Then what should be generated is indeed a getter so that setter may be retained as well in [2].

If the definition is:

{ lazy: { configurable: false, writable: false, enumerable: true, compute: () => Math.random() }, enumerable: false } … [3]

then it defines a value (which is not enumerable until first accessed thus created; contrived example, I know).

This post also shows a proposal how to, in future proof way, define what attributes will the replaced getter/value have: put it In lazy field of prop descriptor, lazy: true means shortcut for “the default way / Inherit from what is there now”.

# herby at mailbox.sk (7 days ago)

On June 12, 2018 2:37:05 PM GMT+02:00, herby at mailbox.sk wrote:

Actually, by malleable I meant only configurable:true, so one can change it via Object.defineProp… api, I did not mean necessarily to define it as value.

I have no strong opinion on what should be there after the first access, but it boils down on how will it be exposed via Object.defineProperty, really, because as little as possible should be changed, IOW as much as possible retained.

Not to create a confusion, I do_not propose that both ways be working, what I wanted to say is that only o e should be chosen, and that will select what will be there as a replacement.

Though, now that I think about it,

lazy field = expr;

and

lazy getter() { return expr; }

are different beasts.

Anyway, as Andrea said, enough said for the moment.

# Andrea Giammarchi (7 days ago)

FWIW, I think to keep it simple lazy: true would be enough for the time being.

Having the new descriptor inside the descriptor seems a bit over engineering at this point, imo, but having a field already won't exclude, in the feature, the ability to improve that field (similar way addEventListener on DOM got promoted from (type, handler, boolean) signature to (type, handler, boolean || options))

I also agree that lazy field = expr is a different beast, and it won't play well with descriptors as we know, but it might allow overwrites if accessed directly.

I wouldn't mind that as long as it plays well with objects and classes, and as long as there is an official way to lazy define properties, and if it could be so lazy that if redefined disappears, in that direct assignment form, it would be a solution to all my cases.

# Herbert Vojčík (7 days ago)

The problem here is that if lazy field and lazy getter are indeed different beast, one should be able to discriminate the two inside the property descriptor.

So maybe

// replaces with getter on first access {lazy: producerFn, get: null, ...}

// replaces with value on first access {lazy: producerFn, value: null, ...}

and in case both get and value are present, just be consistent with what is the behaviour now (early error or preferences of one over the other).

# Augusto Moura (7 days ago)

I don't think a new keyword is the right path here. For classes lazy fields can easily be implemented via decorators in the last proposal1 (also, Groovy has previous work implementing a @Lazy annotation2 with AST transformations for the same use case). In code something like:

const lazy = (fieldMetadata) => {
  // change the field value in descriptor to a lazy factory
};

class Foo {

  @lazy bar = slowCalculation();

  @lazy statements = (() => {
     if (this.bar > 0) {
       return fooify();
     } else {
       return barify();
     }
  }());

}

What it's doesn't cover (and in my opinion should be the focus of a new proposal) is Decorators for literal objects. Something like the code below is yet not proposed:

const foo = {
  @lazy bar: 3,
};

I didn't made a deep search in ESDiscuss history but I remember this feature being mentioned sometime ago.

# Andrea Giammarchi (7 days ago)

To me decorators are the answer to pretty much everything but for the last year I've seen zero progress on what seems to be a forever Stage 2 proposal: tc39/proposals#stage-2

Is there anything anyone can do to speed up adoption/implementation of that proposal?

# Michał Wadas (7 days ago)

Yes, something is getting done about decorators. tc39/agendas#398

# Isiah Meadows (6 days ago)

BTW, I proposed similar (lazy values) 9 months ago 1, and it's been on the list plenty of times 2. I'd personally love to see it happen, but I find it not very likely it'd make it, especially as a property (since decorators can rectify that).


Isiah Meadows me at isiahmeadows.com, www.isiahmeadows.com