Ramda 链的使用
Ramda chain usage
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 的?有人可以为第二个示例提供逐步解释吗?
我熟悉 compose 和 currying。
谢谢
第二个例子展示了 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])
希望对您有所帮助
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
被(大约)定义为(对于函数):(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
是一个辅助函数,returns 一个基于传入标志的单级或完全递归函数。”
_makeFlat(false)
似乎等同于 unnest
而 _makeFlat(true)
似乎等同于 flatten
.
首先查看函数的抽象版本 R.chain
并区分被视为单子的函数 m: r -> a
和被视为 Kleisli 箭头的函数 f: a -> r -> b
可能更容易,如 .
中所述
则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 是如何抛出错误并给出意外结果的:
// 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
这里真正发生了什么,appendCustom
以柯里化形式执行:appendCustom(a)(b)
,第二次调用被委托给某个返回错误的内部函数。
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 的?有人可以为第二个示例提供逐步解释吗?
我熟悉 compose 和 currying。
谢谢
第二个例子展示了 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])
希望对您有所帮助
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
被(大约)定义为(对于函数):(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
是一个辅助函数,returns 一个基于传入标志的单级或完全递归函数。”
_makeFlat(false)
似乎等同于 unnest
而 _makeFlat(true)
似乎等同于 flatten
.
首先查看函数的抽象版本 R.chain
并区分被视为单子的函数 m: r -> a
和被视为 Kleisli 箭头的函数 f: a -> r -> b
可能更容易,如
则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 是如何抛出错误并给出意外结果的:
// 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
这里真正发生了什么,appendCustom
以柯里化形式执行:appendCustom(a)(b)
,第二次调用被委托给某个返回错误的内部函数。