首先查看函数的抽象版本R.chain 并区分被视为单子的函数m: r -> a 和被视为Kleisli 箭头的函数可能更容易,如this answer 中所述。
那么R.chain定义为:
// (a -> r -> b, r -> a) -> r -> b
R.chain = (f, m) => x => f(m(x))(x)
这很有用,当x 是某种配置参数时,f 和m 都一样。那么a = m(x)是m为那个参数返回的值,g = f(_)(x)是f为同一个参数返回的函数。将x 视为某种环境,同时进入m 和f。那么上面的定义可以分解为:
R.chain = (f, m) => x => {
const a = m(x)
, g = a => f(a)(x)
return g(a)
}
相比之下,函数的R.map 对应于f 独立于该参数x 的情况:
// (a -> b, r -> a) -> r -> b
R.map = (f, m) => x => f(m(x))
当然,这是从外部看的通常的函数组合。
定义chain(在Haskell 中又名bind)的另一种概念方法是应用map(又名fmap),然后是flatten(又名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
}
现在对于m = x => R.head(x) 和f = a => x => R.append(a)(x),R.chain(f, m) 相当于将参数x 放入f 和m 并组合结果:
x => R.append(R.head(x))(x)
这给出了预期的结果。
警告。注意这里的R.append 函数必须是柯里化的,因为它代表Kleisli 箭头a -> r -> b。顺便说一句,Ramda 提供了与 uncurried 相同的命名函数,但它是这里使用的 curried 函数。要看到这一点,让我们获取自定义的 uncurried R.append:
const appendCustom = (a, b) => R.append(a, b)
然后注意 Ramda 的 REPL 是如何抛出错误并给出意外结果的:
// 定义我们自己的 uncurried append
const appendCustom = (a, b) => R.append(a, b)
R.chain(appendCustom, R.head)([1, 2]);
// => t(...) 不是函数
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
这里真正发生了什么,appendCustom 以其柯里化形式执行:appendCustom(a)(b),第二次调用被委托给某个返回错误的内部函数。