无法在 Ramda.js 中绕过 "lift"
Can't wrap my head around "lift" in Ramda.js
查看 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, undefined) 还是第一个数组的第二个值以及第二个和第三个数组的第一个值?
即使不考虑这里发生的事情的顺序,我也看不出真正的价值。如果我在没有先 lift
ing 的情况下执行此操作,我将以 concat
作为字符串的数组结束。这看起来有点像 flatMap
但我似乎无法理解它背后的逻辑。
lift
/liftN
"lifts" 将普通函数转换为应用上下文。
// 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);
}
}
现在ap
(f (a->b) -> f a -> f b
)的类型也不太好理解,不过列表的例子应该可以理解。
这里有趣的是你传入一个列表并返回一个列表,所以你可以重复应用它,只要第一个列表中的函数具有正确的类型:
// 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;
这对三个数字进行运算。但是,如果您有 containers 保存数字怎么办?也许我们有 Maybe
s。我们不能简单地将它们加在一起:
const Just = Maybe.Just, Nothing = Maybe.Nothing;
add3(Just(10), Just(15), Just(17)); //=> ERROR!
(好的,这是 Javascript,它实际上不会在这里抛出错误,只是尝试连接它不应该的东西......但它绝对不会做你想要的!)
如果我们能将该功能提升到容器级别,那将使我们的生活更轻松。 Bergi 指出的 lift3
是在 Ramda 中用 liftN(3, fn)
和一个注释 lift(fn)
实现的,它只使用所提供函数的元数。所以,我们可以这样做:
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]
这就是我对 lift
的看法。它需要一个在某些值级别工作的函数,并将其提升到一个在这些值的容器级别工作的函数。
感谢 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]
对我来说,在理解它之前需要回答三个问题。
- Fantasy-land's
Apply
规范(我将其称为 Apply
)以及 Apply#ap
的作用
- Ramda 的
R.ap
implementation and what does Array
与 Apply
规范有关
- 柯里化在
R.lift
中扮演什么角色
了解 Apply
规范
在幻想世界中,对象通过定义 map
方法来实现 Apply
spec when it has an ap
method defined (that object also has to implement Functor
规范。
ap
方法具有以下签名:
ap :: Apply f => f a ~> f (a -> b) -> f b
在fantasy-land's type signature notation中:
=>
声明了类型约束,所以上面签名中的f
指的是类型Apply
~>
声明了 method 声明,因此 ap
应该是在 Apply
上声明的函数,它环绕着我们引用的值as a
(我们会在下面的例子中看到,ap
中的一些fantasy-land's implementations和这个签名不一致,但是思路是一样的)
假设我们有两个对象 v
和 u
(v = f a; u = f (a -> b)
) 因此这个表达式是有效的 v.ap(u)
,这里需要注意一些事情:
v
和 u
都实现了 Apply
。 v
包含一个值,u
包含一个函数,但它们具有与 Apply
相同的 'interface'(这将有助于理解下面的下一节,当谈到 R.ap
和 Array
)
- 值
a
和函数a -> b
不知道Apply
,函数只是转换值a
。 Apply
将值和函数放入容器中,ap
将它们提取出来,调用值上的函数并将它们放回容器中。
了解 Ramda
的 R.ap
R.ap
的签名有两种情况:
Apply f => f (a → b) → f a → f b
:这与上一节中Apply#ap
的签名非常相似,区别在于ap
的调用方式(Apply#ap
vs. R.ap
) 和参数的顺序。
[a → b] → [a] → [b]
:如果我们将Apply f
替换为Array
,这就是版本,还记得上一节中值和函数必须包装在同一个容器中吗?这就是为什么当 R.ap
与 Array
s 一起使用时,第一个参数是一个函数列表,即使你只想应用一个函数,把它放在一个数组中。
让我们看一个例子,我使用Maybe
来自ramda-fantasy
, which implements Apply
, one inconsistency here is that Maybe#ap
的签名是:ap :: Apply f => f (a -> b) ~> f a -> f b
。似乎其他一些 fantasy-land
实现也遵循此,但是,它不应该影响我们的理解:
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 }
理解R.lift
的例子
在R.lift
的数组示例中,一个参数为3的函数被传递给R.lift
:var madd3 = R.lift((a, b, c) => a + b + c);
,它如何与三个数组一起工作[1, 2, 3], [1, 2, 3], [1]
?另请注意,它不是咖喱。
实际上在 source code of R.liftN
内部(R.lift
委托),传入的函数是 auto-curried,然后它遍历值(在在我们的例子中,三个数组),简化为一个结果:在每次迭代中,它使用柯里化函数和一个值(在我们的例子中,一个数组)调用 ap
。这很难用语言解释,让我们看看代码中的等价物:
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 ]
一旦理解了计算result2
的表达式,这个例子就会变得清晰。
这是另一个例子,在 Apply
上使用 R.lift
:
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 }
Scott Sauyet 在评论中建议的一个更好的例子(他提供了很多见解,我建议你阅读它们)会更容易理解,至少它指出了 reader 的方向 R.lift
计算 Array
s 的笛卡尔积。
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]
希望对您有所帮助。
查看 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, undefined) 还是第一个数组的第二个值以及第二个和第三个数组的第一个值?
即使不考虑这里发生的事情的顺序,我也看不出真正的价值。如果我在没有先 lift
ing 的情况下执行此操作,我将以 concat
作为字符串的数组结束。这看起来有点像 flatMap
但我似乎无法理解它背后的逻辑。
lift
/liftN
"lifts" 将普通函数转换为应用上下文。
// 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);
}
}
现在ap
(f (a->b) -> f a -> f b
)的类型也不太好理解,不过列表的例子应该可以理解。
这里有趣的是你传入一个列表并返回一个列表,所以你可以重复应用它,只要第一个列表中的函数具有正确的类型:
// 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;
这对三个数字进行运算。但是,如果您有 containers 保存数字怎么办?也许我们有 Maybe
s。我们不能简单地将它们加在一起:
const Just = Maybe.Just, Nothing = Maybe.Nothing;
add3(Just(10), Just(15), Just(17)); //=> ERROR!
(好的,这是 Javascript,它实际上不会在这里抛出错误,只是尝试连接它不应该的东西......但它绝对不会做你想要的!)
如果我们能将该功能提升到容器级别,那将使我们的生活更轻松。 Bergi 指出的 lift3
是在 Ramda 中用 liftN(3, fn)
和一个注释 lift(fn)
实现的,它只使用所提供函数的元数。所以,我们可以这样做:
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]
这就是我对 lift
的看法。它需要一个在某些值级别工作的函数,并将其提升到一个在这些值的容器级别工作的函数。
感谢 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]
对我来说,在理解它之前需要回答三个问题。
- Fantasy-land's
Apply
规范(我将其称为Apply
)以及Apply#ap
的作用 - Ramda 的
R.ap
implementation and what doesArray
与Apply
规范有关 - 柯里化在
R.lift
中扮演什么角色
了解 Apply
规范
在幻想世界中,对象通过定义 map
方法来实现 Apply
spec when it has an ap
method defined (that object also has to implement Functor
规范。
ap
方法具有以下签名:
ap :: Apply f => f a ~> f (a -> b) -> f b
在fantasy-land's type signature notation中:
=>
声明了类型约束,所以上面签名中的f
指的是类型Apply
~>
声明了 method 声明,因此ap
应该是在Apply
上声明的函数,它环绕着我们引用的值asa
(我们会在下面的例子中看到,ap
中的一些fantasy-land's implementations和这个签名不一致,但是思路是一样的)
假设我们有两个对象 v
和 u
(v = f a; u = f (a -> b)
) 因此这个表达式是有效的 v.ap(u)
,这里需要注意一些事情:
v
和u
都实现了Apply
。v
包含一个值,u
包含一个函数,但它们具有与Apply
相同的 'interface'(这将有助于理解下面的下一节,当谈到R.ap
和Array
)- 值
a
和函数a -> b
不知道Apply
,函数只是转换值a
。Apply
将值和函数放入容器中,ap
将它们提取出来,调用值上的函数并将它们放回容器中。
了解 Ramda
的 R.ap
R.ap
的签名有两种情况:
Apply f => f (a → b) → f a → f b
:这与上一节中Apply#ap
的签名非常相似,区别在于ap
的调用方式(Apply#ap
vs.R.ap
) 和参数的顺序。[a → b] → [a] → [b]
:如果我们将Apply f
替换为Array
,这就是版本,还记得上一节中值和函数必须包装在同一个容器中吗?这就是为什么当R.ap
与Array
s 一起使用时,第一个参数是一个函数列表,即使你只想应用一个函数,把它放在一个数组中。
让我们看一个例子,我使用Maybe
来自ramda-fantasy
, which implements Apply
, one inconsistency here is that Maybe#ap
的签名是:ap :: Apply f => f (a -> b) ~> f a -> f b
。似乎其他一些 fantasy-land
实现也遵循此,但是,它不应该影响我们的理解:
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 }
理解R.lift
的例子
在R.lift
的数组示例中,一个参数为3的函数被传递给R.lift
:var madd3 = R.lift((a, b, c) => a + b + c);
,它如何与三个数组一起工作[1, 2, 3], [1, 2, 3], [1]
?另请注意,它不是咖喱。
实际上在 source code of R.liftN
内部(R.lift
委托),传入的函数是 auto-curried,然后它遍历值(在在我们的例子中,三个数组),简化为一个结果:在每次迭代中,它使用柯里化函数和一个值(在我们的例子中,一个数组)调用 ap
。这很难用语言解释,让我们看看代码中的等价物:
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 ]
一旦理解了计算result2
的表达式,这个例子就会变得清晰。
这是另一个例子,在 Apply
上使用 R.lift
:
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 }
Scott Sauyet 在评论中建议的一个更好的例子(他提供了很多见解,我建议你阅读它们)会更容易理解,至少它指出了 reader 的方向 R.lift
计算 Array
s 的笛卡尔积。
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]
希望对您有所帮助。