Argument unpacking?
No plans. Function.prototype.apply is still there, albeit not as
powerful or convenient.
I'm still hoping for something in ES4 that makes apply universal (it
doesn't work on alert in IE; this should not be allowed by the spec
by leaving it up to "host objects" to be broken if they ought to
delegate to Function.prototype). It would also be helpful for
bootstrapping if new and apply could be composed; this came up when I
wrote Narcissus. Suggestions?
I should mention that destructuring formal parameters allow the
function to take an array actual parameter and bind its elements to
named formal parameters:
function f(a, [b, c]) { print(a, b, c); } f(1, [2, 3]) // prints "1 2 3"
This is different yet again. I wanted to point it out in case it was
missed (it wasn't specified in the first wiki dump, IIRC). It's
implemented in JS1.7 in Firefox 2.
On 2006-11-09, at 16:29 EST, Brendan Eich wrote:
It would also be helpful for bootstrapping if new and apply could
be composed; this came up when I wrote Narcissus. Suggestions?
Make new a class method rather than a keyword? (I know, too
controversial. But that is the Lisp answer.)
The thing I most miss about apply is that I cannot have 'spread args':
foo.apply(bar, bletch, crud, rest) === foo.apply(bar, [bletch,
crud].concat(rest))
On 11/9/06, P T Withington <ptw at pobox.com> wrote:
On 2006-11-09, at 16:29 EST, Brendan Eich wrote:
It would also be helpful for bootstrapping if new and apply could be composed; this came up when I wrote Narcissus. Suggestions?
Make new a class method rather than a keyword? (I know, too controversial. But that is the Lisp answer.)
Well, some workaround do exists within the current language
groups.google.com/group/netscape.public.mozilla.jseng/msg/48395a68e716c9d0
they just won't scale to some host objects, (and, yes, they incur some memory overhead, even though a constant one). So while I like the lisp way, it will unfortunately not accomodate those alien objects.
The thing I most miss about apply is that I cannot have 'spread args':
foo.apply(bar, bletch, crud, rest) === foo.apply(bar, [bletch, crud].concat(rest))
Is
foo.call(bar, bletch, crud, rest)
an answer, or did I miss the point ?
On Nov 9, 2006, at 2:19 PM, P T Withington wrote:
On 2006-11-09, at 16:29 EST, Brendan Eich wrote:
It would also be helpful for bootstrapping if new and apply could
be composed; this came up when I wrote Narcissus. Suggestions?Make new a class method rather than a keyword? (I know, too
controversial. But that is the Lisp answer.)
We could expose intrinsic::construct as the meta-object protocol hook
underlying new, and allow you to write, e.g.:
class Complex! {
static intrinsic function construct(real:double,
imag:double):Complex {...}
. . .
}
Then you could always invoke 'Complex.intrinsic::construct.apply
(Complex, args)' where args is [real, imag]. Or something simpler to
write (static intrinsic function my summer vacation was very
nice ;-), but this example is in keeping with the proposed call hook
(intranet.mozilla.org/ECMA/wiki/doku.php?
id=proposals:builtin_classes). Is this what you mean?
On Nov 9, 2006, at 2:47 PM, Brendan Eich wrote:
We could expose intrinsic::construct as the meta-object protocol
hook underlying new, and allow you to write, e.g.:class Complex! { static intrinsic function construct(real:double,
imag:double):Complex {...} . . . }Then you could always invoke 'Complex.intrinsic::construct.apply (Complex, args)' where args is [real, imag]. Or something simpler
to write (static intrinsic function my summer vacation was very
nice ;-), but this example is in keeping with the proposed call
hook (intranet.mozilla.org/ECMA/wiki/doku.php? id=proposals:builtin_classes).
I see in intranet.mozilla.org/ECMA/wiki/doku.php?
id=proposals:builtin_classes (the Object class at least) a 'static
function new Object() {...}'. This is, I think, obsolete syntax --
we do not want to require the class name to be restated after 'new'
or 'call'. It may be that we agreed to use 'construct', not 'new',
as the MOP hook name, since 'new' is reserved and we don't want to
outlaw direct calls of the static method.
We'll get all this sorted out at next week's TG1 meeting, but the
upshot seems to be that a static (intrinsic, I think) method for
constructing instances of a class will be provided by the runtime,
and it can be overridden to give custom constructor behavior. Anyone
see a problem with this approach?
On Nov 9, 2006, at 2:54 PM, Brendan Eich wrote:
On Nov 9, 2006, at 2:47 PM, Brendan Eich wrote:
We could expose intrinsic::construct as the meta-object protocol
hook underlying new, and allow you to write, e.g.:class Complex! { static intrinsic function construct(real:double,
imag:double):Complex {...} . . . }Then you could always invoke 'Complex.intrinsic::construct.apply (Complex, args)' where args is [real, imag]. Or something simpler
to write (static intrinsic function my summer vacation was very
nice ;-), but this example is in keeping with the proposed call
hook (intranet.mozilla.org/ECMA/wiki/doku.php? id=proposals:builtin_classes).I see in intranet.mozilla.org/ECMA/wiki/doku.php? id=proposals:builtin_classes (the Object class at least) a 'static
function new
Sorry for the private links. See developer.mozilla.org/es4 proposals/builtin_classes of course.
Object() {...}'. This is, I think, obsolete syntax -- we do not
want to require the class name to be restated after 'new' or
'call'. It may be that we agreed to use 'construct', not 'new', as
the MOP hook name, since 'new' is reserved and we don't want to
outlaw direct calls of the static method.
"... we don't want to outlaw explicit calls [and apply's]", I should
have written. The whole point is that you can invoke
C.intrinsic::construct directly or indirectly via .apply/.call, of
course (direct/indirect != explicit/implicit). The implicit call to
intrinsic::construct via operator new could call any old hook-name.
BTW, we use the intrinsic namespace to avoid call or
construct style mangled names while providing early-binding
opportunities to optimizing implementations, without colliding with
existing plain-named 'call' or 'construct' ad-hoc properties in the
installed base.
On 2006-11-09, at 17:47 EST, Brendan Eich wrote:
On Nov 9, 2006, at 2:19 PM, P T Withington wrote:
On 2006-11-09, at 16:29 EST, Brendan Eich wrote:
It would also be helpful for bootstrapping if new and apply could
be composed; this came up when I wrote Narcissus. Suggestions?Make new a class method rather than a keyword? (I know, too
controversial. But that is the Lisp answer.)We could expose intrinsic::construct as the meta-object protocol
hook underlying new, and allow you to write, e.g.:class Complex! { static intrinsic function construct(real:double,
imag:double):Complex {...} . . . }Then you could always invoke 'Complex.intrinsic::construct.apply (Complex, args)' where args is [real, imag]. Or something simpler
to write (static intrinsic function my summer vacation was very
nice ;-), but this example is in keeping with the proposed call
hook (intranet.mozilla.org/ECMA/wiki/doku.php? id=proposals:builtin_classes). Is this what you mean?
Yes. In our implementation new x(...)
is just syntactic sugar for
x.make(...)
. (And make, rather than magically acquiring this
as
constructors do, uses super.make.apply(arguments)
.)
On 2006-11-09, at 17:45 EST, @lnicodon wrote:
The thing I most miss about apply is that I cannot have 'spread args':
foo.apply(bar, bletch, crud, rest) === foo.apply(bar, [bletch, crud].concat(rest))
Is
foo.call(bar, bletch, crud, rest)
an answer, or did I miss the point ?
My example was poor. Replace rest
with [some, more, arguments]
.
What I'd like to say:
foo.apply(bar, bletch, crud, [some, more, arguments])
What I have to say now:
foo.apply(bar, [bletch,crud].concat([some, more, arguments]))
I.e., I wish apply took any number of arguments, where the last
argument was an array that would be 'spread' into the call:
foo.call(bar, bletch, crud, some, more, arguments)
Pardon a somewhat overly long message on a stray issue, but I want to warn against (however well meaning) syntactic sugar introduced in this manner; it's been done before and often ends up doing a lot more harm than good. If you have already spotted the culprit, you need not read on.
On 11/10/06, P T Withington <ptw at pobox.com> wrote:
My example was poor. Replace
rest
with[some, more, arguments]
.What I'd like to say:
foo.apply(bar, bletch, crud, [some, more, arguments])
Imagine you wanted to pass the literal argument array ["bletch", "crud", ["some", "more", "arguments"]] (yes, the third argument being an array) to a bar.foo which takes three arguments. It could of course still be done, by way of either of these syntaxes:
foo.apply(bar, "bletch", "crud", ["some", "more", "arguments"], []); foo.apply(bar, "bletch", "crud", [["some", "more", "arguments"]]);
...but I would guess the most common case in practice would be unsuspecting programmers just passing their third variable blotch (containing ["some", "more", "arguments"], run time), arriving at bar.foo like the call
bar.foo( "bletch", "crud", "some" );
...for all practical purposes, since this version of the function doesn't look at any later arguments. With luck, it breaks loudly for receiving a string where it was intended to have received an array, but Murphy says it'll fail silently until the code has been deployed in the wild.
What I have to say now:
foo.apply(bar, [bletch,crud].concat([some, more, arguments]))
I.e., I wish apply took any number of arguments, where the last argument was an array that would be 'spread' into the call:
foo.call(bar, bletch, crud, some, more, arguments)
Are you really sure that would be a good idea in practice? To cater the case where the last argument passed to foo may sometimes actually contain an array value, the idiomatic apply call signature would always have to be foo.apply( bar, all, args, here, [] ) where you don't use this feature, or that last "here" may end up unpacked across a few extra arguments when it was an array, so foo's third argument actully ends up here[0] rather than what the here variable contained.
It looks like typical syntactic sugar for one case turning into syntactic rat poison for all other cases to me, much like how array literal and new Array() notation is anomalous for the case where the constructor gets passed a single integer attribute (in at least several implementations of javascript -- I am not sure whether it applies to ecmascript 4 too);
new Array("high", 5); => ["high", 5]
new Array("hi"); => ["hi"]
new Array(5); => [undefined, undefined, undefined, undefined, undefined]
Introducing a proper unpack operator would more safely avoid the syntactic overhead you dislike, without adversely affecting other cases, but I would rather keep the pain than defend future code against the proposed feature. It would end up just another one of those dangerous backwaters of the language where only the top few percentiles would go safe and those advanced enough to have found the apply but not its quirks, would be infested with difficult-to-find bugs. Worse still, not always, only in a potentially uncommon case dependent on the runtime type of the last function argument passed.
Completely agree, it's hard to DWIM if array actual parameters get
auto-unpacked without the callee's formal parameter list saying so
(destructuring formal parameters). The better design bias IMHO is to
make callers who want an array actual to be unpacked do more work, be
explicit about what they want. So I'm actually in favor of requiring
something like
foo.apply(bar, [bletch,crud].concat([some, more, arguments]))
Of course it sucks not to be able to use an operator for array
concatenation, but that's fixable in ES4 with operators (tangent:
should we do that in the standard? E.g., map function +(a:Array,
b:Array):Array onto concat?).
On 2006-11-10, at 15:21 EST, Johan Sundström wrote:
[...]
On 11/10/06, P T Withington <ptw at pobox.com> wrote:
My example was poor. Replace
rest
with[some, more, arguments]
.What I'd like to say:
foo.apply(bar, bletch, crud, [some, more, arguments])
Imagine you wanted to pass the literal argument array ["bletch", "crud", ["some", "more", "arguments"]] (yes, the third argument being an array) to a bar.foo which takes three arguments. It could of course still be done, by way of either of these syntaxes:
foo.apply(bar, "bletch", "crud", ["some", "more", "arguments"], []); foo.apply(bar, "bletch", "crud", [["some", "more", "arguments"]]);
Then I would not use apply
. I would use call
.
foo.call(bar, "bletch", "crud", ["some", "more", "arguments"]);
This is exactly the difference between call
and apply
. apply
takes an Array and 'spreads' the arguments out to call
. All I was
asking is to amend apply
to allow you to specify additional spread
arguments before the Array.
Allowing additional arguments before the array is confusing. And aren't you just as likely to want to pass additional arguments after the array? Using an overridden "+" operator - or concat() for that matter - doesn't seem like much of a burden.
Also, unless I'm missing something, the language wouldn't be able to enforce the type of that last array argument any more. So, while this feature would be backwards compatible, it would mean losing something and, even though that thing is small, it doesn't look like much is being gained either.
Peter
Also, unless I'm missing something, the language wouldn't be able to enforce the type of that last array argument any more. So, while this
In ES3 there's no type enforcement, and I wouldn't count on being able to come up with a type for apply anyway. I'm not saying I agree with one side or another on this point, but I'm not sure our type system can express useful type constraints on apply. I believe it's a dependently typed function.
ES3 has little to do with it. In ES4, apply's signature would currently be something like:
function apply(target:Object, args:Array):*;
And there would be compile-time and run-time errors if args was not an Array. With P T Withington's suggestion, it would be:
function apply(target:Object, ...rest):*;
in which case, there would be no compile-time errors and run-time errors would depend on the implementation.
Agreed that apply() is intrinsically dynamic, but there can be some constaints on how it is used, and those can be enforced by the language.
Peter
ES3 has little to do with it.
It does insofar as your point was that we lose something by making
apply' harder to type: since
apply' has never had a type, we haven't
lost anything per se.
But I see what you're saying: it's an "opportunity cost" since it becomes less ES4-typeable.
function apply(target:Object, args:Array):*;
..which is a pretty weak type.
A more precise type for such a function would be something like:
function.<T1, ..., Tn, Trest, Tres>
apply(fun:function(x1:T1, ..., xn:Tn, ...rest:Trest):Tres,
args:[T1, ..., Tn, Trest]):Tres
We can almost express this type in our proposed type system (I think I was wrong about this being a dependent type). It's just a generic type, but one with a variable number of type parameters, which we can't handle.
And there would be compile-time and run-time errors if args was not an Array. With P T Withington's suggestion, it would be:
function apply(target:Object, ...rest):*;
[As a curious side note: we could almost express the type of the Lispy version of `apply'--i.e., a sequence of arguments of type * followed by a single argument of type Array--by giving the rest-args a structural array type; see
developer.mozilla.org/es4/proposals/structural_types_and_typing_of_initializers.html
for a sketch. That page doesn't explain very much but the idea is that array types can have finite heterogeneous prefixes--e.g. the type of arrays whose first element is a Boolean, second element is a String, and remaining elements are ints. So we can express the types of a finite prefix followed by an unbounded suffix, but we can't express the types of an unbounded prefix followed by a finite suffix, which is what we would need in this case.]
in which case, there would be no compile-time errors and run-time errors would depend on the implementation.
Even if it doesn't have a precise type, the library spec can still impose constraints on implementations so it isn't implementation-specific. You're right about compile-time errors, though.
Agreed that apply() is intrinsically dynamic, but there can be some constaints on how it is used, and those can be enforced by the language.
Sure, although it's a modest amount of checking. The trade-off is between a rather minor convenience in error checking versus a similarly minor convenience in flexibility of use.
Is argument unpacking like in Python planned?
See docs.python.org/tut/node6.html#SECTION006740000000000000000 for an introduction.