(_+_+_) confusion

The brief way to write a function with “placeholders” works for 2 args, but seems not to produce the expected result with 3 args:

(_+_).(1, 2) // -> 3

(_+_+_).(1, 2, 3) // -> a BinaryOpFunction

Is there a way to fix this without abandoning the brief/placeholder notation?

I also tried

([_, _, _].sum).(1, 2, 3)
// The preceding error dump is for ERROR: binary operator '+' failed. RECEIVER: nil
// That doesn't work not even like
([_, _].sum).(1, 2) // err
// although just
([_, _]).(1, 2) -> [ 1, 2 ]

I think the problem with first approach is that even

(_.neg + _).(1, 2) // -> a BinaryOpFunction

I see there’s probalby no way to make that work with the current parser

An example of what you can’t do:

(1..8).collect( Point(100 * _, 50) ); // nested expression won't work.
// only the * gets partially applied, not the surrounding expression

Although 3 placeholder args do work in some limited contexts

([_, _, _]).(1, 2, 3) // -> [ 1, 2, 3 ]

But the issue with + is that’s binary, so… it’s like having more “surrounding expression” in _+_+_. So

(_+_+_).(1).(2, 3) // -> 6

Also the order of application is not what you might think in such nested beasts. It’s actually right to left:

(_.neg + _).(1).(4) // -> -3

Actually, it’s possible uncurry that (and even make the order of arguments more “normal”, i.e. left to right in the process)

~uc = {|f| {|y, x| f.(x).(y)} }
f = { |a| { |b| b.neg + a } }
~uc.(f).(1, 4) // -> 3
~uc.(_.neg + _).(1, 4) // -> 3

~uc1 = {|f| { |x ...args| f.(x).valueArray(args) } }
f = { |a| { |b, c| a + b + c } }
~uc1.(f).(1, 2, 3) // -> 6
~uc1.(_+_+_).(1, 2, 3) // -> 6

The order of arguments for uc1 above is still wrong.

~uc1.(_**_+_).(1, 2, 3) // -> 9.0

// The right one is:
~uc1 = {|f| { |...args| f.(args.pop).valueArray(args) } }
~uc1.(_**_+_).(1, 2, 3) // -> 4.0


