Javascript Can';“别把我的头缠在周围”;“电梯”;在Ramda.js中

Javascript Can';“别把我的头缠在周围”;“电梯”;在Ramda.js中,javascript,functional-programming,ramda.js,lifting,Javascript,Functional Programming,Ramda.js,Lifting,查看Ramda.js的源代码,特别是“lift”函数 下面是给定的示例: var madd3 = R.lift(R.curry((a, b, c) => a + b + c)); madd3([1,2,3], [1,2,3], [1]); //=> [3, 4, 5, 4, 5, 6, 5, 6, 7] 因此结果的第一个数字很简单,a、b和c都是每个数组的第一个元素。第二个对我来说并不容易理解。参数是每个数组的第二个值(2,2,未定义),还是第一个数组的第二个值以及第二个和第

查看Ramda.js的源代码,特别是“lift”函数

下面是给定的示例:

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

madd3([1,2,3], [1,2,3], [1]); //=> [3, 4, 5, 4, 5, 6, 5, 6, 7]
因此结果的第一个数字很简单,
a
b
c
都是每个数组的第一个元素。第二个对我来说并不容易理解。参数是每个数组的第二个值(2,2,未定义),还是第一个数组的第二个值以及第二个和第三个数组的第一个值


即使不考虑这里发生的事情的顺序,我也看不出它的价值。如果我在没有先提升它的情况下执行它,我将以数组
concat
作为字符串启用而结束。这看起来有点像
flatMap
,但我似乎无法遵循其背后的逻辑

lift
/
liftN
将普通函数“提升”到应用程序上下文中

// lift1 :: (a -> b) -> f a -> f b
// lift1 :: (a -> b) -> [a] -> [b]
function lift1(fn) {
    return function(a_x) {
        return R.ap([fn], a_x);
    }
}
现在(
f(a->b)->fa->fb
)的类型也不容易理解,但是列表示例应该可以理解

这里有趣的是,您传入一个列表,然后返回一个列表,因此只要第一个列表中的函数具有正确的类型,您就可以重复应用它:

// lift2 :: (a -> b -> c) -> f a -> f b -> f c
// lift2 :: (a -> b -> c) -> [a] -> [b] -> [c]
function lift2(fn) {
    return function(a_x, a_y) {
        return R.ap(R.ap([fn], a_x), a_y);
    }
}

而您在示例中隐式使用的
lift3
,也同样适用于
ap(ap(ap([fn],a_x),a_y),a_z)

Bergi的答案非常好。但另一种思考方法是更具体一点。Ramda确实需要在其文档中包含一个非列表示例,因为列表并不能真正捕获这一点

让我们看一个简单的函数:

var add3 = (a, b, c) => a + b + c;
这对三个数字起作用。但是如果你的容器里有数字呢?也许我们有s。我们不能简单地将它们相加:

const Just = Maybe.Just, Nothing = Maybe.Nothing;
add3(Just(10), Just(15), Just(17)); //=> ERROR!
(好的,这是Javascript,它实际上不会在这里抛出错误,只是尝试连接它不应该连接的东西…但它肯定不会做你想要的!)

如果我们能将这种功能提升到容器的水平,我们的生活就会更轻松。Bergi指出的
lift3
在Ramda中通过
liftN(3,fn)
和一个gloss
lift(fn)
实现,该gloss仅使用所提供函数的算术性。因此,我们可以:

const madd3 = R.lift(add3);
madd3(Just(10), Just(15), Just(17)); //=> Just(42)
madd3(Just(10), Nothing(), Just(17)); //=> Nothing()
但是这个函数不知道关于容器的任何具体信息,只知道它们实现了
ap
。Ramda为列表实现
ap
,方法类似于将函数应用于列表叉积中的元组,因此我们也可以这样做:

madd3([100, 200], [30, 40], [5, 6, 7]);
//=> [135, 136, 137, 145, 146, 147, 235, 236, 237, 245, 246, 247]

这就是我对电梯的看法。它接受一个在某些值级别上工作的函数,并将其提升到在这些值的容器级别上工作的函数。

多亏了Scott Sauyet和Bergi的回答,我才对它了如指掌。在这样做的过程中,我觉得仍然有一些箍环需要跳起来把所有的部件组合在一起。我将记录我在旅途中遇到的一些问题,希望能对一些人有所帮助

下面是我们试图理解的
R.lift
示例:

