你能给我一个如何使用 Ramda lift 的例子吗?

Can you give me an example of how to use Ramda lift?

我正在阅读 ramda 文档

const madd3 = R.lift((a, b, c) => a + b + c);

madd3([1,2,3], [1,2,3], [1]); //=> [3, 4, 5, 4, 5, 6, 5, 6, 7]

看起来是个很有用的功能。我看不出它有什么用例。

谢谢

此函数只能接受数字:

const add3 = (a, b, c) => a + b + c;
add3(1, 2, 3); //=> 6

但是,如果这些数字每个都包含在一个函子中呢? (即包含值的事物;下例中的数组)

add3([1], [2], [3]); //=> "123"

这显然不是我们想要的。 您可以 "lift" 函数,以便它可以 "extract" 每个 parameter/functor:

的值
const add3Lifted = lift(add3);
add3Lifted([1], [2], [3]); //=> [6]

数组显然可以保存多个值,并结合知道如何提取每个函子值的提升函数,您现在可以这样做:

add3Lifted([1, 10], [2, 20], [3, 30]);
//=> [6, 33, 24, 51, 15, 42, 33, 60]

如果您这样做,这基本上就是您所得到的:

[
  add3(1, 2, 3),    // 6
  add3(1, 2, 30),   // 33
  add3(1, 20, 3),   // 24
  add3(1, 20, 30),  // 51
  add3(10, 2, 3),   // 15
  add3(10, 2, 30),  // 42
  add3(10, 20, 3),  // 33
  add3(10, 20, 30)  // 60
]

请注意,每个数组的长度不必相同:

add3Lifted([1, 10], [2], [3]);
//=> [6, 15]

所以回答你的问题:如果你打算 运行 具有不同值集的函数,提升该函数可能是一个有用的考虑因素:

const results = [add3(1, 2, 3), add3(10, 2, 3)];

等同于:

const results = add3Lifted([1, 10], [2], [3]);

基本上它采用笛卡尔积并将函数应用于每个数组。

const
    cartesian = (a, b) => a.reduce((r, v) => r.concat(b.map(w => [].concat(v, w))), []),
    fn = ([a, b, c]) => a + b + c,
    result = [[1, 2, 3], [1, 2, 3], [1]]
        .reduce(cartesian)
        .map(fn);

console.log(result); // [3, 4, 5, 4, 5, 6, 5, 6, 7]

当前答案中没有提到的是,像 R.lift 这样的函数不仅适用于数组,而且适用于任何表现良好的 Apply1数据类型。

例如,我们可以重用 R.lift:

生成的相同函数
const lifted = lift((a, b, c) => a + b - c)

应用类型为函数:

lifted(a => a * a,
       b => b + 5,
       c => c * 3)(4) //=> 13

可选类型(调度到 .ap):

const Just = val => ({
  map: f    => Just(f(val)),
  ap: other => other.map(otherVal => val(otherVal)),
  getOr: _  => val
})

const Nothing = {
  map: f    => Nothing,
  ap: other => Nothing,
  getOr: x  => x
}

lifted(Just(4), Just(6), Just(8)).getOr(NaN) //=> 2

lifted(Just(4), Nothing, Just(8)).getOr(NaN) //=> NaN

异步类型(调度到 .ap):

const Asynchronous = fn => ({
  run: fn,
  map: f    => Asynchronous(g => fn(a => g(f(a)))),
  ap: other => Asynchronous(fb => fn(f => other.run(a => fb(f(a)))))
})

const delay = (n, x) => Asynchronous(then => void(setTimeout(then, n, x)))

lifted(delay(2000, 4), delay(1000, 6), delay(500, 8)).run(console.log)

...还有更多。这里的要点是,任何可以支持任何 Apply 类型所期望的接口和法则的东西都可以使用通用函数,例如 R.lift.

1.幻想世界规范中列出的 ap 的参数顺序与 Ramda 中名称分配支持的顺序相反,但在使用 fantasy-land/ap 命名空间方法时仍然受支持。

