Idea: Reflect.hasExternalReference

# Andrea Giammarchi (3 months ago)

I am not sure how feasible this concept is, and I'll start just with the pattern it's useful for.

  one: 'or more',
  runtime: 'definition'

In JS user-land, the amount of unnecessary duplicated objects is embarrassing.

We all know that immutability is great and we all know that functions should never mutate received arguments, but there is absolutely zero side effect in modifying, changing, erasing, you name it, an object passed like the one in that .setup({...})

As of today, basically every method used to setup one-off objects will use one of the following techniques:

  • a shallow copy via Object.create(Object.getPrototypeOf(options), Object.getOwnPropertyDescriptors(options))
  • an enumerable only copy via Object.assign({}, options), probably more common but yet, most of the time, practically unnecesary

Things become even more difficult to improve when it comes to simply chaining objects together, where it's not possible to simply define the prototype of the received object, throwing away a performance boost possibility like the one Netfilx recently had [1]

Shortcutting and destructuring also makes the usage of one-off literals great, and states are the most basic, yet common, example.


Back to the Reflect.hasExternalReference idea

Within the hypothetical .setState(...) method, invoking Reflect.hasExternalReference would return true it the object was passed as literal, without any possible external reference.

class Component {
  setState(state) {
    this.state = Reflect.hasExternalReference(state) ?
      Object.assign({}, this.state, state) :
      Object.setPrototypeOf(state, this.state);
    // update only differences

If there was a way to know passed literals, I am sure a lot of software could automatically boost performance:

  • less polluted GC
  • more recycled one-off objects
  • less need to copy/clone over and over one-off literals that couldn't be mutate anywhere else anyway

What do you think? Does any of this make sense?



# Alexander Jones (3 months ago)

IMO we should explore generalising syntax for map literals and object destructuring, bringing something like Immutable.Map into the spec, and thus bringing JS into a new modern era.

Otherwise people who care about this stuff will just continue to move to other languages like ClojureScript in order to get the expressivity and safety they want!

# Andrea Giammarchi (3 months ago)

I am not following. The object is immutable from their side, because they'll never be able to even access it since it's not referenced.

New syntax wouldn't solve anything I've talked about.

Have I misunderstood you?

# T.J. Crowder (3 months ago)

I think the point is that immutability syntax would apply to the use case you've described, because if the object passed is immutable, it doesn't matter whether the sender has a reference to it or not. The receiver can use it without caring, and without making a defensive copy, since it's immutable.

-- T.J.

# Andrea Giammarchi (3 months ago)

That means the receiver has to check if the object is immutable but there's no way to setup the prottypal inheritance at that point, right?

That is the main point I've made, and for Netflix was a merge boost from 500ms to 60ms.

They simlpy do Object.setPrototypeOf(newState, currentState) internally, but not a single library author that knows upfront if such object can be manipulated as such because there's no way to tell if it was unreferenced (aka: 0 side effects)

If immutable can have mutated its __proto__ then I'm OK with new syntax as long as Reflect.isMutable is available since a method, internally, wouldn't know which syntax has been used to send that argument.

# T.J. Crowder (3 months ago)

I'm assuming that Alexander Jones means that you'd document you require an immutable, and thus not need to check, and just happily use it. (Ideally, syntax would enforce it.) But it's for him to say, don't want to put words in anyone's mouths.

But if you need to merge options (as in your setup example), I'm not sure immutability helps, as it wouldn't prevent creating a new object (presumably you can't setPrototypeOf an existing immutable object) unless you changed the option lookup operation to be a function that first checks the immutable and then the defaults, trading lookup time for memory churn savings...

FWIW, rather than having a check for outstanding references, perhaps something akin to the transfer concept used by web workers, where the receiver explicitly takes ownership of the object, and the sender's reference becomes invalid... Then you could just fill in missing options with defaults and not have to use setPrototypeOf (or you could if you preferred)... Hmmm....

-- T.J.

# Alexander Jones (3 months ago)

Some sort of formal way for a method to accept an immutable map, and perhaps silently convert from a mutable object if passed one.

Using a prototype chain to get 'copy on write' style sharing is really a poor man's equivalent to a HAMT IMO.

Honestly, not sure how we get from here to there, but exposing reference count in any way seems dubious to me!


# Andrea Giammarchi (3 months ago)

I'll try to simplify the pattern I'm talking about.

prevState = {a: 'a', b: 'b', c: 'c'};
newState = {a: 'A'};

// quick merge
newState.__proto__ = prevState;

// all keys reachable, a "zero" effort merge
// but what changed between states ?

// how to retrieve all state keys, if needed ?
var keys = [];
for (var key in newState) keys.push(key);

// how to flatten state, let's say before storing through JSON ?
keys.forEach(key => { newState[key] = newState[key]; });

If you own all objects lifecycle this pattern is straight forward, but unfortunately if you don't know if the object can be abused, you end up still copying the new state properties over via Object.assign, as I've explained in details on a last month post of mine [1]

That copies "only" new, changed, properties, instead of flattening all of them like Netflix was doing before via Object.asign({}, prevState, newState) but it's clear to me there are many use cases when knowing an argument is unreferenced would benefit for everyone, including the engine.

Hope I've explained better ... and no, immutability wouldn't help much in here.