Ramda 链的使用

Ramda chain usage

来自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]); //=> [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];

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));
}));

其中的重要部分是:

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 是某种配置参数时,fm 都相同。那么a = m(x)就是m对那个参数返回的值,g = f(_)(x)就是f对同一个参数返回的函数。将 x 视为某种同时进入 mf 的环境。那么上面的定义可以分解为:

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同时放到fm中组合结果:

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

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),第二次调用被委托给某个返回错误的内部函数。