Lazy evaluation

# Isiah Meadows (7 years ago)

It'd be really nice if lazy values made it into the spec somehow. I've already found myself using things like this 1 quite a bit, and I've also found myself frequently initializing properties not on first access.

As for what would be a nice API, maybe something like one of these?

class Lazy<T> {
    constructor(init: () => T);
    get(): T; // or error thrown
}

function lazy<T>(init: () => T): () => T; // or error thrown

function lazy<T>(init: () => T): {
    get(): T; // or error thrown
}

Alternatively, syntax might work, with do expression semantics:

const x = lazy do { ... }
// expose via `x.get()` or just `x()`

Isiah Meadows me at isiahmeadows.com

Looking for web consulting? Or a new website? Send me an email and we can get started. www.isiahmeadows.com

# Andrea Giammarchi (7 years ago)

the following is how I usually consider lazy values

class Any {
  _lazy(name) {
    switch (name) {
      case 'uid': return Math.random();
      // others ... eventually
    }
  }
  get uid() {
    var value = this._lazy('uid');
    // from now on, direct access
    Object.defineProperty(this, 'uid', {value});
    return value;
  }
}

const a = new Any;
a.uid === a.uid; // true

If I understand correctly your proposal is to use Lazy as generic descriptor, is that correct ?

Object.defineProperty({}, 'something', new Lazy(function (val) {
  return this.shakaLaka ? val : 'no shakaLaka';
}));

???

If that's the case I see already people confused by arrow function in case they need to access the context, plus no property access optimization once resolved.

It's also not clear if such property can be set again later on (right now it cannot) 'cause lazy definition doesn't always necessarily mean inability to reassign.

What am I missing/misunderstanding?

# Naveen Chawla (7 years ago)

Could you not do this with a promise? If not, what's missing in promise that you could do with "lazy"? Sorry if I've missed the whole premise

# Michael DeByl (7 years ago)

I was going to read this in detail but I was too lazy to properly evaluate it

# Isiah Meadows (7 years ago)

No. Lazy is intended to be an object to be used directly, not a descriptor of any kind.

(My lazy.get() is an unbound method, so using it in a descriptor would get it passed the wrong this.)

Isiah Meadows me at isiahmeadows.com

Looking for web consulting? Or a new website? Send me an email and we can get started. www.isiahmeadows.com

# Andrea Giammarchi (7 years ago)

using it in a descriptor would get it passed the wrong this

sorry, what?

var a = {};
var b = {get() { return this; }};
Object.defineProperty(a, 'self', b);

a.self === a; // true
# Isiah Meadows (7 years ago)

Promises are inherently eager, but also async - consider that new Promise(resolve => resolve(1)) is roughly equivalent to var promise = Promise.resolve(1).

My proposal is for a single immediate value, but created on demand (when you call .get()) rather than immediately.

Isiah Meadows me at isiahmeadows.com

Looking for web consulting? Or a new website? Send me an email and we can get started. www.isiahmeadows.com

# Isiah Meadows (7 years ago)

With my proposed Lazy class, if you were to use an instance as a descriptor, the this value it'd receive would not be a Lazy instance like it'd expect.

Consider it the difference between a.self and b.get() in your example. b.get() is what I'd be expecting.

Isiah Meadows me at isiahmeadows.com

Looking for web consulting? Or a new website? Send me an email and we can get started. www.isiahmeadows.com

# Andrea Giammarchi (7 years ago)

right ... so ... I'm not sure I understand what this proposal would solve.

Instead of this:

obj.val || (obj.val = getValue())

you want to do this

