Delay two Pbind

Actually, one steam can be infinite, but it should be left-hand one for the expected results.

(0..2) +.x [0, 10] // -> [ 0, 10, 1, 11, 2, 12 ]

(Pseries(0, 1) +.x Pseq([0, 10])).asStream.nextN(6)
 // -> [ 0, 10, 1, 11, 2, 12 ]

(Pseq([0, 10]) +.x Pseries(0, 1)).asStream.nextN(6) 
// -> [ 0, 1, 2, 3, 4, 5 ]

The even more confusing behavior is when you apply it to a combo of an array and an infinite stream

(Pseries(0, 1) +.x [0, 10]).asStream.nextN(3)
 // -> [ [ 0, 10 ], [ 0, 10 ], [ 0, 10 ] ]

([0, 10] +.x Pseries(0, 1)).asStream.nextN(3)
 // -> [ [ 0, 10 ], [ 1, 11 ], [ 2, 12 ] ]

You can [P]flatten the latter for the desired result…

([0, 10] +.x Pseries(0, 1)).flatten.asStream.nextN(6)
// -> [ 0, 10, 1, 11, 2, 12 ]

but there’s the confusing usability issue that a finite Pseq needs to go “on the right”, but a (finite, obvious) array needs to “go on the left” . Also, in the latter case, since you have to re-flatten, it’s no different than using a plain ‘+’

([0, 10] + Pseries(0, 1)).flatten.asStream.nextN(6)
// -> [ 0, 10, 1, 11, 2, 12 ]

There’s a special implementation for cross adverbs in BinaryOpXStream: when the right-hand side stream yields nil, it gets reset. (Cross adverbs are detected by Pbinop.asStream which uses BinaryOpXStream for the latter instead of BinaryOpStream.) That implementation makes it a non-commutative operation, but then so is its finite counterpart on arrays… BinaryOpXStream also works more like a flatMap in Java etc. As “proof”:

(0..2) +.x [0, 10] // same result as next line
(0..2).collect({|a| a +.x [0, 10]}).flatten // -> [ 0, 10, 1, 11, 2, 12 ]

// so, "patternizing" that idea
Pflatten(1, Pcollect({|a| a +.x [0, 10]}, Pseries(0, 1))).asStream.nextN(6) // or 
Pcollect({|a| a +.x [0, 10]}, Pseries(0, 1)).flatten.asStream.nextN(6) // or
Pseries(0, 1).collect({|a| a +.x [0, 10]}).flatten.asStream.nextN(6)
// -> [ 0, 10, 1, 11, 2, 12 ]

Because of the wrapping behavior of adding a single scalar to an array, that’s the same as

(0..2).collect({|a| a + [0, 10]}).flatten //-> [ 0, 10, 1, 11, 2, 12 ]; also
Pflatten(1, Pcollect({|a| a + [0, 10]}, Pseries(0, 1))).asStream.nextN(6) // etc.

But in the more general case where Pseries returned an array, there’s a difference where you flatten, or equivalently commuting the arguments of +.x (on an array), i.e row vs column “major order”, e.g.

[0, 10] +.x [0, 0.1] // -> [ 0, 0.1, 10, 10.1 ]
[0, 0.1] +.x [0, 10] // -> [ 0, 10, 0.1, 10.1 ]

(Pseries([0, 0.1], 1) +.x Pseq([0, 10])).flatten.asStream.nextN(12) // same as
Pcollect({|a| [0, 10] +.x a}, Pseries([0, 0.1], 1)).flatten.asStream.nextN(12)
// -> [ 0, 0.1, 10, 10.1, 1, 1.1, 11, 11.1, 2, 2.1, 12, 12.1 ]

(Pseries([0, 0.1], 1).flatten +.x Pseq([0, 10])).asStream.nextN(12) // same as
Pcollect({|a| a +.x [0, 10]}, Pseries([0, 0.1], 1)).flatten.asStream.nextN(12)
// -> [ 0, 10, 0.1, 10.1, 1, 11, 1.1, 11.1, 2, 12, 2.1, 12.1 ]

N.B., *.x is mostly called an “outer” rather than “cross” product/operation is most non-SC contexts. Actually, “Kronecker product” is probably more appropriate since the result of .x is flattened. The .t adverb in SC gives the true (unflattened) outer product.