var 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]
对我来说,在理解它之前,有三个问题需要回答

  • 的规范(我将其称为
    Apply
    )以及
    Apply#ap
    的作用
  • Ramda的实现以及与
    Apply
    spec的关系
  • 咖喱在其中扮演什么角色
  • 了解
    应用
    规范 在FantasyLand中,当对象定义了
    ap
    方法时,该对象实现spec(该对象还必须通过定义
    map
    方法来实现spec)

    ap
    方法具有以下签名:

    ap :: Apply f => f a ~> f (a -> b) -> f b
    
    在:

    • =>
      声明类型约束,因此上面签名中的
      f
      指类型
      Apply
    • ~>
      声明方法声明,因此
      ap
      应该是在
      Apply
      上声明的函数,它围绕着一个值,我们称之为
      a
      (我们将在下面的示例中看到,一些
      ap
      与此签名不一致,但想法是相同的)
    假设我们有两个对象
    v
    u
    v=fa;u=f(a->b)
    ),因此这个表达式是有效的
    v.ap(u)
    ,需要注意的是:

    • v
      u
      都实现
      应用
      v
      持有一个值,
      u
      持有一个函数,但它们具有与
      Apply
      相同的“接口”(这将有助于理解下面关于
      R.ap
      数组的部分)
    • a
      和函数
      a->b
      不知道
      Apply
      ,函数只是转换值
      a
      。是
      Apply
      将值和函数放在容器中,是
      ap
      将它们提取出来,对值调用函数并将它们放回容器中
    了解Ramda的
    R.ap
    签名有两种情况:

  • Apply f=>f(a→ (b)→ f a→ f b
    :这与上一节中的
    Apply#ap
    签名非常相似,区别在于调用
    ap
    的方式(
    Apply#ap
    R.ap
    )以及参数顺序
  • [a]→ b]→ [a]→ [b] 
    :如果我们将
    Apply f
    替换为
    Array
    ,这是一个版本,还记得上一节中的值和函数必须包装在同一个容器中吗?这就是为什么将
    R.ap
    Array
    s一起使用时,第一个参数是函数列表,即使您只想应用一个函数,也要将其放入数组中
  • 让我们看一个例子,我使用的是
    Maybe
    from,它实现了
    Apply
    ,这里的一个不一致之处是它的签名是:
    ap::Apply f=>f(a->b)~>fa->
    
    const R = require('ramda');
    const Maybe = require('ramda-fantasy').Maybe;
    
    const a = Maybe.of(2);
    const plus3 = Maybe.of(x => x + 3);
    const b = plus3.ap(a);  // invoke Apply#ap
    const b2 = R.ap(plus3, a);  // invoke R.ap
    
    console.log(b);  // Just { value: 5 }
    console.log(b2);  // Just { value: 5 }
    
    const R = require('ramda');
    
    const madd3 = (x, y, z) => x + y + z;
    
    // example from R.lift
    const result = R.lift(madd3)([1, 2, 3], [1, 2, 3], [1]);
    
    // this is equivalent of the calculation of 'result' above,
    // R.liftN uses reduce, but the idea is the same
    const result2 = R.ap(R.ap(R.ap([R.curry(madd3)], [1, 2, 3]), [1, 2, 3]), [1]);
    
    console.log(result);  // [ 3, 4, 5, 4, 5, 6, 5, 6, 7 ]
    console.log(result2);  // [ 3, 4, 5, 4, 5, 6, 5, 6, 7 ]
    
    const R = require('ramda');
    const Maybe = require('ramda-fantasy').Maybe;
    
    const madd3 = (x, y, z) => x + y + z;
    const madd3Curried = Maybe.of(R.curry(madd3));
    const a = Maybe.of(1);
    const b = Maybe.of(2);
    const c = Maybe.of(3);
    const sumResult = madd3Curried.ap(a).ap(b).ap(c);  // invoke #ap on Apply
    const sumResult2 = R.ap(R.ap(R.ap(madd3Curried, a), b), c);  // invoke R.ap
    const sumResult3 = R.lift(madd3)(a, b, c);  // invoke R.lift, madd3 is auto-curried
    
    console.log(sumResult);  // Just { value: 6 }
    console.log(sumResult2);  // Just { value: 6 }
    console.log(sumResult3);  // Just { value: 6 }
    
    var madd3 = R.lift((a, b, c) => a + b + c);
    madd3([100, 200], [30, 40, 50], [6, 7]); //=> [136, 137, 146, 147, 156, 157, 236, 237, 246, 247, 256, 257]