函数式编程是一个很长的数学主题,尤其是 the part dealing with monads and cathegory theory in general. But it is worth to take a look at it, here is a funny introduction with pictures

简而言之,lift 是一个函数,它将采用 n 个参数 函数并将产生一个采用 n wrapped-values 并产生另一个结果 wrapped-value。采用单参数函数的提升由以下类型签名定义

 // name :: f is a wrp-value => function -> wrp-value -> wrp-value
 liftA :: Applicative f   => (a -> b) -> f a -> f b

等等...包装值?

我简单介绍一下Haskell,只是为了说明这一点。在haskell中,wrapped-value的一个简单例子是Maybe,Maybe可以是wrapped-value或者nothing,这也是wrapped-value。以下示例将函数应用于包含值的 Maybe 和空的 Maybe。

> liftA (+ 8) (Just 8)
Just 16
> liftA (+ 8) Nothing
Nothing

列表也是一个包装值,我们可以对其应用函数。在第二种情况下,liftA2 将双参数函数应用于两个列表。

> liftA (+ 8) [1,2,3]
[9,10,11]
> liftA2 (*) [1..3] [1..3]
[1,2,3,2,4,6,3,6,9]

这个 wrapped-value 是一个 Applicative Functor,所以从现在开始我将把它称为 Applicative。

也许也许你从这点开始失去兴趣... 但是我们之前有人在这个话题上迷失了方向,

让我们看看他看到了什么...

...

他看到了幻境

In fantasy-land, an object implements Apply spec when it has an ap method defined (that object also has to implement Functor spec by defining a map method).

  • Fantasy-land 是函数式编程规范的奇特名称 javascript。 Ramda 紧随其后。
  • Apply是我们的Applicative,一个 Functor 也实现了 ap 方法。
  • A Functor,是具有 map 方法的东西。

所以,等等...javascript 中的数组有一个映射...

[1,2,3].map((a)=>a+1) \=> [ 2, 3, 4 ]

那么 Array 是一个 Functor,map 将一个函数应用于它的所有值,返回另一个具有相同数量的值的 Functor。

但是 ap 有什么作用?

ap applies a list of functions to a list of values.

Dispatches to the ap method of the second argument, if present. Also treats curried functions as applicatives.

让我们试着用它做点什么。

const res = R.ap(
  [
    (a)=>(-1*a), 
    (a)=>((a>1?'greater than 1':'a low value'))
  ], 
  [1,2,3]); //=>  [ -1, -2, -3, "a low value", "greater than 1", "greater than 1" ]

console.log(res);
<script src="https://cdn.jsdelivr.net/npm/ramda@0.26.1/dist/ramda.min.js"></script>

ap 方法采用函数数组(或其他一些 Applicative)并将其应用于值的 Applicative 以生成另一个 Applicative 扁平化。

方法的签名解释了这一点

[a → b] → [a] → [b]
Apply f => f (a → b) → f a → f b

最后,电梯有什么作用?

Lift 采用带有 n 个参数的函数,并生成另一个采用 n Aplicatives 并生成扁平的 Aplicative 结果。

在这种情况下,我们的 Applicative 是数组。

const add2 = (a, b) => a + b;
const madd2 = R.lift(add2);

const res = madd2([1,2,3], [2,3,4]); 
//=> [3, 4, 5, 4, 5, 6, 5, 6, 7]

console.log(res);
// Equivalent to lift using ap
const result2 = R.ap(R.ap(
  [R.curry(add2)], [1, 2, 3]),
  [2, 3, 4]
  );
//=> [3, 4, 5, 4, 5, 6, 5, 6, 7]
console.log(result2);
<script src="https://cdn.jsdelivr.net/npm/ramda@0.26.1/dist/ramda.min.js"></script>

这些 wrappers(Applicatives、Functors、Monads)很有趣,因为它们可以是实现这些方法的任何东西。在haskell中,this用于包装不安全的操作,如input/output。它也可以是错误包装器或树,甚至是任何数据结构。