[Proposal] New syntax for lazy getters

Aadit M Shah aaditmshah at fastmail.fm
Tue Jun 12 11:31:34 UTC 2018


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.> 
> ```js
> 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 Regards
> 
> 
> 
> 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:>> 
>> ```js
>> 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.>> 
>> ```js
>> 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?
>> 
>> ```js
>> 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] https://github.com/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 get*ish**
>>>> 
>>>> 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:>>>> 
>>>> ```js
>>>> 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 Regards
>>>> 
>>>> 
>>>> 
>>>> 
>>>> 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.>>>>> 
>>>>> Regards,
>>>>> Aadit M Shah
>>>>> 
>>>>> _______________________________________________
>>>>> es-discuss mailing list
>>>>> es-discuss at mozilla.org
>>>>> https://mail.mozilla.org/listinfo/es-discuss
>>>>> 
>>> 


Links:

  1. https://github.com/tc39/ecma262/issues/1223
  2. https://github.com/tc39/ecma262
  3. https://github.com/tc39/ecma262/blob/master/CONTRIBUTING.md
  4. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get#Smart_self-overwriting_lazy_getters
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20180612/aabff4cc/attachment-0001.html>


More information about the es-discuss mailing list