Destructuring and evaluation order

# Andreas Rossberg (11 years ago)

The way destructuring assignment currently is specified leads to rather inconsistent evaluation order. Consider:

  let o = {}
  function f() { print(1); return o }
  function g() { print(2); return 5 }

  f().x = g()    // 1, 2
  {a: f().x} = {a: g(}}   // 2, 1

That is, destructuring assignments violate the left-to-right evaluation that is otherwise maintained.

A similar problem is created by computed property names, and this one also applies to destructuring binds:

  function h() { print(1); return "a" }
  function k() { print(2); return "a" }

  let {[h()]: x} = {[k()]: 5}  // 2, 1

I'm wondering if this is the behaviour we want. Of course, it is easier to specify that way (and marginally easier to implement), but I'm not sure if it is good to violate left-to-right evaluation like that. In particular, the discrepancy between the two assignments in the first example is rather irritating, as it violates the spirit of a regular and composable pattern language.

(Note that either way, left-to-right evaluation order of course can only hold up to default initializers, but that arguably is natural.)

Thoughts?

# Oliver Hunt (11 years ago)

I don’t believe that there is any reason to break from left-to-right evaluation order. Even if it was uncommon it’s still an unnecessary hazard.

Spec bug maybe?

# Allen Wirfs-Brock (11 years ago)

Nope, it's always been designed this, going back to the original wiki strawman harmony:destructuring#semantics and I assume the original FF implementation.

It has also been something that we has been discussed here and in TC39 multiple times. If we wan to revisit this decision we really need to dredge up all of those previous discussions to see if there is new data that would cause us to change this decision.

Basically for destructing assignment, each property element can be an arbitrary assignment expression and all of their Reference values would have to be saved to get left to right order.

# Oliver Hunt (11 years ago)

Yup. Honestly I don’t think destructuring to an arbitrary expression would be that common so I think it’s worth it to define order of operations as being left to right and leave it to the engines to optimise things sanely.

Burning some stack space to hold the intermediate slots doesn’t seem that bad, although that said I’ve forgotten what the semantics for

o = {__proto__: {set foo() {print(“bar”)}}
o.foo = (o.__defineSetter__(“foo”, function(){ print(“foo”)})), 5

Is expected to be (I could see what we actually do but that’s no guarantee that it’s the right thing).

# Allen Wirfs-Brock (11 years ago)

On Apr 25, 2014, at 11:45 AM, Oliver Hunt wrote:

o = {__proto__: {set foo() {print(“bar”)}}
o.foo = (o.__defineSetter__(“foo”, function(){ print(“foo”)})), 5
  1. o.f evaluates to a Reference whose base is the value of o and whose referenced name is "foo". No lookup occurs at this point.
  2. o.__defineSetter__ creates an accessor own property on o
  3. a PutValue is done using the reference from step 1. It does a property lookup and finds/uses the setter defined in step 2
  4. print("foo") happens
# David Herman (11 years ago)

FWIW (not much probably) I've always agreed with what Andreas and Ollie are saying here, but I don't have the time or energy to do the dredging. :)