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]); //=> [1, 2, 3, 1]
第一个例子非常简单,它将duplicate()应用于数组中的每个元素并连接结果。但我无法理解第二个例子。究竟是如何将R.append + R.head映射到数组上的呢?有人可以为第二个例子提供一步一步的解释吗?
我熟悉构图和曲目。
由于
答案 0 :(得分:12)
第二个示例显示R.chain
如何使用除数组之外的其他内容,例如函数(或实现Fantasy Land chain规范的任何内容)。
如果您熟悉映射和连接数组的概念,您可以考虑将函数映射到另一个函数作为普通函数组合。连接部分需要进一步解释。
R.chain
声明其签名为:
Chain m => (a → m b) → m a → m b
对于数组,我们可以将m
与[]
交换为:
(a → [b]) → [a] → [b]
对于接收某个参数r
的函数,它变为:
(a → r → b) → (r → a) → (r → b)
因此,只有可用的那些类型的知识,产生最终r → b
函数的唯一方法是执行以下操作:
r
传递给第二个函数以生成a
a
和原始r
同时应用于第一个函数,以生成结果b
或代码:
// specialised to functions
const chain = (firstFn, secondFn) =>
x => firstFn(secondFn(x), x)
交换示例中的函数,您可以看到它变为:
x => R.append(R.head(x), x)
如果您熟悉R.converge
,那么这是有效的:
R.converge(firstFn, [secondFn, R.identity])
答案 1 :(得分:0)
可能更容易首先查看函数的R.chain
的抽象版本,并区分被视为monad的函数m: r -> a
和被视为Kleisli箭头的函数f: a -> r -> b
,如上所述在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))
当然,这是来自外部的通常功能组合。
在Haskell中定义chain
(又名bind
)的另一种概念方法是应用map
(又名fmap
)后跟flatten
(aka {{ 1}})。
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)
功能必须是curry,因为它代表Kleisli箭头R.append
。顺便提一下,a -> r -> b
提供了相同的命名功能,也就是未经证实的功能,但它是这里使用的curry。要查看此内容,请让我们自定义Ramda
:
R.append
然后注意Ramda的REPL如何抛出错误并给出意想不到的结果:
// 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
这里真正发生了什么,const appendCustom = (a, b) => R.append(a, b)
以curry形式执行:appendCustom
,第二个调用被委托给一些返回错误的内部函数。
答案 2 :(得分:-1)
希望这有帮助
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]))
答案 3 :(得分:-1)
chain
(大约)定义为(对于函数):(fn, monad) => x => fn(monad(x))(x)
因此,我们可以像这样转换R.chain(R.append, R.head)([1, 2, 3]);
:
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];
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));
}));
其中重要的部分是:
function chain(fn, monad) {
if (typeof monad === 'function') {
return function(x) { return fn(monad(x))(x); };
}
return _makeFlat(false)(map(fn, monad));
}
根据the comment in the code,“_makeFlat
是一个辅助函数,它根据传入的标志返回一个或一个完全递归的函数。”
_makeFlat(false)
似乎等同于unnest
,_makeFlat(true)
似乎等同于flatten
。