(obj.val || (obj.val = new Lazy(getValue)).get();

Where is the "win" and why is that?

# Isiah Meadows (7 years ago)

It'd solve a problem similarly to Kotlin's by lazy { ... } delegate, .NET's System.Lazy<T>, Swift's lazy var, among many other

languages. It's very useful for lazy initialization 1, such as lazily setting up a database, requesting a resource, among other costly things. 2

How often do you start out with a class like this, where you have an expensive resource you don't want to open right away?

class Foo {
    constructor() {
        this._db = undefined
    }

    _initDb() {
        if (this._db) return this._db
        return this._db = new Promise((resolve, reject) => {
            // open a database connection
            // set up whatever tables you need to
            // etc.
        })
    }
}

Or maybe, a large lookup table that takes a while to build, and might not even be used, so you don't want to do it on load?

var table

function initTable() {
    if (table) return
    table = new Array(10000)
    // do some expensive calculations
}

Things you don't want to initialize right away because initialization is expensive and/or the value might not even be used. That's the problem I'm aiming to solve, and it's something I feel would be useful in its own right in the language, about equal in importance to weak references. (Slightly specialized, but the need is not non-zero.)


Isiah Meadows me at isiahmeadows.com

Looking for web consulting? Or a new website? Send me an email and we can get started. www.isiahmeadows.com

# Andrea Giammarchi (7 years ago)

How often do you start out with a class like this ...

Never, like I've said. This is the lazy pattern I know since ever.

class Foo {
  get _db() {
    return Object.defineProperty(this, '_db', {
      value: new Promise((resolve, reject) => {
        // open a database connection
        // set up whatever tables you need to
        // etc.
      })
    })._db;
  }
}

Whenever you need, you just access this._db, no need to create an enumerable variable and a class method.

It looks cleaner to me.

Things you don't want to initialize right away because initialization

You don't really have to convince me, I've written lazy properties since getters and setters were introduced [1]

All I am saying is that this proposal doesn't compose well with classes, it's just yet another SuperPrimitive for the language.

It is also something trivial to implement on user land, yet I haven't seen many writing code like the following:

function Lazy(fn) {
  let c = false, v;
  return {get(){ return c ? v : (c = !c, v = fn()) }};
}

var o = Lazy(() => Math.random());

o.get(); // ...

Maybe it's me that hasn't seen this widely adopted from some library?

Anyway, this is just my opinion, maybe others would be happy with this.

Best

[1] Class.lazy example WebReflection/prototypal/blob/master/Class.md#classlazycallback

# Andrea Giammarchi (7 years ago)

this proposal doesn't compose well with classes

to expand a little, if you were proposing

class WithLazyVals {
  lazy _db() { return new Promise(...); }
}

I would've taken first flight to come over and hug you.

Best

# Isiah Meadows (7 years ago)

What about this (using the stage 3 class fields proposal)?

declare function lazy<T>(init: () => T): () => T;

class WithLazyVals {
    _db = lazy(() => new Promise(...));
}

Isiah Meadows me at isiahmeadows.com

Looking for web consulting? Or a new website? Send me an email and we can get started. www.isiahmeadows.com

# Andrea Giammarchi (7 years ago)

it wouldn't work, would it ? I mean, you still have to pass through the "ugly" _db.get() thingy, right?

how do you access and trigger the lazy bit within the class?

# Isiah Meadows (7 years ago)

Note the TS-ish declaration above it. That's the variant I was referring to (I presented about 3 different variants initially).

// The declaration I included
declare function lazy<T>(init: () => T): () => T;

On Thu, Aug 31, 2017 at 3:05 PM, Andrea Giammarchi <andrea.giammarchi at gmail.com> wrote:

it wouldn't work, would it ? I mean, you still have to pass through the "ugly" _db.get() thingy, right?

how do you access and trigger the lazy bit within the class?

On Thu, Aug 31, 2017 at 7:56 PM, Isiah Meadows <isiahmeadows at gmail.com> wrote:

What about this (using the stage 3 class fields proposal)?

declare function lazy<T>(init: () => T): () => T;

class WithLazyVals {
    _db = lazy(() => new Promise(...));
}

Isiah Meadows me at isiahmeadows.com

Looking for web consulting? Or a new website? Send me an email and we can get started. www.isiahmeadows.com

On Thu, Aug 31, 2017 at 1:34 PM, Andrea Giammarchi <andrea.giammarchi at gmail.com> wrote:

this proposal doesn't compose well with classes

to expand a little, if you were proposing

class WithLazyVals {
  lazy _db() { return new Promise(...); }
}

I would've taken first flight to come over and hug you.

Best

On Thu, Aug 31, 2017 at 6:25 PM, Andrea Giammarchi <andrea.giammarchi at gmail.com> wrote:

How often do you start out with a class like this ...

Never, like I've said. This is the lazy pattern I know since ever.

class Foo {
  get _db() {
    return Object.defineProperty(this, '_db', {
      value: new Promise((resolve, reject) => {
        // open a database connection
        // set up whatever tables you need to
        // etc.
      })
    })._db;
  }
}

Whenever you need, you just access this._db, no need to create an enumerable variable and a class method.

It looks cleaner to me.

Things you don't want to initialize right away because initialization

You don't really have to convince me, I've written lazy properties since getters and setters were introduced 1

All I am saying is that this proposal doesn't compose well with classes, it's just yet another SuperPrimitive for the language.

It is also something trivial to implement on user land, yet I haven't seen many writing code like the following:

function Lazy(fn) {
  let c = false, v;
  return {get(){ return c ? v : (c = !c, v = fn()) }};
}

var o = Lazy(() => Math.random());
o.get(); // ...

Maybe it's me that hasn't seen this widely adopted from some library?

Anyway, this is just my opinion, maybe others would be happy with this.

Best

1 Class.lazy example

WebReflection/prototypal/blob/master/Class.md#classlazycallback

On Thu, Aug 31, 2017 at 6:03 PM, Isiah Meadows <isiahmeadows at gmail.com> wrote:

It'd solve a problem similarly to Kotlin's by lazy { ... } delegate, .NET's System.Lazy<T>, Swift's lazy var, among many other languages. It's very useful for lazy initialization 1, such as lazily setting up a database, requesting a resource, among other costly things. [2]

How often do you start out with a class like this, where you have an expensive resource you don't want to open right away?

class Foo {
    constructor() {
        this._db = undefined
    }

    _initDb() {
        if (this._db) return this._db
        return this._db = new Promise((resolve, reject) => {
            // open a database connection
            // set up whatever tables you need to
            // etc.
        })
    }
}

Or maybe, a large lookup table that takes a while to build, and might not even be used, so you don't want to do it on load?

var table

function initTable() {
    if (table) return
    table = new Array(10000)
    // do some expensive calculations
}

Things you don't want to initialize right away because initialization is expensive and/or the value might not even be used. That's the problem I'm aiming to solve, and it's something I feel would be useful in its own right in the language, about equal in importance to weak references. (Slightly specialized, but the need is not non-zero.)

[2]:

stackoverflow.com/questions/978759/what-is-lazy-initialization-and-why-is-it-useful

Isiah Meadows me at isiahmeadows.com

Looking for web consulting? Or a new website? Send me an email and we can get started. www.isiahmeadows.com

On Thu, Aug 31, 2017 at 12:23 PM, Andrea Giammarchi <andrea.giammarchi at gmail.com> wrote:

right ... so ... I'm not sure I understand what this proposal would solve.

Instead of this:

obj.val || (obj.val = getValue())

you want to do this

(obj.val || (obj.val = new Lazy(getValue)).get();

Where is the "win" and why is that?

On Thu, Aug 31, 2017 at 5:18 PM, Isiah Meadows <isiahmeadows at gmail.com> wrote:

With my proposed Lazy class, if you were to use an instance as a descriptor, the this value it'd receive would not be a Lazy instance like it'd expect.

Consider it the difference between a.self and b.get() in your example. b.get() is what I'd be expecting.

Isiah Meadows me at isiahmeadows.com

Looking for web consulting? Or a new website? Send me an email and we can get started. www.isiahmeadows.com

On Thu, Aug 31, 2017 at 12:12 PM, Andrea Giammarchi <andrea.giammarchi at gmail.com> wrote:

using it in a descriptor would get it passed the wrong this

sorry, what?

var a = {};
var b = {get() { return this; }};
Object.defineProperty(a, 'self', b);

a.self === a; // true

On Thu, Aug 31, 2017 at 5:09 PM, Isiah Meadows <isiahmeadows at gmail.com> wrote:

No. Lazy is intended to be an object to be used directly, not a descriptor of any kind.

(My lazy.get() is an unbound method, so using it in a descriptor would get it passed the wrong this.)

Isiah Meadows me at isiahmeadows.com

Looking for web consulting? Or a new website? Send me an email and we can get started. www.isiahmeadows.com

On Thu, Aug 31, 2017 at 9:39 AM, Andrea Giammarchi <andrea.giammarchi at gmail.com> wrote:

the following is how I usually consider lazy values

class Any {
  _lazy(name) {
    switch (name) {
      case 'uid': return Math.random();
      // others ... eventually
    }
  }
  get uid() {
    var value = this._lazy('uid');
    // from now on, direct access
    Object.defineProperty(this, 'uid', {value});
    return value;
  }
}

const a = new Any;
a.uid === a.uid; // true

If I understand correctly your proposal is to use Lazy as generic descriptor, is that correct ?

Object.defineProperty({}, 'something', new Lazy(function (val)
{
  return this.shakaLaka ? val : 'no shakaLaka';
}));

???

If that's the case I see already people confused by arrow function in case they need to access the context, plus no property access optimization once resolved.

It's also not clear if such property can be set again later on (right now it cannot) 'cause lazy definition doesn't always necessarily mean inability to reassign.

What am I missing/misunderstanding?

On Thu, Aug 31, 2017 at 2:21 PM, Isiah Meadows <isiahmeadows at gmail.com> wrote:

It'd be really nice if lazy values made it into the spec somehow. I've already found myself using things like this 1 quite a bit, and I've also found myself frequently initializing properties not on first access.

1:

gist.github.com/isiahmeadows/4c0723bdfa555a1c2cb01341b323c3d4

As for what would be a nice API, maybe something like one of these?

class Lazy<T> {
    constructor(init: () => T);
    get(): T; // or error thrown
}

function lazy<T>(init: () => T): () => T; // or error thrown

function lazy<T>(init: () => T): {
    get(): T; // or error thrown
}

Alternatively, syntax might work, with do expression semantics:

const x = lazy do { ... }
// expose via `x.get()` or just `x()`

Isiah Meadows me at isiahmeadows.com

Looking for web consulting? Or a new website? Send me an email and we can get started. www.isiahmeadows.com


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


Isiah Meadows me at isiahmeadows.com

Looking for web consulting? Or a new website? Send me an email and we can get started. www.isiahmeadows.com

# Andrea Giammarchi (7 years ago)

Sorry I don't speak TS, I speak ES.

Can you please tell me in JavaScript what does that do?

# Isiah Meadows (7 years ago)

It takes a function, and returns a function that (if necessary) initializes the value and then gets it.

Isiah Meadows me at isiahmeadows.com

Looking for web consulting? Or a new website? Send me an email and we can get started. www.isiahmeadows.com

# Andrea Giammarchi (7 years ago)

so in JavaScript that results into this._db() each time, resolved lazily with the first value returned once ?

I still think my approach is cleaner and more transparent.

get _thing() { return defineProperty(this, 'thing', value) }

but if your TS-ish stuff translates into that, works for me

# Isiah Meadows (7 years ago)

Yes. I'll point out that having it as a function, rather than a property-specific thing, makes it more flexible, since you can define constants as lazy values (I do that in quite a few places).

If you want to make it transparent, it's not that hard to make a single-line getter/method that hides the abstraction.

Granted, most of my lazy values are properties, not constants, so I could consider it an acceptable compromise.

Isiah Meadows me at isiahmeadows.com

Looking for web consulting? Or a new website? Send me an email and we can get started. www.isiahmeadows.com

# Andrea Giammarchi (7 years ago)

it's a matter of semantics.

If I see this

var later = anyWrappingName(() => Math.random());

// this is an assumption, not something obvious
later() === later()

If instead, I write this:

this.later === this.later;

I expect that to never possibly fail like arr.length === arr.length or any obj.prop, in APIs with common sense, are equal to obj.prop.

Invokes via instances and objects? It's never obvious at first look, if that is a method execution, but it's surely a new invoke.

If you've trapped once the result behind the scene, reading that, is just noise for anyone eyes.

So, once again, are we proposing something that results into exactly this?

class Later {
  get thing() {
    return Object.defineProperty(this, 'thing', {value: anyLazy()});
  }
  constructor() {
    // always true, no matter when/where
    this.thing === this.thing;
  }
}

If so, I'm happy. If not, this is confusing and solving not much.

Best

# Michał Wadas (7 years ago)

Why not something like decorators (not sure if decorator proposal covers this already)?

class Foo { @cached get bar() { return something(this); } }

On 31 Aug 2017 10:30 pm, "Andrea Giammarchi" <andrea.giammarchi at gmail.com>

wrote:

it's a matter of semantics.

If I see this

var later = anyWrappingName(() => Math.random());

// this is an assumption, not something obvious
later() === later()

If instead, I write this:

this.later === this.later;

I expect that to never possibly fail like arr.length === arr.length or any obj.prop, in APIs with common sense, are equal to obj.prop.

Invokes via instances and objects? It's never obvious at first look, if that is a method execution, but it's surely a new invoke.

If you've trapped once the result behind the scene, reading that, is just noise for anyone eyes.

So, once again, are we proposing something that results into exactly this?

class Later {
  get thing() {
    return Object.defineProperty(this, 'thing', {value: anyLazy()});
  }
  constructor() {
    // always true, no matter when/where
    this.thing === this.thing;
  }
}

If so, I'm happy. If not, this is confusing and solving not much.

Best

# Alexander Jones (7 years ago)

I'm surprised no-one has mentioned memoization yet.

A lazy constant can be modelled with memoization of a nullary function. Memoization only works when you have defined a concept of value for the argument pack (to act as the cache key), which IMO is probably best dealt with by something like Immutable.js. The 0 argument "constant" case is trivial and need not invoke something like Immutable, but I'd rather try to consider approaching something a little more general than just "lazy values".

const memoize = fn => /* some definition based on Immutable values as

function arguments */;
const expensiveToRetrieve = memoize(() => whatever);

const fastestRoute = memoize((pointA, pointB) => whatever);

With freestanding "getters" you could even have these resembling normal variables without the need for (). Not sure where we stand on that though.

Alex

# kai zhu (7 years ago)

inline

On Sep 1, 2017, at 1:03 AM, Isiah Meadows <isiahmeadows at gmail.com> wrote:

It'd solve a problem similarly to Kotlin's by lazy { ... } delegate, .NET's System.Lazy<T>, Swift's lazy var, among many other languages. It's very useful for lazy initialization [1], such as lazily setting up a database, requesting a resource, among other costly things. [2]

How often do you start out with a class like this, where you have an expensive resource you don't want to open right away?

class Foo {
   constructor() {
       this._db = undefined
   }

   _initDb() {
       if (this._db) return this._db
       return this._db = new Promise((resolve, reject) => {
           // open a database connection
           // set up whatever tables you need to
           // etc.
       })
   }
}

lazy db-initialization is over-engineering and unnecessary. almost all applications i encounter can be designed more simply with explicit db-initialization during startup. the only problem that arises is when the user tries to access the db before db-initialization completes. my solution (for indexeddb) is to wrap every db-crud method with a deferred-callback that waits for db-initialization to complete (or a promise object as andreas mentioned).

kaizhu256.github.io/node-db-lite/build..master..travis-ci.org/apidoc.html#apidoc.element.db-lite.storageDefer, kaizhu256.github.io/node-db-lite/build..master..travis-ci.org/apidoc.html#apidoc.element.db-lite.storageDefer

storageGetItem = function (key, onError) { /*

  • this function will get the item with the given key from storage / defer({ action: 'getItem', key: key }, onError); } storageRemoveItem = function (key, onError) { /
  • this function will remove the item with the given key from storage / defer({ action: 'removeItem', key: key }, onError); } storageSetItem = function (key, value, onError) { /
  • this function will set the item with the given key and value to storage / defer({ action: 'setItem', key: key, value: value }, onError); } storageDefer = function (options, onError) { /
  • this function will defer options.action until storage is ready / var data, isDone, objectStore, onError2, request, tmp; onError = onError || function (error) { // validate no error occurred console.assert(!error, error); }; if (!storage) { deferList.push(function () { defer(options, onError); }); init(); return; } switch (modeJs) { case 'browser': onError2 = function () { / istanbul ignore next / if (isDone) { return; } isDone = true; onError( request && (request.error || request.transaction.error), data || request.result || '' ); }; switch (options.action) { case 'clear': case 'removeItem': case 'setItem': objectStore = storage .transaction(storageDir, 'readwrite') .objectStore(storageDir); break; default: objectStore = storage .transaction(storageDir, 'readonly') .objectStore(storageDir); } switch (options.action) { case 'clear': request = objectStore.clear(); break; case 'getItem': request = objectStore.get(String(options.key)); break; case 'keys': data = []; request = objectStore.openCursor(); request.onsuccess = function () { if (!request.result) { onError2(); return; } data.push(request.result.key); request.result.continue(); }; break; case 'length': request = objectStore.count(); break; case 'removeItem': request = objectStore.delete(String(options.key)); break; case 'setItem': request = objectStore.put(options.value, String(options.key)); break; } ['onabort', 'onerror', 'onsuccess'].forEach(function (handler) { request[handler] = request[handler] || onError2; }); // debug request local._debugStorageRequest = request; break; case 'node': switch (options.action) { case 'clear': child_process.spawnSync('rm -f ' + storage + '/', { shell: true, stdio: ['ignore', 1, 2] }); setTimeout(onError); break; case 'getItem': fs.readFile( storage + '/' + encodeURIComponent(String(options.key)), 'utf8', // ignore error function (error, data) { onError(error && null, data || ''); } ); break; case 'keys': fs.readdir(storage, function (error, data) { onError(error, data && data.map(decodeURIComponent)); }); break; case 'length': fs.readdir(storage, function (error, data) { onError(error, data && data.length); }); break; case 'removeItem': fs.unlink( storage + '/' + encodeURIComponent(String(options.key)), // ignore error function () { onError(); } ); break; case 'setItem': tmp = os.tmpdir() + '/' + Date.now() + Math.random(); // save to tmp fs.writeFile(tmp, options.value, function (error) { // validate no error occurred console.assert(!error, error); // rename tmp to key fs.rename( tmp, storage + '/' + encodeURIComponent(String(options.key)), onError ); }); ...

Or maybe, a large lookup table that takes a while to build, and might not even be used, so you don't want to do it on load?

var table

function initTable() {
   if (table) return
   table = new Array(10000)
   // do some expensive calculations
}

this is a textbook-example for using memoization as alexander mentioned. here's a real-world memoized file-server solution kaizhu256.github.io/node-utility2/build..master..travis-ci.org/apidoc.html#apidoc.element.utility2.middlewareFileServer, kaizhu256.github.io/node-utility2/build..master..travis-ci.org/apidoc.html#apidoc.element.utility2.middlewareFileServer

middlewareFileServer = function (request, response, nextMiddleware) { /*

  • this function will run the middleware that will serve files / if (request.method !== 'GET' || local.modeJs === 'browser') { nextMiddleware(); return; } request.urlFile = (process.cwd() + request.urlParsed.pathname // security - disable parent directory lookup .replace((/./..//g), '/')) // replace trailing '/' with '/index.html' .replace((//$/), '/index.html'); // serve file from cache local.taskCreateCached({ cacheDict: 'middlewareFileServer', key: request.urlFile // run background-task to re-cache file }, function (onError) { local.fs.readFile(request.urlFile, function (error, data) { onError(error, data && local.base64FromBuffer(data)); }); }, function (error, data) { // default to nextMiddleware if (error) { nextMiddleware(); return; } // init response-header content-type request.urlParsed.contentType = (/.[^.]$/).exec(request.urlParsed.pathname); request.urlParsed.contentType = local.contentTypeDict[ request.urlParsed.contentType && request.urlParsed.contentType[0] ]; local.serverRespondHeadSet(request, response, null, { 'Content-Type': request.urlParsed.contentType }); // serve file from cache response.end(local.base64ToBuffer(data)); }); } taskCreateCached = function (options, onTask, onError) { /
  • this function will
    1. if cache-hit, then call onError with cacheValue
    1. run onTask in background
    1. save onTask's result to cache
    1. if cache-miss, then call onError with onTask's result */ local.onNext(options, function (error, data) { switch (options.modeNext) { // 1. if cache-hit, then call onError with cacheValue case 1: // read cacheValue from memory-cache local.cacheDict[options.cacheDict] = local.cacheDict[options.cacheDict] || {}; options.cacheValue = local.cacheDict[options.cacheDict][options.key]; if (options.cacheValue) { // call onError with cacheValue options.modeCacheHit = true; onError(null, JSON.parse(options.cacheValue)); if (!options.modeCacheUpdate) { break; } } // run background-task with lower priority for cache-hit setTimeout(options.onNext, options.modeCacheHit && options.cacheTtl); break; // 2. run onTask in background case 2: local.taskCreate(options, onTask, options.onNext); break; default: // 3. save onTask's result to cache // JSON.stringify data to prevent side-effects on cache options.cacheValue = JSON.stringify(data); if (!error && options.cacheValue) { local.cacheDict[options.cacheDict][options.key] = options.cacheValue; } // 4. if cache-miss, then call onError with onTask's result if (!options.modeCacheHit) { onError(error, options.cacheValue && JSON.parse(options.cacheValue)); } (options.onCacheWrite || local.nop)(); break; } }); options.modeNext = 0; options.onNext(); }
# Andrea Giammarchi (7 years ago)

I thought decorators were nowhere higher than stage 0 (since ever)

# Michał Wadas (7 years ago)

Stage 2, but they move really slow.

# kdex (7 years ago)

Just so that there is no confusion: There's also function expression decorators and method parameter decorators, both of which stage 0.

# Andrea Giammarchi (7 years ago)

then yes, decorators solve my use case pretty well.

const lazy = (Class, prop, desc) => {
  var get = desc.get;
  desc.get = function () {
    var result = get.apply(this, arguments);
    Object.defineProperty(this, prop, {value: result});
    return result;
  };
};

class A {
  @lazy
  get random() { return Math.random(); }
}

let a = new A;
a.random === a.random; // true
# herby at mailbox.sk (7 years ago)

On August 31, 2017 6:15:20 PM GMT+02:00, Isiah Meadows <isiahmeadows at gmail.com> wrote:

Promises are inherently eager, but also async - consider that new Promise(resolve => resolve(1)) is roughly equivalent to var promise = Promise.resolve(1).

My proposal is for a single immediate value, but created on demand (when you call .get()) rather than immediately.

But then, it seems to me Andrea's self-rewriting getter gets to the point.

Maybe there can be 'caching' getter planned as in:

Object.defineProperty(foo, "bar", { get: () => "baz", caching: true });

with a shortcut

Object.defineLazyValue( foo, "bar", () => "baz");

which is implementable via a lib.

# Michał Wadas (7 years ago)

BTW consider this:

class Foo {
  get bar() {
    Object.defineProperty(this, 'bar', {value: []});
    return this.bar;
  }
}
const a = new Foo;
Foo.prototype.bar;
const b = new Foo;
const c = new Foo;
// b.bar === c.bar

So I recommend this instead:

const wm = new WeakMap;
class Foo {
  get bar() {
    if (!wm.has(this)) {
      wm.set(this, []);
    }
    return wm.get(this);
  }
}
const a = new Foo;
Foo.prototype.bar;
const b = new Foo;
const c = new Foo;
// b.bar !== c.bar
# Andrea Giammarchi (7 years ago)

Using weakmaps has disadvantages if instances are many, due pressure, and it's also slower than just further property access, requiring a wm.get(...) each time.

On top of that, you workaround the issue, but you don't solve it.

The platform way to solve your issue is the following:

class Foo {
  get bar() {
    if (this.constructor.prototype === this) throw 'unauthorized';
    var result = [];
    Object.defineProperty(this, 'bar', {value: result});
    return result;
  }
}

I've used the long check instead of this === Foo.prototype because the condition this.constructor.prototype works through inheritance chain too.

I also use a variable and return it instead because not everyone knows that IE might go in recursion accessing a getter within the getter ;-)

# Darien Valentine (7 years ago)

I use the WeakMap approach, too. Recently I find myself writing classes where the class has a corresponding WeakMap holding the "shadow instances" (as opposed to having one WM per property):

const PRIV = new WeakMap();

class Foo {
  constructor() {
    PRIV.set(this, { bar: 0, /*...other private state init...*/ });
  }

  get bar() {
    return PRIV.get(this).bar;
  }

  set bar(val) {
    if (!Number.isInteger(val)) throw new TypeError('no!');

    PRIV.get(this).bar = val;
  }
}

I only do this for classes that are part of some public interface, where I want finer control over what state is exposed and wish to ensure that the object cannot enter an invalid state; for internal stuff it’d probably be overkill.

The property-that-redefines-itself approach makes me uncomfortable because I don’t want property access to have observable side effects from the consumer side.

const foo = new ClassWithThatPattern;

foo.hasOwnProperty('bar'); // false
foo.bar;
foo.hasOwnProperty('bar'); // true

In any case ... re: lazy initialization, I would agree that decorators represent a perfect way to make this pattern declarative & expressive. I suppose the private instance properties aspect of the class properties proposal, now at stage 3, also provides a way to reduce boilerplate by a bit, but not to the same degree.

(I’d second kaizu’s opinion that the example of lazy init of a db seems kind of iffy, at least for node apps, where you kinda want to know your db is working before you even init the rest of the app, as failure almost invariably represents a terminal condition — but that said, it’s just an example, and there are certainly cases where lazy init of properties is worthwhile, e.g. when you have very large collections of many small instances and only an unknown-in-advance subset will actually need such-and-such properties calculated ultimately.)

# Andrea Giammarchi (7 years ago)

Darien you managed to sneak-in another pattern that has nothing to do with laziness, it's rather an approach to simulate private variables (exposing them though).

To meaningfully compare your solution with mine you need these two classes:

const WM = new WeakMap();

class CaseWM {
  get bar() {
    var shadow = WM.get(this);
    if (!shadow) {
      shadow = {bar: Math.random()};
      WM.set(this, shadow);
    }
    return shadow.bar;
  }
}

class CaseLazy {
  get bar() {
    var value = Math.random();
    Object.defineProperty(this, 'bar', {value});
    return value;
  }
}

You can verify the benchmark here: jsperf.com/lazy-property-patterns

In my Chromium the lazy property is around 4X faster and there's no GC/memory pressure due WeakMap.

It's a matter of trades-off and compromise. I don't care about hasOwnProperty for properties defined in the prototype, it's a misleading check anyway and I don't see any real-world side effect, or better, I cannot think of a single case I've had so far that would've been problematic.

I use the in operator and you should probably do the same if that's a concern, or maybe explain why/when/how that could be a concern.

# Darien Valentine (7 years ago)

I’m not sure what was sneaky haha ... it was in response to the prior discussion of that subject, and I think it is quite related to lazy props, example code aside, since lazy props are just a specific case for the more general pattern of properties with some form of associated state / stateful behavior.

The benchmark is great info. I agree that the observability via hasOwnProperty/getOwnPropertyDescriptor is not at all likely to create issues for any given case, but I was speaking about my reservations with library code in mind. For apps or internal classes I simply use _underscoreProps for the same need. Perhaps property definition is still faster even then, in which case I’d switch to it for those cases. But for the public API of a lib, I prefer to stick with the keep-it-unobservable rule (people have relied on stranger things).

# Andrea Giammarchi (7 years ago)

For apps or internal classes I simply use _underscoreProps for the same

need.

that is still not an answer to lazy property definition ;-)

I agree in most you are saying but you keep diverging from the topic of this thread: Lazy evaluation

Neither wm.get(this) nor this._pseudoPrivate are strictly solutions to the topic.

That's all I'm saying.

# Darien Valentine (7 years ago)

Hm, I’m sorry, but I don’t follow. The accessor + shadow property pattern is a (rather common?) way to achieve lazy instantiation of properties, since it provides the necessary hook for "do this on first access". But I’m not suggesting it as a new solution in any case, I was only commenting on the subject of ways-one-can-have-stateful-access (which, it seems to me, laziness is an example use case of).

In any case, apologies if you thought my comment was unhelpful.

# peter miller (7 years ago)
class CaseLazy {
  get bar() {
    var value = Math.random();
    Object.defineProperty(this, 'bar', {value});
    return value;
  }
}

Doesn't this count as redefining the shape of the object? Or are the
compilers fine with it?

Peter

# Andrea Giammarchi (7 years ago)

Unless you have a faster way to do lazy property assignment, I think it's irrelevant if internally VMs are not too happy. VMs are there to solve our problems, not vice-versa ;-)

# Matthew Robb (7 years ago)

I think it's irrelevant if internally VMs are not too happy. VMs are

there to solve our problems, not vice-versa ;-) ​ This ^​ is very important for everyone to get on board with. Regardless the cost should be negligible as the shape is only changing at the point of delayed init. This will cause, for example V8, to deop the object and have to build a new hidden class but only the one time. I guess it would potentially be interesting to support an own property that when undefined would delegate up the proto chain.

  • Matthew Robb
# Steve Fink (7 years ago)

On 9/11/17 5:36 AM, Matthew Robb wrote:

I think it's irrelevant if internally VMs are not too happy. VMs are there to solve our problems, not vice-versa ;-) ​ This ^​ is very important for everyone to get on board with. Regardless the cost should be negligible as the shape is only changing at the point of delayed init. This will cause, for example V8, to deop the object and have to build a new hidden class but only the one time. I guess it would potentially be interesting to support an own property that when undefined would delegate up the proto chain.

(I don't know, but) I would expect it to be worse than this. The shape is changing at the point of delayed init, which means that if an engine is associating the possible set of shapes with the constructor (or some other form of allocation site + mandatory initialization), then that site will produce multiple shapes. All code using such objects, if they ever see both shapes, will have to handle them both. Even worse, if you have several of these delayed init properties and you end up lazily initializing them in different orders (which seems relatively easy to do), then the internal slot offsets will vary.

You don't need to bend over backwards to make things easy for the VMs, but you don't want to be mean to them either. :-)

Not to mention that the observable property iteration order will vary.

# Andrea Giammarchi (7 years ago)

Steve it's not solved in any other way. Even if you use a WeakMap with an object, you gonna lazy attach properties to that object.

I honestly would like to see alternatives, if any, 'cause so far there is a benchmark and it proves already lazy property assignment is around 4x faster.

So, it's easy to say "it's not the best approach" but apparently hard to prove that's the case?

Looking forward to see better alternatives.

# Naveen Chawla (7 years ago)
# Steve Fink (7 years ago)

My intent was only to respond to the performance analysis, specifically the implication that the only performance cost is in building the new hidden class. That is not the case; everything that touches those objects is affected as well.

Whether or not it's still the right way to accomplish what you're after, I wasn't venturing an opinion. I could probably come up with a benchmark showing that your WeakMap approach can be faster -- eg by only accessing the property once, but feeding the old and new versions of the object into code that executes many many many times (doing something that never looks at that property, but is now slightly slower because it isn't monomorphic). But I suspect that for practical usage, redefining the property is faster than a WeakMap.

If I were to look beyond for other solutions for your problem, then I'm just speculating. Can decorators populate multiple properties once the expensive work is done?

I really want to tell the VM what's going on. I guess if it knew that accessing a getter property would convert it into a value property, and that it was doing something that would access the getter, then it could know to use the outgoing shape instead of the incoming shape. If only it knew that the getter was pure... but that way lies madness.

Given that most code that would slow down would also trigger the lazy defineProperty(), it's really not going to be that much of an issue. Any access after the first will see a single shape.

meh. Just take the perf hit, with awareness that you may be triggering slight slowdowns in all users of that object. Or you might not. I doubt it'll be that big, since you'll probably just end up with an inline cache for both shapes and there won't be all that much to optimize based on knowing a single shape.

Oh, and I think I was wrong about property enumeration order. The properties already existed, so defineProperty shouldn't modify the order IIUC. (I am awful with language semantics.)

# Matthew Robb (7 years ago)

I think it would be nice to be able to define an own data property on an object that while defined delegated up the prototype chain. This would allow a getter in the proto to lazily assign to the own property without triggering the property setter. This is even more nice when combined with super as you could conceivably hit the setter which itself could assign to the own property of the current instance.

Is that all too complicated? It seems perfect if only a little complicated.

# Alex Kodat (7 years ago)

I think to the degree that almost anyone would care, the public and private key proposal would address most of these issues. The private keys would not be affected by object shape so would have rip-roaring good performance. You'd simply need to provide getter functions in the prototype that check for undefined (or whatever) in the private variable, if not undefined return value, otherwise calculate value, set private variable, and return the value. Unless you're trying to calculate pi to a million decimal places this should be pretty much good enough for anything.

And it gives you the encapsulation you get in many other programming languages such as Java and C++. Public JS APIs would end up looking a lot like these for many applications -- a bunch of private variables and public functions including getters and setters.

Basically, most of our code already looks like this but we use closures rather than private variable to accomplish this. And yes, there is a dark side to closures, specifically the challenge in implementing closed function that don't gobble up a lot of memory. In V8 if I construct an object with say 20 function properties that's 20 JSFunction objects V8 has to create, each one being on the order of 72 bytes per function. It's not too horrible if we don't have a lot of these and they have relatively long lives but problematic for lots of instantiation of objects with lots of functions. The private key proposal would address this issue.


Alex Kodat

From: es-discuss [mailto:es-discuss-bounces at mozilla.org] On Behalf Of Matthew Robb Sent: Tuesday, September 12, 2017 4:49 PM To: Steve Fink <sphink at gmail.com>

Cc: es-discuss at mozilla.org >> es-discuss <es-discuss at mozilla.org>

Subject: Re: Lazy evaluation

I think it would be nice to be able to define an own data property on an object that while defined delegated up the prototype chain. This would allow a getter in the proto to lazily assign to the own property without triggering the property setter. This is even more nice when combined with super as you could conceivably hit the setter which itself could assign to the own property of the current instance.

Is that all too complicated? It seems perfect if only a little complicated.

On Sep 12, 2017 5:39 PM, "Steve Fink" <mailto:sphink at gmail.com> wrote:

My intent was only to respond to the performance analysis, specifically the implication that the only performance cost is in building the new hidden class. That is not the case; everything that touches those objects is affected as well.

Whether or not it's still the right way to accomplish what you're after, I wasn't venturing an opinion. I could probably come up with a benchmark showing that your WeakMap approach can be faster -- eg by only accessing the property once, but feeding the old and new versions of the object into code that executes many many many times (doing something that never looks at that property, but is now slightly slower because it isn't monomorphic). But I suspect that for practical usage, redefining the property is faster than a WeakMap.

If I were to look beyond for other solutions for your problem, then I'm just speculating. Can decorators populate multiple properties once the expensive work is done?

I really want to tell the VM what's going on. I guess if it knew that accessing a getter property would convert it into a value property, and that it was doing something that would access the getter, then it could know to use the outgoing shape instead of the incoming shape. If only it knew that the getter was pure... but that way lies madness.

Given that most code that would slow down would also trigger the lazy defineProperty(), it's really not going to be that much of an issue. Any access after the first will see a single shape.

meh. Just take the perf hit, with awareness that you may be triggering slight slowdowns in all users of that object. Or you might not. I doubt it'll be that big, since you'll probably just end up with an inline cache for both shapes and there won't be all that much to optimize based on knowing a single shape.

Oh, and I think I was wrong about property enumeration order. The properties already existed, so defineProperty shouldn't modify the order IIUC. (I am awful with language semantics.)

On 9/11/17 2:48 PM, Andrea Giammarchi wrote: Steve it's not solved in any other way. Even if you use a WeakMap with an object, you gonna lazy attach properties to that object.

I honestly would like to see alternatives, if any, 'cause so far there is a benchmark and it proves already lazy property assignment is around 4x faster.

So, it's easy to say "it's not the best approach" but apparently hard to prove that's the case?

Looking forward to see better alternatives.

On Mon, Sep 11, 2017 at 8:15 PM, Steve Fink <mailto:sphink at gmail.com> wrote:

On 9/11/17 5:36 AM, Matthew Robb wrote:

I think it's irrelevant if internally VMs are not too happy. VMs are there to solve our problems, not vice-versa ;-) ​ This ^​ is very important for everyone to get on board with. Regardless the cost should be negligible as the shape is only changing at the point of delayed init. This will cause, for example V8, to deop the object and have to build a new hidden class but only the one time. I guess it would potentially be interesting to support an own property that when undefined would delegate up the proto chain.

(I don't know, but) I would expect it to be worse than this. The shape is changing at the point of delayed init, which means that if an engine is associating the possible set of shapes with the constructor (or some other form of allocation site + mandatory initialization), then that site will produce multiple shapes. All code using such objects, if they ever see both shapes, will have to handle them both. Even worse, if you have several of these delayed init properties and you end up lazily initializing them in different orders (which seems relatively easy to do), then the internal slot offsets will vary.

You don't need to bend over backwards to make things easy for the VMs, but you don't want to be mean to them either. :-)

Not to mention that the observable property iteration order will vary.

On Mon, Sep 11, 2017 at 7:09 AM, Andrea Giammarchi <mailto:andrea.giammarchi at gmail.com> wrote:

Hi Peter.

Unless you have a faster way to do lazy property assignment, I think it's irrelevant if internally VMs are not too happy. VMs are there to solve our problems, not vice-versa ;-)

On Mon, Sep 11, 2017 at 11:54 AM, peter miller <mailto:fuchsia.groan at virgin.net> wrote:

Hi Andrea,

class CaseLazy {
  get bar() {
    var value = Math.random();
    Object.defineProperty(this, 'bar', {value});
    return value;
  }
}

Doesn't this count as redefining the shape of the object? Or are the compilers fine with it?

# Andrea Giammarchi (7 years ago)

The properties already existed, so defineProperty shouldn't modify the

order IIUC

well, nope. the property existed in the prototype, not in the object.

anyway, I guess private properties, that are a possible solution, will be transpiled through a WeakMap so that most likely anything discussed in here won't make sense and the future code would look like the following

class A {
  #random;
  get random() {
    return this.#random ||
          (this.#random = Math.random());
  }
}


// transpiled
var A = function (wm) {
  function A() {
    wm.set(this, {random: void 0});
  }
  Object.defineProperties(
    A.prototype,
    {
      random: {
        configurable: true,
        get: function () {
          return wm.get(this).random ||
                (wm.get(this).random = Math.random());
        }
      }
    }
  );
  return A;
}(new WeakMap);
# Alex Kodat (7 years ago)

What do you mean by “will be transpiled through”? My understanding of the private property proposal is that private properties will be in fixed slots (inaccessible outside the class) in the object so there would be no WeakMap. Maybe you mean "will behave more or less as if (except more efficiently)"?


Alex Kodat

From: es-discuss [mailto:es-discuss-bounces at mozilla.org] On Behalf Of Andrea Giammarchi Sent: Wednesday, September 13, 2017 2:31 AM To: Steve Fink <sphink at gmail.com>

Cc: es-discuss at mozilla.org Subject: Re: Lazy evaluation

The properties already existed, so defineProperty shouldn't modify the order IIUC

well, nope. the property existed in the prototype, not in the object.

anyway, I guess private properties, that are a possible solution, will be transpiled through a WeakMap so that most likely anything discussed in here won't make sense and the future code would look like the following

class A {
  #random;
  get random() {
    return this.#random ||
          (this.#random = Math.random());
  }
}


// transpiled
var A = function (wm) {
  function A() {
    wm.set(this, {random: void 0});
  }
  Object.defineProperties(
    A.prototype,
    {
      random: {
        configurable: true,
        get: function () {
          return wm.get(this).random ||
                (wm.get(this).random = Math.random());
        }
      }
    }
  );
  return A;
}(new WeakMap);

On Tue, Sep 12, 2017 at 10:39 PM, Steve Fink <mailto:sphink at gmail.com> wrote:

My intent was only to respond to the performance analysis, specifically the implication that the only performance cost is in building the new hidden class. That is not the case; everything that touches those objects is affected as well.

Whether or not it's still the right way to accomplish what you're after, I wasn't venturing an opinion. I could probably come up with a benchmark showing that your WeakMap approach can be faster -- eg by only accessing the property once, but feeding the old and new versions of the object into code that executes many many many times (doing something that never looks at that property, but is now slightly slower because it isn't monomorphic). But I suspect that for practical usage, redefining the property is faster than a WeakMap.

If I were to look beyond for other solutions for your problem, then I'm just speculating. Can decorators populate multiple properties once the expensive work is done?

I really want to tell the VM what's going on. I guess if it knew that accessing a getter property would convert it into a value property, and that it was doing something that would access the getter, then it could know to use the outgoing shape instead of the incoming shape. If only it knew that the getter was pure... but that way lies madness.

Given that most code that would slow down would also trigger the lazy defineProperty(), it's really not going to be that much of an issue. Any access after the first will see a single shape.

meh. Just take the perf hit, with awareness that you may be triggering slight slowdowns in all users of that object. Or you might not. I doubt it'll be that big, since you'll probably just end up with an inline cache for both shapes and there won't be all that much to optimize based on knowing a single shape.

Oh, and I think I was wrong about property enumeration order. The properties already existed, so defineProperty shouldn't modify the order IIUC. (I am awful with language semantics.)

On 9/11/17 2:48 PM, Andrea Giammarchi wrote: Steve it's not solved in any other way. Even if you use a WeakMap with an object, you gonna lazy attach properties to that object.

I honestly would like to see alternatives, if any, 'cause so far there is a benchmark and it proves already lazy property assignment is around 4x faster.

So, it's easy to say "it's not the best approach" but apparently hard to prove that's the case?

Looking forward to see better alternatives.

On Mon, Sep 11, 2017 at 8:15 PM, Steve Fink <mailto:sphink at gmail.com> wrote:

On 9/11/17 5:36 AM, Matthew Robb wrote:

I think it's irrelevant if internally VMs are not too happy. VMs are there to solve our problems, not vice-versa ;-) ​ This ^​ is very important for everyone to get on board with. Regardless the cost should be negligible as the shape is only changing at the point of delayed init. This will cause, for example V8, to deop the object and have to build a new hidden class but only the one time. I guess it would potentially be interesting to support an own property that when undefined would delegate up the proto chain.

(I don't know, but) I would expect it to be worse than this. The shape is changing at the point of delayed init, which means that if an engine is associating the possible set of shapes with the constructor (or some other form of allocation site + mandatory initialization), then that site will produce multiple shapes. All code using such objects, if they ever see both shapes, will have to handle them both. Even worse, if you have several of these delayed init properties and you end up lazily initializing them in different orders (which seems relatively easy to do), then the internal slot offsets will vary.

You don't need to bend over backwards to make things easy for the VMs, but you don't want to be mean to them either. :-)

Not to mention that the observable property iteration order will vary.

On Mon, Sep 11, 2017 at 7:09 AM, Andrea Giammarchi <mailto:andrea.giammarchi at gmail.com> wrote:

Hi Peter.

Unless you have a faster way to do lazy property assignment, I think it's irrelevant if internally VMs are not too happy. VMs are there to solve our problems, not vice-versa ;-)

On Mon, Sep 11, 2017 at 11:54 AM, peter miller <mailto:fuchsia.groan at virgin.net> wrote:

Hi Andrea,

class CaseLazy {
  get bar() {
    var value = Math.random();
    Object.defineProperty(this, 'bar', {value});
    return value;
  }
}

Doesn't this count as redefining the shape of the object? Or are the compilers fine with it?

# Alex Kodat (7 years ago)

Also, FWIW, since we’re talking about nanoseconds of performance, here, I think slightly better than:

return this.#random || (this.#random = Math.random());

Is

return (this.#random === undefined) ? this.#random = Math.random() : this.#random;

This is because in the former, the compiled code has to determine whether this.#random is coercible into a Boolean. At the very least, it has to do an "Is this an object and, if so, not undefined ==> true" test. From first principles, the ternary expression is going to be more efficient. It simply checks if the value at this.#random is the same as the global undefined value and, if not, returns that value. No extra tests necessary.

And at least with V8 theory == practice in this case (at least in all my tests). And yes, we're talking nanosecond here.

While this looks a bit like the null propagation operator would be applicable, it's really not.

Also FWIW, if you get tired of seeing/typing undefined, we add _ to the global object which means the above can be written as:

return (this.#random === _) ? this.#random = Math.random() : this.#random;

You might think that this would perform worse than the undefined but, in fact, for V8 it's identical -- undefined and _ are both just properties of the global object. I think this reads especially well in function calls where you want an undefined placeholder

fooey(_, "whatever");

Visually, _ just says undefined to me.


Alex Kodat

From: es-discuss [mailto:es-discuss-bounces at mozilla.org] On Behalf Of Andrea Giammarchi Sent: Wednesday, September 13, 2017 2:31 AM To: Steve Fink <sphink at gmail.com>

Cc: es-discuss at mozilla.org Subject: Re: Lazy evaluation

The properties already existed, so defineProperty shouldn't modify the order IIUC

well, nope. the property existed in the prototype, not in the object.

anyway, I guess private properties, that are a possible solution, will be transpiled through a WeakMap so that most likely anything discussed in here won't make sense and the future code would look like the following

class A {
  #random;
  get random() {
    return this.#random ||
          (this.#random = Math.random());
  }
}


// transpiled
var A = function (wm) {
  function A() {
    wm.set(this, {random: void 0});
  }
  Object.defineProperties(
    A.prototype,
    {
      random: {
        configurable: true,
        get: function () {
          return wm.get(this).random ||
                (wm.get(this).random = Math.random());
        }
      }
    }
  );
  return A;
}(new WeakMap);
# Isiah Meadows (7 years ago)

Nit: _ is a very valid identifier. (Consider Underscore/Lodash)

# Andrea Giammarchi (7 years ago)

Maybe you mean "will behave more or less as if (except more efficiently)"?

no, I meant: it will transpiled into something using private WeakMaps.

I don't have any interest in talk nanoseconds for something unrelated to the topic though.

Best

# kai zhu (7 years ago)

inline

class A {
  #random;
  get random() {
    return this.#random ||
          (this.#random = Math.random());
  }
}


// transpiled
var A = function (wm) {
  function A() {
    wm.set(this, {random: void 0});
  }
  Object.defineProperties(
    A.prototype,
    {
      random: {
        configurable: true,
        get: function () {
          return wm.get(this).random ||
                (wm.get(this).random = Math.random());
        }
      }
    }
  );
  return A;
}(new WeakMap);
  • overloaded getters/setters and Object.defineProperties are generally over-engineered anti-patterns in javascript-programming

    • please use normal method-calls instead, which are more maintainable
  • WeakMap is an over-engineered anti-pattern in javascript-programming

    • they are inferior to using a plain-object cache-dictionary in pretty much every use-case.
    • WeakMap's implicit-garbage-collecting feature is error-prone, hard to validate with tests, and generally becomes unmaintainable over time.
/*jslint node: true */
'use strict';

function LazyClass() {
    this.cacheDict = {};
}
LazyClass.prototype.getRandomCached = function () {
    this.cacheDict.random = this.cacheDict.random || Math.random();
    return this.cacheDict.random;
};
LazyClass.prototype.clearCache = function () {
    this.cacheDict = {};
};

var lazyObject = new LazyClass();
console.log(lazyObject.getRandomCached());
console.log(lazyObject.getRandomCached());
lazyObject.clearCache();
console.log(lazyObject.getRandomCached());
# Andrea Giammarchi (7 years ago)

please use normal method-calls instead, which are more maintainable

no, thanks.

WeakMap's implicit-garbage-collecting feature is error-prone, hard to

validate with tests, and generally becomes unmaintainable over time.

no, thanks.

We were discussing patterns here, nothing to "please don't" and IMO, dogma oriented rules are over-engineered anti-pattern too.

Let's not make this ML an awkward place for who wants to just learn or explore JS language features, thanks.

# Alex Kodat (7 years ago)

I realize in going over the last few e-mails for this thread, that as usual, I’m to blame for a bit of poor communication. I had intended to include the link to the public-private field proposal in my comments about it but whiffed. So the link: tc39.github.io/proposal-class-fields. And yes, this link has appeared on other e-mails (sorry?).

This brings me to a (noobie) meta-question. This proposal is very interesting and looks nice to me and it is indicated to be in Stage 3. This suggests that it's pretty close to going in as-is into the ECMAScript standard? In case I'm not the last person in the world to find the process: tc39.github.io/process-document.

So is this the appropriate forum for questions or comments about this spec? I notice many of the very interesting discussions leading up to this proposal happened elsewhere (mostly GitHub). But there's no link in the spec proposal to a discussion page so maybe this is it? Sorry if this is a stupid question.


Alex Kodat

# kai zhu (7 years ago)

you can call it dogma. i call it the the javascript-way of solving frontend-engineering problems, which is simpler and more practical for laymen than what i learned from cs-theory in school using c++/java.

its time we showed some pride in javascript (like the haskell/lisp/etc folks), and admit some of our 20+ year-old “hacks” are actually good design-patterns that rival and challenge traditional cs-teachings, instead of replacing them with bad-practices from other languages (cough operator-overloading cough).

# Andrea Giammarchi (7 years ago)

there's nothing bad in using accessors (getters/setters) but you can do what you want, just don't tell everyone what you do is the only way 'cause it's not.

I'm off this thread, there's probably nothing else to add.

Best