[Proposal] New syntax for lazy getters

Herbert Vojčík herby at mailbox.sk
Tue Jun 12 14:03:44 UTC 2018


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).

Herby

Andrea Giammarchi wrote on 12. 6. 2018 14:48:
> 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.
> 
> Regards
> 
> 
> On Tue, Jun 12, 2018 at 2:37 PM, <herby at mailbox.sk 
> <mailto: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.
> 
>     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
> 
>     On June 12, 2018 2:02:28 PM GMT+02:00, Aadit M Shah
>     <aaditmshah at fastmail.fm <mailto:aaditmshah at fastmail.fm>> wrote:
>     >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
>     <mailto:herby at mailbox.sk> wrote:
>      >>
>      >>
>      >> On June 12, 2018 11:32:22 AM GMT+02:00, Aadit M Shah
>      >> <aaditmshah at fastmail.fm <mailto: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 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 <mailto: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 <mailto:es-discuss at mozilla.org>
>     >>>>> https://mail.mozilla.org/listinfo/es-discuss
>     <https://mail.mozilla.org/listinfo/es-discuss>
>     >>>>> 
>     >>> 
>     >>> 
>     >>> Links:
>     >>> 
>     >>> 1. https://github.com/tc39/ecma262/issues/1223
>     <https://github.com/tc39/ecma262/issues/1223>
>     >>> 2. https://github.com/tc39/ecma262 <https://github.com/tc39/ecma262>
>     >>> 3. https://github.com/tc39/ecma262/blob/master/CONTRIBUTING.md
>     <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
>     <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get#Smart_self-overwriting_lazy_getters>
>     >
>      >Links:
>      >
>      >  1.
>     https://github.com/tc39/proposal-class-fields#field-declarations
>     <https://github.com/tc39/proposal-class-fields#field-declarations>
> 
> 


More information about the es-discuss mailing list