Name | # | Haskell | Ramda | Sanctuary | Signature |
---|---|---|---|---|---|
identity | I | id |
identity |
I |
a → a |
constant | K | const |
always |
K |
a → b → a |
apply | A | ($) |
call |
I ¹ |
(a → b) → a → b |
thrush | T | (&) |
applyTo |
T |
a → (a → b) → b |
duplication | W | join ² |
unnest ² |
join ² |
(a → a → b) → a → b |
flip | C | flip |
flip |
flip |
(a → b → c) → b → a → c |
compose | B | (.) , fmap ² |
map ² |
compose , map ² |
(b → c) → (a → b) → a → c |
substitution | S | (<*>) ² |
ap ² |
ap ² |
(a → b → c) → (a → b) → a → c |
chain | S_³ | (=<<) ² |
chain ² |
chain ² |
(a → b → c) → (b → a) → b → c |
converge | S2³ | apply2way , liftA2 ², liftM2 ² |
lift2 ² |
(b → c → d) → (a → b) → (a → c) → a → d |
|
psi | P | on |
on |
on |
(b → b → c) → (a → b) → a → a → c |
fix-point4 | Y | fix |
(a → a) → a |
¹) The A-combinator can be implemented as an alias of the I-combinator. Its implementation in Haskell exists because the infix nature gives it some utility. Its implementation in Ramda exists because it is overloaded with additional functionality.
²) Algebras like ap
have different implementations for different types.
They work like Function combinators only for Function inputs.
³) I could not find a consistent name for these combinators, but they are common enough in the JavaScript ecosystem to justify their inclusion. I named them myself in order to refer to their implementation.
4) In JavaScript and other non-lazy languages, it is impossible to
implement the Y-combinator. Instead a variant known as the applicative or
strict fix-point combinator is implemented. This variant is sometimes
rererred to as the Z-combinator. The implementation found in combinators.js
is the strictly evaluated "Z" combinator, which needs the extra wrapper
around g (g)
on the right hand side.
@kirilloid
This reply is years later, so forgive me if you're already way past this point in understanding. But:
join
collapses monadic contexts, not containers.The
join
function fuses together a single nested layer of monadic contexts. What is a monadic context for (A)? Sometimes, it's a "container"-like structure, e.g. list of (A) or either X or (A). Sometimes, however, it's not really a container at all, e.g. function with input type X and output type (A) or an IO program that would produce (A). (The ultimate non-container monad Proxy for (A), defined asdata Proxy a = Proxy
, which has a phantom type param and thus never holds ana
-type value.)The specific monad cited in this combinators article, corresponding to the
W
combinator, is the "Reader" monad – that is, the monad of functions which take a specified input type (and may have different output types).This type is usually cited in Haskell as
((->) r)
for annoying syntactic reasons. It would be more intuitive asr -> _
but that syntax isn't allowed in class instance definitions, so we have a sectioned operator(->)
followed by its first input type (r
), yielding the type signature of a "function which takes typer
as its input".Anyway, since the reader monad is a function with a specific input type, nested reader monads would be something like "a function of input type
r
, and output type (a function with input typer
, and output typex
). Or, to use type signatures:Then, it becomes much clearer (at least, in terms of type signatures) how
join
for the reader monad collapses theR ->
monads into a singleR ->
:Remember, the function arrow is right-associative, so
(i -> (i -> a))
is the same asi -> i -> a
:And we can again use the right-associativity of
->
to simplify one step further:So there we go,
join
in Haskell is the same as theW
combinator, when we are talking about the Reader monad. (Obviouslyjoin
will not be the same asW
whenjoin
is specialized to any other monad, e.g.List
orMaybe
orIO
. That's what the footnote was saying.) The thing we are collapsing, the monadic context, is the input side of a function arrow:r ->
(written in Haskell typeclass instance sigs as((->) r)
).You may want to know, what on earth do you use the reader monad for? This is out of scope, but… the short answer is that functions which all take the same input-type can be chained together so they all receive a single value as that input. That value then becomes a dynamic / parameterized shared readable constant – a configuration value which each function can read. So in practice, the reader monad is used in Haskell to make it easy to thread read-only config data through an application.
Properly explaining and demonstrating the reader monad in practice is a whole topic unto itself, so I recommend anyone interested consult tutorials/articles/docs/books/videos on the subject. I just wanted to focus on the following major points:
W
combinator is the same asjoin
for the reader monad (but notjoin
for other monads)r
" (for somer
type variable)