Just picking this up again. I’ve made some changes that others might be interested in…
I’ve also removed the backwards pipe, because it was just confusing and I never used it.
Happy to make a PR if liked by others - @license.
Again, these are only useful if you keep the functions short and don’t nest combinators as it gets messy quickly!
If anyone has any suggestions or useful (or even highly controversial) opinions please let me know
implementation
PF {
*new {|a| ^PF.getFunc(a) }
*getFunc {|a|
^a.isKindOf(Symbol).if(
{ {|...b| a.applyTo(*b)} },
{ a }
)
}
*expand { |f|
^{|x| PF(f).(*x) }
}
*id { ^{|n| n} }
*concat {
^{ |...x| x.reject(_.isNil) }
}
*concatKeepNil {
^{ |...x| x.reject(_.isNil) }
}
*tap {|f|
^{|x| PF(f).(x); x }
}
*par {|redux ...fs|
if(fs.size < 1, {Error("PF.fork must have 1 or more functions").throw});
^{|x|
PF.concatKeepNil.(x, *fs.collect({|f| PF(f).(x) }))
.reduce(PF(redux))
}
}
*fork {|redux ...fs|
if(fs.size < 3, {Error("PF.fork must have 3 or more functions").throw});
^{|x|
fs.collect({|f| PF(f).(x) })
.reduce(PF(redux))
}
}
*split {|...fs|
^{|xs|
if(fs.size != xs.size, {Error("Size mismatch in split").throw});
xs.collect{|x, i| PF(fs[i]).(x) }
}
}
}
+Object {
|> { |f, adverb|
^case
{adverb.isNil} {PF(f).(this)}
{adverb == \fork}{
PF.fork(*f).(this)
}
{adverb == \par}{
PF.par(*f).(this)
}
{adverb == \tap} {
PF.tap(f).(this)
}
{adverb == \split}{
PF.split(*f).(this)
}
{ Error("does not understand adverb " + adverb).throw }
}
}
+Symbol {
|> {|f, adverb| ^PF(this).perform('|>', f, adverb) } // deffers to Function's impl
}
+Function {
|> { |f, adverb|
^case
{adverb.isNil} {
{ |...a| PF(f).(this.(*a)) }
}
{adverb == \fork}{
if(f.size < 3, {Error("|>.fork must have an array of 3 or more functions").throw});
{ |...a|
PF.fork(*f).( this.(*a) )
}
}
{adverb == \par}{
if(f.size < 2, {Error("|>.split must have an array of 2 or more functions").throw});
{ |...a|
PF.par(*f).( this.(*a) )
}
}
{adverb == \tap} {
{ |...a|
PF.tap(f).( this.(*a) )
}
}
{adverb == \split}{
{|...a|
PF.split(*f).( this.(*a) )
}
}
{ Error("does not understand adverb " + adverb).throw }
}
}
1 Define functions
This wasn’t possible before, you had to use it there and then.
a = {|l,r| l + r } |> _.pow(2);
a.(1, 2); // produces (1+2).pow(2)
or
a = PF('+') |> _.pow(2);
a.(1, 2);
or just
a = '+' |> _.pow(2);
2 Side effects - tap
Named after its use in ruby.
Used for side effects, like outputting or writing to a buffer. It returns the input, but executes the function, ignoring its output.
SinOsc.ar(345)
|> LPF.ar(_, 565)
|>.tap Out.ar(\sideOut.kr, _) // evaluated, but output ignored
|> Out.ar(\out.kr, _)
4 Expand
Since we don’t have multiple return values this is necessary.
[1, 1] |> PF.expand('+') // 2 - behaves as reduce here
[1, 1, 1] |> PF.expand('+') // 2 '+' only takes 2 args, final 1 dropped
[1, 1, 3] |> PF.expand({|a, b, c| a + b * c}) // 6 (1+1*3)
5 Concat
++
doesn’t work with numbers and many things. This is a problem when writing generic code.
a = PF.concat;
a.(1,2,3,4)
This removes Nils as otherwise calling with things like reduce produce an extra value. This is probably an issue with reduce.
For that reason there is also PF.concatKeepNil
.
This is particularly ugly…
IMHO, 1 ++ 1
should work and "asdf" ++ "fdsa"
should produce ["asdf", "fdsa"]
.
6 Some Nary combinator / reduce thing - fork
Takes 3 or more functions.
First is a binary op, used to .reduce
the others.
a
|>.fork ['+', _*2, _-1, _+1 ]
…this does …
( a * 2 ) + (a - 1) + (a + 1)
This means mid-side conversion can be written like…
stereo
|>.fork [PF.concat, PF.expand('+'), PF.expand('-')]
7 Identity
Some times .value
returns the object, sometimes it doesn’t. This always returns the object.
a = PF.id
a.(30) // 30
8 Variant of fork - par
Takes 2 or more functions, first function is the reducer (as in fork), remainder are passed the object,
however… the reduce function is also passed the original object.
boost = {|sig, db| BHiShelf.ar(sig, 5000, 1, db) |> LPF.ar(_, 11000) };
WhiteNoise.ar
|>.par ['+', boost(_, 4)]
… this does…
var sig = WhiteNoise.ar;
sig = sig + boost(sig, 4);
9 Variant of expand - split
[1,2,3]
|>.split [_+1, _.pow(2), _-2]
Spread args in array over each function.
10 Adverbs also care callable like…
PF.split
This makes some things nicer to read.
|>.split [ foo, bar ] // does the samething
|> PF.split(foo, bar) // does the samething
example
sig
|>.fork [PF.concat, PF.expand('+'), PF.expand('-')] // to ms
|>.split [PF.id, _ * 4.dbamp] // boost side
|>.tap PF.split(BufWr.ar(_, \midBuf.kr), nil) // write mid to buffer
|>.fork [PF.concat, PF.expand('-'), PF.expand('+')] // to stereo