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