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…)