I’m still not sure how to fix the [_, _, _].sum though since that gives an outright error. The problem with the latter is that even {}.sum is an error, while that should probably build some kind of AbstractFunction. Actually [{}].sum does build that (a BinaryOpFunction)
so, I’m probably still thinking the wrong way about this latter example… although [_, _, _] itself is a plain Function. The problem seems to be that sum evaluates functions (or rather Function.sum does that

{2}.sum // -> 4
{[1, 2]}.sum // -> [ 2, 4 ]

And that’s because

Function {
	sum { arg n = 2;
		var sum = 0;
		n.do {|i| sum = sum + this.value(i) };
		^sum
	}
}

So yeah, one has to bypass that. And one way to do it

UnaryOpFunction(\sum, [_, _, _]).value(2, 3, 4) // -> 9

Or equivalently, stealing the guts of that

~uf = { |m, af| { |...args| af.valueArray(args).perform(m) } }
~uf.(\sum, [_, _, _]).(2, 3, 4) // -> 9

I’ve done an “automated” version the uncurry thing, but it’s pretty horrible just to handle usual trees build by a simple (_+_+_+_) style chaining. It only handles binary-ops for now, but it does auto-detect how many arguments the bottom layer needs because you need to know that to know when to stop the recursion without consuming too many of the args. Otherwise:

(_+_+_).(3).(2) // boom because there's no automatic currying in SC

So

(~uca = {|f| { |...args|
	var rval = f, rcall = {
		var fda = [], nfa = f.def.argNames.size;
		nfa do: { fda = fda.addFirst(args.pop) };
		rval = rval.(*fda);
	};
	while { rcall.().isKindOf(BinaryOpFunction) } {
		f = rval.slotAt(\a);
		while { f.isKindOf(BinaryOpFunction) } { f = f.slotAt(\a) };
	};
	rval
}})

Some tests:

~uca.(_+_+_+_+_+_).(1, 2, 3, 4, 5, 6) // 21
~uca.(_.neg + _).(1, 4) // 3

As I discovered, my assumption that the top-level thing is a Function isn’t quite correct in the case of constant in the last position (but only for two or more real args):

(3+_) // -> a Function
(_+3) // -> a Function

(3+_+_) // -> a Function
(_+3+_) // -> a Function
(_+_+3) // -> a BinaryOpFunction

But you can work around that for now, e.g.

(~uca.(_+_)+3).(1, 2) // 6
(~uca.(_+_+_)+4).(1, 2, 3) // 10

Actually the version that handles that case correctly was an easy change (and I’ve got rid of the helper function too, it was there just for clarity):

(~uca = {|f| { |...args|
	var rval = f, fda;
	while {
		while { f.isKindOf(BinaryOpFunction) } { f = f.slotAt(\a) };
		fda = []; f.def.argNames.size do: { fda = fda.addFirst(args.pop) };
		(f = rval = rval.(*fda)).isKindOf(BinaryOpFunction)
	} {};
	rval 
}})

~uca.(_+_+_+4).(1, 2, 3) // 10 (ok now)

And that still doesn’t handle parathesized expressions that build the “deep tree” on the right (Slot b).

~uca.(_+(_+_)).(1, 2, 3) // boom
~uc1.(_+(_+_)).(1, 2, 3) // 6

So one need a proper AST traversal to handle that

(~maxfas = { |f|
	case { f.isKindOf(BinaryOpFunction) } {
		max(~maxfas.(f.slotAt(\a)), ~maxfas.(f.slotAt(\b)));
	} { f.isKindOf(Function) } {
		f.def.argNames.size;
	}
	{ /* ("Unhandled " + f.cs).postln; */ 0; }
};)

~maxfas.("boo")
~maxfas.(_+_)
~maxfas.(_+(_+_)) // -> 1 Ok, because it's an uneval fun
~maxfas.((_+(_+_)).(3)) // -> 2

(~uca = { |f| { |...args|
	var fda;
	while {
		fda = []; ~maxfas.(f) do: {fda = fda.addFirst(args.pop)};
		(f = f.(*fda)).isKindOf(BinaryOpFunction)
	} {};
	f
}})

~uca.(_+(_+_)).(1, 2, 3) // 6

Handling the other AbstractFunctions now involves adding more cases to ~maxfas.

But an issue I see is that it’s not clear how you could even handle the following correctly (in automatic way) since the tree built is “wrong to begin with” to make left-right parameter assignments by popping

~uc1.((max(_, _ + 1))).(1, 2) // 2 ??
~uca.((max(_, _ + 1))).(1, 2) // 2 ??

(max(_, _ + 1)).(2).(1) // 2
(max(_, _ + 1)).(1).(2) // 3

The list approach also has gotchas this way

[_, _ + 1].(1, 2) // -> [ 1, a Function ]

And I think I totally broke the system with

(max(_ + _, _ - _)).(1, 1) // -> 2
(max(_ + _, _ - _)).(1, -1) // -> 2
((_+_)+(_+_)).(1, 2) // -> 6

Note that these only take 2 arguments total (not 4) but return a final value. It’s because BinaryOpFunction duplicates its arguments to its children nodes. So that preserves the total number of placeholders only if one of the slots of BinaryOpFunction is a constant. (I was hoping to count how many arguments the LHS and RHS take and pass the appropriate number to each slot…)

Actually, it occurred to me that once you get in the right mindset, you can use the placeholders more effectively, despite their limitations. E.g. the above can be written more directly with no helper functions besides composition as

(_.sum <> [_, _, _]).(2, 3, 4) // -> 9

The first placeholder is actually “used up” here for the result returned from the 2nd function, which does take 3 arguments. But still that’s more writing than using named args.

{|...aa| aa.sum}.(2, 3, 4)

FWIW: consider ‘reduce’ and ‘inject’, either directly or for your helper function / method.

Actually one can use the weird fact that BOF (BinaryOpFunction ) replicates the args over the sub-expressions and bit of a helper compose-with-array function to write functions with numerical arg position like:

((_[0]*_[1]+_[2]) <> [_, _, _]).(2, 3, 4) // -> 10

~caf = { |f| { |...aa| f.(aa) } } // helper

~caf.(_[0]*_[1]+_[2]).(2, 3, 4)  // -> 10

Writing with @ instead of brackets doesn’t save any typing here because unlike brackets @ doesn’t have precedence, so you have use parens

(_@0 + _@1).([1, 2]) // BOF!
((_@0) + (_@1)).([1, 2]) // 3

Alas this _[i] notation isn’t much of a gain over using letter args; even with an arg list you have roughly 3 chars per arg (2x letter + comma), and with this you have four (plus the helper call). And it’s obviously something you can get with a single (varargs-style) arg array…

~caf.(_[0]*_[1]+_[2])
     {|a,b,c| a*b+c}
{|...a| a[0]*a[1]+a[2]}
// for comparison with the non-index placeholder
{|a,b,c| a*b+c}
~uca.(_*_+_)

~caf.(_[0]+_[1]+_[2]+_[3]+_[4])
{|a,b,c,d,e| a+b+c+d+e}
~uca.(_+_+_+_+_) 

Sorry for bumpin in… This is kind of thread is exactly why i keep lurching on this forum. (: . Peace out and stay You.

<3 Lukiss.