tl;dr node’s streams are horrible and I hate them.
EDIT: save yourself the trouble of using them, I’ve written Pirandello
I’ve been trying to shoehorn some algebra and category theory into node.js’s streams. I figured I’d see how much of a fantasy land I could make them think they were in.
Readable streams are pretty obviously a Semigroup and Monoid. Semigroup because you can concatenate streams just by switching over to the second stream when the first is done. Monoid because a stream that just ends can be stuck anywhere and not change anything. So:
(more on Readable.of later)
Well, it’s not pretty. What does it do? It tries to read from the first stream. If it can, it pushes to the output stream. Once it can’t, it switches to the second stream and says “hey, try that again maybe?”. When it can’t from the second stream, it ends. Sounds an awful lot like it’s concatenating the contents of the streams, which is neat.
What’s left? Functor. Applicative. Chain. Monad.
It’s definitely a Functor. We can map over the data chunks.
Oh, but hold on. We can derive Functor from Monad. A Monad is an Applicative and a Chain. An Applicative needs of and ap. But ap can derive from Monad, too.
Then all we need is Readable.of and Readable::chain and we’ve got Functor, Monad, and Applicative for free. Not bad.
So, Readable.of just creates a Readable stream from a bare value. Readables work over Strings and Buffers, which can both be .sliced.
Now we see how Readable.empty works: it pushes the empty string, then because we’ve tried to read more than zero bytes, pushes null and ends the stream.
How about Chain? Well, the type signature of chain for Readable would be
But now we get implementations of ap and map for free:
Nice. Wait. What does Applicative even mean for a stream?
As you can probably tell, I am fed up to the back teeth of _read: (size)-> and @push null. Node streams are a stupid API over a ridiculous abstraction. I’ve tried to make them nicer so you don’t have to.