The behavior of References isn't as arbitrary or different from other languages as it might seem. It's really a way to specify l-values.

Not arbitrary, but different, and quite drastically so (as far as
usage is concerned). Your remarks helped me to pin down the difference (and eliminated two of my questions, thanks!-): References are l-values, but they cannot be used as such, due to forced, implicit conversion curtailing their lifetimes.

The differences between References and general l-values (_values_ that represent locations where other values may be
stored) lies in how they may be used and how long they live:

- l-values are first-class values: they can be passed around, assigned to variables, stored in data structures; they happen to support a de-referencing operation, but merely evaluating an l-value does not de-reference it; l-values can be de-referenced explicitly; some languages implicitly coerce l-values into r-values (causing de-reference) depending on usage context (this is where the names come from: values on the left and right hand sides of assignments), but even those languages tend to provide means to control when coercions take place

- References start out as l-values, but don't live long enough
to be used as such. They cannot be passed around, stored in data structures, or assigned to variables; any attempt to evaluate them leads to immediate de-reference, no matter whether the usage context expects an l-value or not; there
   is no way to prevent the implicit de-reference

It is mostly the implicit coercion in evaluation, combined with
the early evaluation inherent in Javascript's call-by-value semantics, that breaks those equivalences.

In other languages, e.g., C or Java, you have the same problem:
  int x = something.prop;
  x = 10;
is not the same as
  something.prop = 10;

My C has been buried for too long, but were not l-values
one of C's showcases? Something like this

   int *x = &(something.prop);
   *x = 10;

should work, by making x hold l-values (in case I messed up the syntax beyond recognition: x should be a pointer to int,
its value being the address of something.prop, so we can use
the r-value of x as an l-value in the second assignment).

C allowed us to be explicit about whether we wanted l-values
or r-values, overriding the default conversions when necessary.
Some later languages, such as Haskell or Standard ML, dropped
the implicit coercions entirely, so all de-references are explicit.

ECMAScript relies on implicit de-reference, but triggers that by
every evaluation. So we don't have explicit de-reference, we do
not have C's flexibility for explicit de-reference control, and we
do not even have C's context-sensitive implicit de-reference.

Which means that things like

 (1 ? obj.prop : obj.prop) = 3;
 (0, obj.prop) = 2;

will work in C, but fail in ES (References are very short-lived
l-values - every operation evaluates and de-references them,
independent of whether the result is going to be used in an
l-value or r-value context).
Also, in C we can write

 x = &(obj.prop);
 *x = 4;

to express that we want x to hold l-values, and storing l-values in arrays isn't much different

 int *a[1] = { &obj.prop };
 *(a[0]) = 1;

In ES, we only have the default-to-r-value path. For instance,

[obj.prop][0] = 1; will not use obj.prop as an l-value, and there does not seem to be a straightforward alternative for programmers who want to work with ES References as l-values.

So far, References as a specification mechanism is just following other languages, and behaving exactly as any seasoned programmer would expect.

Does the above explain why a seasoned programmer
might reasonably expect differently, because ECMAScript behaves differently from other languages?

The binding of "this" when calling a Reference value mimics method calls. It does so fine when you treat objects as objects, but not when you try to extract a method from its object .. It's a shallow abstraction, but it does work when you play along with it.

I was trying to point out that the mechanism works for the
simple case and is known to confuse programmers for other cases. In particular, I am trying to find out whether the current mechanism is a special case of a more complete mechanism,
one that works equally well for simple and non-simple cases.

Since PropertyReferences hold the object the method was
selected from, all that seems needed is to make References
survive evalutation, ie, make References first-class values.

An alternative would be to preserve context-information
during evaluation: if the result of '(?:)' is to be used as an
l-value, then evaluation should perhaps not de-reference
the l-values in the conditional.

I am less concerned with being able to use '(?:)' on the
left hand side of assignments, and more with being able
to use equivalences like '(true ? x : x) <--> x', independent
of where the expression occurs. Since we can write such
conditionals on left hand sides, why not make sure that
they actually work there?

Question 1: Should a Reference hold on to the current property value?

Which value? The one the property (if it existed) had when the reference was created? Or the current one - if the Reference survives for any amount of time, the object property could change its value in the meantime.

