Pforai
Pforai code/implementation
Pforai : Pattern {
var <>outerPattern, <>arrayPattern, <>itemMergeFunc, <>arrayEndNils = 1;
*new { arg outerPattern, arrayPattern, itemCombineFunc, arrayEndNils = 1;
^super.newCopyArgs(outerPattern, arrayPattern, itemCombineFunc, arrayEndNils)
}
embedInStream { arg inval;
var outerStr = outerPattern.asStream, arrayStr = arrayPattern.asStream;
var outerVal = outerStr.next(inval), innerVal;
var nilCount = 0, arrayIdx = 0, array;
if (outerVal.isNil) { ^nil; };
loop {
array = arrayStr.next(inval);
// Nil test must come before we pad
// because (nil ++ nil.dup(1)) == ([] ++ nil.dup(1))
// and we want to treat nil array differently from an empty one here.
if (array.isNil) { ^nil };
array = array ++ nil.dup(arrayEndNils);
array.do { |item, idx|
if (item.isNil) {
// always advance outerStr on a nil item in the array
outerVal = outerStr.next(inval);
if (outerVal.isNil) { ^nil };
} { // todo: maybe have flag to pass nils to func; it's nasty for that to the default though
inval = yield(itemMergeFunc.value(outerVal.copy, item.copy, inval.copy, idx, array.size));
};
};
};
}
// TODO: handle cleanups (for event streams)
// todo: storeOn
}
Basic usage is even simpler than for Pforp because you don’t have to even build a Pseq-style sequence:
Pforp(Pseries(), Pseq([0, 10, nil, 100, nil], inf), (_+_), 2).iter.nextN(9)
// -> [ 0, 10, 101, 2, 12, 103, 4, 14, 105 ]
// Now just:
Pforai(Pseries(), [0, 10, nil, 100], (_+_)).iter.nextN(9)
// -> [ 0, 10, 101, 2, 12, 103, 4, 14, 105 ]
But it accepts a pattern on the 2nd arg, not just fixed array. Before we get to that kind of usage, more comparison with the previous solutions. As you could probably from the above, the a “array end” is (by default) equivalent to one nil, i.e. it will automatically pull (just one) value from the outer pattern (which by the way, it still a simple pattern.) Recall the nasty example we trying to deal with in the end of the last post:
Pforp2(Pseries(), Pseq([0, 10, nil, nil, 100]), (_+_), 3).iter.nextN(9)
// -> [ 0, 10, 102, 4, 14, 106, 8, 18, 110 ]
// Now:
Pforai(Pseries(), [0, 10, nil, nil, 100], (_+_)).iter.nextN(9) // arr end just advances by 1 now
// -> [ 0, 10, 102, 3, 13, 105, 6, 16, 108 ]
There’s no weird jump now at the end of the array, the 3s (from the outer pattern) are not “mysteriously” missing anymore.
Pforai also takes a numerical 4th argument, but unlike Pforp this says how many nils the array end translates to. So for a (pretty extreme) example you could skip 1000 items from the outer stream when the array ends:
Pforai(Pseries(), [0, 10, nil, nil, 100], (_+_), 1000).iter.nextN(9)
// -> [ 0, 10, 102, 1002, 1012, 1104, 2004, 2014, 2106 ]
And yes, you can set that 4th argument to zero if don’t want any “row advances” (outer stream pulls) when the array “wraps around”.
Pforai(Pseries(), [0, 10, nil, 100], (_+_), 0).iter.nextN(9)
// -> [ 0, 10, 101, 1, 11, 102, 2, 12, 103 ]
That zero setting will generally makes sense only you’re using some internal nils in the array.
The array is actually a stream/pattern of arrays instead of a fixed one… The following examples are bit gratuitous (as they could be done with a plain array with nils in the right places), but it shows that it works to have Pseqs outputting arrays there. And someone might prefer “sub arrays” to writing nil for outer pattern “advance/next command”, but these have to be a in Pse.
Actually the first of the following examples is not entirely gratuitous because the entire array has to be/become a nil for the inner patter to terminate the Pforai:
Pforai(Pseries(0, 10), Pseq([[1, 2], [5, 6]]), (_+_)).iter.nextN(9)
// -> [ 1, 2, 15, 16, nil, nil, nil, nil, nil ]
Pforai(Pseries(0, 10), Pseq([[1, 2], [5, 6]], inf), (_+_)).iter.nextN(9)
// -> [ 1, 2, 15, 16, 21, 22, 35, 36, 41 ]
Can obviously vary array size between “iterations” output by the Pseq.
Pforai(Pseq([10, 100], inf), Pseq([[1, 2, 3], [66]], inf), (_+_)).iter.nextN(9)
// -> [ 11, 12, 13, 166, 11, 12, 13, 166, 11 ]
With a “dummy”/constant outer pattern is basically an array-swap-enabled Pseq-like, plus Pcollect-syyle map.
Pforai(100, [1, 2, 3], (_+_)).iter.nextN(9)
// -> [ 101, 102, 103, 101, 102, 103, 101, 102, 103 ]
If you dynamically swap the array, e.g. via a Pfunc you gent Pn+Plazy-style, full play of the previous array before the swap becomes effective:
~myarr = [1, 2];
r = Pforai(100, Pfunc {~myarr}, (_+_)).iter
r.nextN(5) // -> [ 101, 102, 101, 102, 101 ]
// swap array now;
~myarr = [5, 6];
r.nextN(5) // -> [ 102, 105, 106, 105, 106 ]
And a fairly minimalistic example of using Pforai with events as items:
// using with event patterns as items (Event.next does .copy.putAll via .composeEvents)
Pforai((dur: 0.2), [(degree: 1), (note: 10)], (_.next(_))).iter.nextN(3, ())
// -> [ ( 'degree': 1, 'dur': 0.2 ), ( 'note': 10, 'dur': 0.2 ), ( 'degree': 1, 'dur': 0.2 ) ]
// sound (blending) example; array used for the blendFrac with a nil "pulling"
// the Rest() from the outer sequence, which is then "matched" with the 0-blendFrac.
(
~bf = {|self, other, blendFrac=0.5|
self.putAll(self.blend(other, blendFrac, false)) };
Pforai(Pbind(\dur, Pseq([0.5, Rest(1.4)], 3), \degree, Pseries(1, 5)),
0.1*(1..9) ++ [nil, 0],
~bf.(_, (dur: 0.1, degree: 8), _)).trace.play;
)
N.B. the final two letters in the Pforai name stand for “array item”, not something pretentious. I was considering (also) doing a variant that passes the entire array to the merging function, but insofar I’m happy enough with Pforai.