Why is this Pbinop (using +.t) yielding just nils?

I know that there’s a special BinaryOpXStream used by Pbinop when it encounters the .x adverb, but I’ve been wondering why Pbinop fails to produce anything with the regular BinaryOpStream on the (related) .t adverb in the following example:

[0, 0.1] +.t [0, 10, 100] // -> [ [ 0, 10, 100 ], [ 0.1, 10.1, 100.1 ] ]

(Pseries([0, 0.1], 1) +.t [0, 10, 100]).asStream.nextN(2) // -> [ nil, nil ]

// Workaround (internally uses Pcollect instead of Pbinop):
Pseries([0, 0.1]).collect({|a| a +.t [0, 10, 100]}).asStream.nextN(2)
// -> [ [ [ 0, 10, 100 ], [ 0.1, 10.1, 100.1 ] ], [ [ 1, 11, 101 ], [ 1.1, 11.1, 101.1 ] ] ]

Any ideas why does the middle example fails to produce/yield any non-nil output?

Actually, the problem seems to be a bit more general:

[0, 0.1] +.s [0, 10, 100] // -> [ 0, 10.1 ]
(Pseries([0, 0.1], 1) +.s [0, 10, 100]).asStream.nextN(2) // -> [ nil, nil ]

And it’s not that Pbinop doesn’t “see” the adverb, because passing it explicitly also fails the same way…

Pbinop('+', Pseries([0, 0.1], 1), [0, 10, 100], 's').asStream.nextN(2) // -> [ nil, nil ]

Non-adverbed stuff (fairly obviously) works though:

(Pseries([0, 0.1], 1) + [0, 10, 100]).asStream.nextN(2) // or
Pbinop('+', Pseries([0, 0.1], 1), [0, 10, 100]).asStream.nextN(2)
// -> [ [ 0, 10.1, 100 ], [ 1, 11.1, 101 ] ]

Duh, the other adverbs besides ‘.x’ aren’t actually supported in Pbinop

Pbinop : Pattern {
	var <>operator, <>a, <>b, <>adverb;
	*new { arg operator, a, b, adverb;
		^super.newCopyArgs(operator, a, b, adverb)
	}

	asStream {
		if (adverb.isNil) {
			^BinaryOpStream.new(operator, a.asStream, b.asStream);
		};
		if (adverb == 'x') {
			^BinaryOpXStream.new(operator, a.asStream, b.asStream);
		};
		^nil
	}
}

Although the help page for Pbinop doesn’t mention this, the adverbs help page does say

There is currently one adverb defined for streams. This is the cross adverb, ‘x’.

And no, something like this doesn’t work…

Pbinop('+.s', Pseries([0, 0.1], 1), [0, 10, 100]).asStream.nextN(2) // err

The .s adverb is pretty redundant on streams as the behavior of BinaryOpStream is like that anyway

[1, 2] + [1, 2, 5] // -> [ 2, 4, 6 ]
[1, 2] +.s [1, 2, 5] // -> [ 2, 4 ]
(Pseq([1, 2]) + Pseq([1, 2, 5])).asStream.nextN(5) // -> [ 2, 4, nil, nil, nil ]

And since wrapping is not done/supported, I suppose .f which is a variant of that isn’t supported for streams for the same reason… although since [some streams] have a notion of repeating a sub-stream after it returns nil (including BinaryOpXStream which does that for its rightmost argument), I suppose one could define:

BinaryOpStreamW : BinaryOpStream {
	next { arg inval;
		var vala, valb;
		vala = a.next(inval);
		if (vala.isNil, { ^nil });
		valb = b.next(inval);
		if (valb.isNil) {
			b.reset;
			valb = b.next(inval);
			if (valb.isNil) { ^nil };
		};
		^vala.perform(operator, valb);
	}
}

Although that works

BinaryOpStreamW('+', Pseq([1, 2, 5]).asStream, Pseq([1, 2]).asStream).nextN(4) // -> [ 2, 4, 6, nil ]

it’s not terribly useful since it’s easy to do the same by making the appropriate stream wrap/repeat, which doesn’t have any limitations on which of the arguments is allowed to do that…

(Pseq([1, 2, 5]) + Pseq([1, 2], inf)).asStream.nextN(4) // -> [ 2, 4, 6, nil ]

The f suffix equivalent can be done by making the appropriate argument a Pwalk with an appropriate directionPattern. Or a Pseq([...].mirror1, inf) .

I suppose one could implement .t for patterns by pulling all the stream values from the right operand into an array before doing the operation. That would hang the interpreter if the right-hand operand is infinite-length however – which is common for patterns, hence risky.

hjh

1 Like