Thanks. So the answer probably is 'no' - which value we get depends on when we look behind the reference. I guess I was confused by property accessors not actually accessing the property - once the decision is made to return a Reference instead of the property value, it would only be
consequent to keep Reference construction and de-reference
separated. As long as we are able to specify when to pass the reference and when to look up the value behind it.

Question 2:
    If a Reference allows us to recover 'obj' from 'obj.method',
why does this information have to get lost when passing it through a variable binding? .. could this error source be eliminated by passing the Reference as a value, instead of only the value component
    without the base object, one step further?

Of course it's possible, but personally I prefer to have the "this" object obvious in the call line. That way I know what object the method is being called on. Without it, the loss of context is in the source code, making it harder to read and maintain.

I'm afraid you won't get that comfort;-) At the moment, programmers can just write the more complicated

   var short = function(x) { return obj.method(x); };
   short("hi"); // no 'this' object on the call line

We can try to make this more readable, and we can try to
eliminate a common source of bugs, but the rest is between
you and your team's coding style and style checker.

Question 3:
    It seems that trying to reuse References for 'this' forces
    early calls to GetValue (because users should not have to
call 'obj.method.valueOf()' or 'obj.property.valueOf()' to trigger the delayed selection, and because the property
    value is not stored in the PropertyReference).

I'm not sure I understand what the problem is here.

Probably because the description is a bit confusing. I was
trying to understand why References get eliminated early,
through calls to GetValue, and was enumerating non-reasons
before coming to my question:

This loses information that we would like to hold on to - if we really cannot solve this for References in general,
why not store the 'this'-candidate in the function instead
(similar to the fairly new '[[boundThis]]')?

Won't work. The same function can be used in many places at the same time.

Ah, good point. If functions were constants, we could make copies (sharing the code, but with different this values). But
they aren't, so we need the 'this'-candidates outside the function.

E.g.
 [obj1.foo, obj2.foo][(Math.random() * 2) | 0]();

Note that, currently, either selection will have that anonymous first array as 'this', not obj1 or obj2. But
you were aware of that, right?-)

Question 4: (general version of question 2)
    Why is the origin information in References lost so easily?

    It seems that most parts of the spec require GetValue() by
    default, with few exceptions. What would go wrong if the
    available information would be passed on instead (isn't
    it sufficient for the final consumers to call GetValue(),
    provided that the original property value is stored in the
    PropertyReference, to avoid interference)?

References is a specification tool. If it survived for an extended amount of time, and visibly so, implementations would have to actually implement something to represent it. As it is now, a reference is found and immediately consumed, which allows implementations to never create it at all, and work directly on the value in r-value contexts, and on the object and property in l-value contexts.

Yes, and that is my argument. L-values as first-class values is the common way to handle references, whether it is in C, in Haskell, in ML, .., ever since Strachey documented l-values in 1967, and probably longer than that. Once references become visible to programmers, one might as well support them fully. Eliminating temporary structures is a common implementation optimization, not limited to References, and not a language spec concern.

Broken equivalences:
..
    var x = obj.m; x(); <-/-> obj.m();

    obj.m.valueOf() <-/-> obj.m

Why should that work? The valueOf function isn't guaranteed to return anything related to the object it's on.

The default valueOf for Function comes from Object,
where it is 'ToObject this', which for Object is the input
argument without conversion. I think..

This is exactly correct. Extracting a method from its object will break the connection to the object. Which is kind of expected when you allow any function to be used as both a method and a non-method.

I expected none of these:

- Property access is not extraction.
- Extraction is triggered by constructs that could just
   as well pass on the property accessor (and probably
   should, as the alternative leads to runtime errors).
- Extraction alone will not break the connection, only
   some forms of triggering extractions will do so.
- A new connection is established by trying to pass
   a property accessor through an Array.

I was not aware that just about any code transformation
would be invalidated by the handling of References. If this cannot be fixed, could the specification be more explicit about this, please?

When I think of References as l-values, the current behavior actually become the expected one. The only tricky bit is that method-calls actually need an l-value to work correctly.

Even the l-value part is unusual, as I've tried to show.

Claus


_______________________________________________
es-discuss mailing list
es-discuss@mozilla.org
https://mail.mozilla.org/listinfo/es-discuss

Reply via email to