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.
When you assign to an object property, e.g., "o.x = 42", the l-value here
is the "x" property of
the "o" object. We need to capture both.
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;
In C/C++, you know that the something.prop on the right-hand side of an
assignment
means something slightly different from the one on the left-hand side.
We have the same thing in ECMAScript:
var x = something.prop;
x = 10;
is not the same as
something.prop = 10;
So far, References as a specification mechanism is just following other
languages,
and behaving exactly as any seasoned programmer would expect. Try checking
your questions
against the expected behavior if a Reference is just an l-expression.
Where it differs is function/method calls. The traditional languages that
I compared
to does not have functions as first-class values. If a function sits on an
object,
you can't extract it and call it without such an object.
Well, there is C++ method pointers which stay bound to the object they
were extracted from
(but don't try to guess the size of one, they are probably bigger than
whatever you
might think is necessary for that). And they are different from pointers
to static
functions.
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
(try doing that in Java!). It's a shallow abstraction, but it does work
when you play along
with it.
A Reference is purely a specification-tool that desn't have to exist in
any form inside
an actual implementation. If we start exposing it, we would require
implementations to take
steps they might not need in order to visibly create and pass around such
a reference.
If you really need user-level references, you can create them yourself,
and just do
var ref = new Reference(object, "prop");
var val = ref.GetValue();
ref.SetValue(val + 10);
ref.SetValue(someFunction);
ref.call(arg1, arg2);
/L
On Mon, 11 Apr 2011 10:55:49 +0200, Claus Reinke <claus.rei...@talk21.com>
wrote:
Like most Javascript programmers, I have tended to follow a simple rule
for functions using 'this': eta-expand method selections, use .bind, or
get into trouble.
Then I got curious about how method calls determine what
object to pass as 'this': a method is a function selected from an
object, and functions are first-class values, so, by the time they get
called, how do we know where they came from?
So I looked into the spec, and things deteriorated from there.
I would be interested in the rationale for the current specification of
PropertyReferences, as it seems to invalidate a large class of
program equivalences (see below for examples).
Status:
According to the Ecmascript spec (11.2.1), property accessors return not
the selected value but a Reference (8.7), which is a combination of the
object selected from and the name of
the property being selected (the property value is not stored
immediately, but selected later when calling GetValue on such a
PropertyReference).
Function calls (11.2.3) then construct 'this' from the object
in a PropertyReference, and the whole Reference concept,
as far as its use for 'this' is concerned, seems finely tuned to mimic a
piece of syntax (conserve the base object info
just long enough to use it for 'this'), rather than a semantic value
(trying to pass around PropertyReferences is likely
to end up calling GetValue, losing the reference info).
Question 1: Should a Reference hold on to the current property value?
Currently, there seems to be no way to store a property
Reference without GetValue getting called, so there is no window
for changing the property value such a Reference
refers to behind its back. That would no longer be true if
References could be passed around, as language values.
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.
What if it's getter property?
What if it's a setter property with not getter?
If you make a reference a first-class value, then you probably don't want
to make too many assumptions about how it's used. Don't read a value from
it
unless the user wants to do so.
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?
obj.longish() // correct 'this'
var short = obj.longish; // try to define a shorthand
short(); // oops, wrong 'this'
This seems to be a very popular mistake - most beginners
seem to get burned once. Naively, one thinks of selection
losing the information, but that does not seem to be the
case. So 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?
Ofcourse 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.
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.
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.
E.g.
[obj1.foo, obj2.foo][(Math.random() * 2) | 0]();
That might allow to limit the equivalence breakage with
respect to determining 'this'.
Currently, searching for 'Reference' in the spec gives an uneven
picture. For instance, the spec claims that The Reference
Specification Type is used to explain the behaviour of such
operators as delete, typeof, and the assignment operators
However, References are also used to determine the value
of 'this' for function calls, and most expressions/operations/
variable bindings/function calls cannot pass through References,
returning only their values instead. This information is spread over too
many pages - it could be summarized in the section on References.
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.
Broken equivalences:
It is not too surprising that eta-conversion does not hold
obj.method <-/-> function() { return obj.method(); }
although usually, the problems are with termination, side-effects, or
type errors, while in this case, the problem
seems to be context-sensitive: many contexts treat References
differently. That can be confusing.
Currently, quite a few code transformations are not valid
if the code involves References and might be used in the
context of a function call (MemberExpression/CallExpression).
Here are some examples:
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.
(function(){ return obj.m; }()) <-/-> obj.m
(x = obj.m) <-/-> obj.m // where x is unused
[obj.m][0] <-/-> obj.m // obvious in hindsight?
// but really surprising the first time
{tmp: obj.m}.tmp <-/-> obj.m
// this explains the one above
(0,obj.m) <-/-> obj.m
(true && obj.m) <-/-> obj.m
(false || obj.m) <-/-> obj.m
(true ? obj.m : obj.m) <-/-> obj.m // Firefox 3.6.11
wrongly optimizes this one
I find the result very confusing: not only will a method
lose its Reference just by passing it around, but it will
pick up a new Reference by passing it through any kind of Object. This
picking-up-new-Reference is probably
needed for mixins (copying methods from one object
to another), but it means that storing naked method References in Arrays
is not recommended.
This is exactly correct. Extracting a method from its object will break the
connection to the object.
Which is kindof expected when you allow any function to be used as both a
method and a non-method.
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.
/L
--
Lasse Reichstein - reichsteinatw...@gmail.com
_______________________________________________
es-discuss mailing list
es-discuss@mozilla.org
https://mail.mozilla.org/listinfo/es-discuss