burrido
An experiment in bringing Haskell's programmable semicolon to JavaScript, using generators.
Installation
npm install --save burrido
Usage
const ArrayMonad = ArrayMonad // -> [3,4,6,8]
The above should look fairly self-explanatory to a Haskell programmer: we are declaring a Monad
instance for arrays, which requires us to define two functions: pure
and bind
. Then we obtain a special function Do
which is a do-notation tailored to that particular monad. We pass a generator function to Do
, within which we gain access to the yield
keyword, allowing us to "unwrap" monadic values and release their effects.
In fact this is a bit more versatile than Haskell's do-notation in a couple of interesting ways:
- Haskell's
Monad
is a type class, which means that there can only be one way in which a given type constructor is considered a monad within a given scope. But some type constructors can be considered monadic in more than one way (e.g.Either
). By contrast, here you can create as manyMonad
definitions as you want for a particular type (constructor), and each just has its own specialDo
function. - While
const foo = bar
is comparable to
foo <- bar
in do-notation, one can also create compound yield
expressions which have no direct analogue in Haskell. For example,
const foo = bar
would have to be written as
foo' <- barfoo <- foo'
in do-notation. In the context of Do
blocks, yield
serves a similar purpose to the !
operator in both Idris and the Effectful library for Scala.
RxJS
An example usingRxJS Observable
s form a monad in several different ways:
const just: pure = Observable const Do: doConcat = const Do: doMerge = const Do: doLatest =
It's insructive to see what happens when you apply these different do-notations to the same generator block:
const from = Observable const block = { // for each x in [1,2,3]... const x = // wait 1 second // then return the value return x} // Prints 1, 2, and 3 separated by 1 second intervals// Waits 1 second and then prints 1, 2, 3 all at once// Waits 1 second and then prints 3
This should make sense if you think about the semantics of each of these different methods of "flattening" nested Observable
s. Each do*
flavor applies its own semantics to the provided block, but they all return Observable
s, so we can freely combine them:
RxJS has a function spawn
which allows you to use this kind of syntax with Observable
s, but it only works properly with single-valued streams (essentially Promises), whereas burrido allows manipulating streams of multiple values, using multiple different semantics.