Ticket #357 (new defect)

Opened 5 months ago

Last modified 1 week ago

Add ... (splat) operator to complement rest params and support new/apply composition

Reported by: brendan Assigned to: anonymous
Type: defect Priority: major
Milestone: Component: Proposals
Version: 4 Keywords:
Cc: graydon, jeffdyer, lth, chrispi, dherman, cormac, jaz, ptw

Description

Define ... as a unary prefix operator, informally called "splat", at the same precedence level as !, ~, -, delete, typeof, void, etc.

Require that a splat expression be used only as the last or only actual parameter in a call or new expression.

Require the evaluated operand of splat to be an arguments or array object, else throw a TypeError?.

Evaluate splat once its operand has been evaluated by replacing the arguments or array object, call it A, with A.length actual trailing parameters from A[0] to A[A.length-1].

If the callee or constructor requires a different total number of arguments from the result after replacing A with its elements, throw a TypeError? as for any argument count mismatch.

Perform actual vs. formal type checking and conversion as usual, after replacing A with its elements and matching against trailing formals, including any optional ultimate ...rest parameter.

/be

Attachments

Change History

Changed 5 months ago by jaz

  • cc changed from graydon, jeffdyer, lth, chrispi, dherman, cormac to graydon, jeffdyer, lth, chrispi, dherman, cormac, jaz

Implemented in revision c346be33eeaffd40c3404beb144d4c39a6d3ee8c

I'd really like a code review or three, if anyone has the inclination. Here's an overview of the implementation:

ast.sml - Ast.Spat is added as a new unary operator

parser.sml - argumentList was changed to allow a splat expression only as the last argument in the list

- A splat expression is parsed as a UnaryExpr?.

verify.sml - The verification for a splat expression (incorrectly) expects the splatted value to be Ast.ArrayType? [anyType]. It should also allow an arguments object, but as far as I can tell, no such thing exists in the RI right now.

mach.sml - Added a new variant to Mach.VAL: Splat of VAL. I don't know if that was a good idea. When a splat expression is evaluated (assuming there is no error), a Splat value is created. The Splat value is really meaningful only to evalArgs in eval.sml. That function knows to splice the splatted array into the function arguments list.

eval.sml - In addition to the evalArgs changes mentioned above, I added new arms to a bunch of case expressions that dispatch on Mach.VAL objects. Most of these exist only to mollify the compiler (so we don't get so many non-exhaustive match warnings). However, I'm not entirely confident that my changes here are right.

- I moved the arrayToList function from native.sml to eval.sml, since I needed it in evalArgs, and attempting to call it as Native.arrayToList resulted in a circular dependency error, whereas calling Eval.arrayToList from native.sml worked fine. Perhaps the function should be elsewhere, though?

Changed 5 months ago by lth

Isn't this functionality somewhat redundant? For functions we have apply, and even the static apply method on the Function object. For new, the meta-objects proposal provides for a reflect::construct() method on all objects that implement ClassType that instantiates the class; this is a proper method and takes an array of arguments. (Well, the current spec says it takes a ValueIterator but that's a bug.)

Changed 5 months ago by brendan

We can squeeze out redundancy several ways. I would rather do it by getting rid of the less compositional and more verbose extensions. Problems or limitations with apply and construct:

* All-or-nothing array requirement -- you can't splat an array at the end of a list of positional actual parameters.

* Function.apply -- verbose novelty, who needs it given splat?

* Neither of these helps with composing new and apply (call it the new o apply problem, hope I have order right) for all constructors.

* reflect::construct does not help the function-as-constructor new o apply case, if I understand it correctly. Using it on the Function class would invoke new Function with an array of arguments. It could not be used on a function object (a Function instance).

I would gladly lose Function.apply in favor of splat. Function.prototype.apply allows an explicit |this| parameter to be passed, and it's not going away anyway. But it is not enough to compose new and apply, or to handle combinations of positional and trailing arrayed actual parameters.

Rather than extend apply, this ticket proposes a universal operator that's orthogonal to what the callee or constructor might be, what's in its prototype chain, what its meta-object is, etc.

/be

Changed 5 months ago by jaz

Lars and Brendan's messages made me revisit the thread from es4-discuss that led to this proposal. The example was:

class A extends B {
     function A(x,y,z) : super(...arguments) { }
  }

Turns out, my code didn't work properly for that case. This is fixed in revision b051495df01e84b1e587ee6365f7847dd5121391.

Changed 5 months ago by brendan

Jon, thanks for RI'ing, and for recovering that other reason splat wins and apply loses: super calls.

/be

Changed 5 months ago by ptw

  • cc changed from graydon, jeffdyer, lth, chrispi, dherman, cormac, jaz to graydon, jeffdyer, lth, chrispi, dherman, cormac, jaz, ptw

This is extremely important to me for the super case. When I need a constructor and don't need to care about my superclass's arguments, but want to pass them on untrammled:

  class Foo extends Bar {
    function Foo(...rest) : super(...rest) {
      ...
    }
    ...

Also, it allows me to prefix arguments in apply without having to concat:

  foo.apply(this, [1, 2].concat(more));

becomes:

  this.foo(1, 2, ...more);

I have several use cases for this in the OpenLaszlo code base.

[Historical note: in Lisp this same operation was called 'spreading' the arguments, and accomplished by . (cons-ing), since rest arguments were Lists not Arrays:

  (foo 1 2 . more)

There was a fun optimization in LispM hardware to optimize away the consing if the called function took a rest arg in the right spot.]

Changed 5 months ago by brendan

So to be clear, I would like this ticket to include, as a consequence of the ... as unary operator being universal, the withdrawal of Function.apply (the static Function method that is generic with respect to its first parameter's type provided that type is callable) as a mini-proposal in ES4.

In short, no need for

Function.apply(callable, thisp, argsArray)

Just use:

Function.call(callable, thisp, ...argsArray)

Function.call(callable, thisp, arg1, ... argN) still makes sense, since it allows thisp to be passed explicitly.

/be

Changed 5 months ago by lth

That kind of minimalism seems uncalled for, IMO. The cost of including apply is very small indeed.

Changed 5 months ago by brendan

Lars, I took your "Isn't this functionality somewhat redundant? For functions we have apply, and even the static apply method on the Function object" from an early comment in this ticket too much to heart. If you don't hold to that (new o apply, super o apply, are both strong use-cases in my book, sounds like the latter won you over), then I withdraw my proposed withdrawal of Function.apply ;-).

/be

Changed 4 months ago by jaz

Per Jeff Dyer's grammar update, I updated the RI to allow splat expressions in array literals. (Revision 5137803983c4392d80ba61b22356ed56650a7fc9.)

Changed 2 months ago by airforce1

Changed 1 week ago by add

Iron decorates your home. They have a wide variety of design, assuring that you will find a special one for display at home. The metal stai http://www.hebei-railings.cn/

Note: See TracTickets for help on using tickets.