Function with rest of arguments

How do I define a function which gets some positional arguments and a whole rest of arguments. This is not working:

(
{arg x, ...rest; [x, rest].postln}.(1,2,3,4,5)
)

I had expected it to answer [1, [2, 3, 4, 5]]

Because the ... is already a separator between tokens, the comma would be redundant and should be left out.

(
{ arg x ... rest; [x, rest].postln }.(1,2,3,4,5)
)

I was looking for where this is documented and… can’t find it just now…

EDIT: OK, yeah, this is confusing: “If the last argument in the list is preceded by three dots (an ellipsis), then all the remaining arguments that were passed will be assigned to that variable as an Array. Arguments must be separated by commas” when it’s really “Arguments must be separated by commas except when using ...” – Like in your writing classes in school, “A paragraph should have only one topic sentence” – this one switches topics and led to a wrong conclusion that “,” is required for ... args.

So maybe change like this:

Arguments must be separated by commas.**

If the last argument in the list is preceded by three dots (an ellipsis), then all the remaining arguments that were passed will be assigned to that variable as an Array. In this case only, the comma should be omitted.

** (Except that isn’t true either: { |x 1 y 2| [x, y].postln }.value)

hjh

1 Like

… and `{| a b … args| } doesn’t need commas either!

how about:

An argument list immediately follows the open curly bracket of a function definition. An argument list either begins with the reserved word arg, or is contained between two vertical bars. When using the ‘arg’ style arguments must be separated by commas, when using vertical bars (‘pipe mode’) commas are optional.
[ … ]
If the last argument in the list is preceded by three dots (an ellipsis), then all the remaining arguments that were passed will be assigned to that variable as an Array. Ellipses should not be preceded by a comma.

Slightly off topic…

Was just looking at the bison file to see how this was defined and realised a whole bunch of stuff.
This is all valid…

Modify existing (right to left) arguments

{
	|foo(#car, bar = gar.collect(_+1)), bar, car ...gar|
	[foo, bar, car, gar].postln
}.(gar: [1,2,3]) // [ [ 2, 3, 4 ], 3, 2, [ 1, 2, 3 ] ]

Thowing if an arg isn’t set (perhaps legitimately useful?)

~foo = {
	|a( Error("arg not set").throw )|
	a + 1
};
~foo.(); // throws
~foo.(1); // 2

Mutating globals.

~c = 10;
~m = { var out = ~c; ~c = ~c - 1; out };

~a = { |a( ~m.() )| a  };
~a.() // 10
~a.() // 9
~a.() // 8
....

Multiple expressions and blocking.

~a = {
	|a( 1.wait; \bar )|
	a.postln 
}

Any valid expression can go in there, including, my favourite…

{ |head(thisFunction.value)| }.()   // (this will crash the interpreter)

Also, when using pipes (which is called slotdef in the code) there can can only have literals after the equals sign but any expression in brackets, whereas with arg you can have any expression after the equals.

ok this is nuts - can you walk me through this? when did car get set to 2?

If there’s arg a = expression or | a(expression) |, it compiles the same as arg a = nil; a !? { a = expression }.

So this example is really:

{
    |foo, bar, car ...gar|
    foo !? { foo = (#car, bar = gar.collect(_+1)) };
    [foo, bar, car, gar].postln
}.(gar: [1,2,3]);

EDIT: I forgot the variable reassignment initially, fixed.

I’ve doubts about readability but it’s interesting that it can be pushed this far.

hjh

Yeah, the arg list syntax and semantics are pretty crusty when you consider all the cases :smiley: I don’t think anyone makes too significant use of it.

This is one of the more delightfully weird corners of the sclang syntax. But, consider that this partly what saves us from the fate of Python, where the default values for function arguments are actually globals that can be mutated

As soon as you can have any default that isn’t a literal (essential for sclang because lots of basic things aren’t literals…), you’re a bit stuck allowing any expression as a default and executing it more or less like this - or, disallowing “weird” cases in semi-arbitrary ways. I think the semantics and allowances here are pretty close to C++, except in C++ the defaults are evaluated at the call site so the other arguments aren’t in scope to use / mutate.

It used to be possible to mutate String literal argument defaults :astonished: until we made String literals immutable to fix that :laughing:

hjh