From the documentation:
var duplicate = n => [n, n];
R.chain(duplicate, [1, 2, 3]); //=> [1, 1, 2, 2, 3, 3]
R.chain(R.append, R.head)([1, 2, 3]); //=&g
It is probably easier to first look at the abstract version of the R.chain
for functions and to distinguish between functions m: r -> a
treated as monads and functions f: a -> r -> b
treated as Kleisli arrows, as mentioned in this answer.
Then R.chain
is defined as:
// (a -> r -> b, r -> a) -> r -> b
R.chain = (f, m) => x => f(m(x))(x)
This can be useful, when x
is some kind of configuration parameter, the same for both f
and m
. Then a = m(x)
is the value returned by m
for that parameter, and g = f(_)(x)
is the function returned by f
for the same parameter. Think of x
as some kind of environment that goes into both m
and f
. Then the above definition can be broken down as:
R.chain = (f, m) => x => {
const a = m(x)
, g = a => f(a)(x)
return g(a)
}
In comparison, the R.map
for functions corresponds to the case when f
is independent of that parameter x
:
// (a -> b, r -> a) -> r -> b
R.map = (f, m) => x => f(m(x))
which, of course, is the usual function composition from the outside.
Another conceptual approach to define chain
(aka bind
in Haskell) is to apply map
(aka fmap
) followed by flatten
(aka join
).
R.chain = (f, m) => {
// m1: r -> r -> b
const m1 = R.map(f, m)
// flattening to fm1: r -> b
const fm1 = x => m1(x)(x)
return fm1
}
Now for m = x => R.head(x)
and f = a => x => R.append(a)(x)
, R.chain(f, m)
is equivalent to putting the parameter x
into both f
and m
and composing the results:
x => R.append(R.head(x))(x)
which gives the expected result.
Warning. Note that the R.append
function here must be curried as it represents the Kleisli arrow a -> r -> b
. Incidentally, Ramda
provides the same named function also as uncurried, but it is the curried one used here. To see this, let us get our custom uncurried R.append
:
const appendCustom = (a, b) => R.append(a, b)
Then note how Ramda's REPL throws an error and gives unexpected result:
// Define our own uncurried append const appendCustom = (a, b) => R.append(a, b) R.chain(appendCustom, R.head)([1, 2]); // => t(...) is not a function
http://ramdajs.com/repl/?v=0.25.0#?%2F%2F%20Define%20our%20own%20uncurried%20append%0Aconst%20appendCustom%20%3D%20%28a%2C%20b%29%20%3D%3E%20R.append%28a%2C%20b%29%0AR.chain%28appendCustom%2C%20R.head%29%28%5B1%2C%202%5D%29%3B%0A
What really happens here, appendCustom
is executed in its curried form: appendCustom(a)(b)
, with the second call is delegated to some internal function returning the error.
The second example is showing how R.chain
can be used things other than arrays, such as functions (or anything implementing the Fantasy Land chain spec).
If the concept of mapping and then concatenating an array is familiar to you, you can think of mapping a function over another function as plain function composition. The concatenating part will require further explanation.
R.chain
declares its signature as:
Chain m => (a → m b) → m a → m b
For arrays, we can swap the m
with []
to get:
(a → [b]) → [a] → [b]
For functions that receive some argument r
, it becomes:
(a → r → b) → (r → a) → (r → b)
So with only the knowledge of those types available, the only way to produce the final r → b
function is to do the following:
r
to the second function to produce an a
a
and the original r
to the first function to produce the resulting b
Or in code:
// specialised to functions
const chain = (firstFn, secondFn) =>
x => firstFn(secondFn(x), x)
Swapping in the functions from the example, you can see it becomes:
x => R.append(R.head(x), x)
If you are familiar with R.converge then this is effectively:
R.converge(firstFn, [secondFn, R.identity])
Hope this helps
let R = require('ramda')
// using vanillajs
let append = (arr1) => (arr2) => arr2.concat(arr1)
let double = (arr1) => arr1.map( x => 2*x )
let chain = (f, g) => arr => {
let yarr = g(arr)
return f(yarr)(arr)
}
console.log(chain(
append,
double
)([10, 15, 20]))
//using Ramda
console.log(R.chain(append, double)([10, 15, 20]))
chain
is (approximately) defined as (for functions): (fn, monad) => x => fn(monad(x))(x)
Therefore, we can transform R.chain(R.append, R.head)([1, 2, 3]);
like so:
R.chain(R.append, R.head)([1, 2, 3]);
R.append(R.head([1, 2, 3]), [1, 2, 3]); // This is the important step
R.append(1, [1, 2, 3]);
[1, 2, 3, 1];
The actual source:
var chain = _curry2(_dispatchable(['fantasy-land/chain', 'chain'], _xchain, function chain(fn, monad) {
if (typeof monad === 'function') {
return function(x) { return fn(monad(x))(x); };
}
return _makeFlat(false)(map(fn, monad));
}));
The important part of that is this:
function chain(fn, monad) {
if (typeof monad === 'function') {
return function(x) { return fn(monad(x))(x); };
}
return _makeFlat(false)(map(fn, monad));
}
According to the comment in the code, "_makeFlat
is a helper function that returns a one-level or fully recursive function based on the flag passed in."
_makeFlat(false)
seems to be equivalent to unnest
and _makeFlat(true)
seems to be equivalent to flatten
.