查看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,未定义)还是第一个数组的第二个值以及第二个和第三个数组的第一个值?
即使无视这里发生的事情的顺序,我也不会真正看到它的价值。如果我在没有lift
的情况下执行此操作,我将最终将数组concat
作为字符串。这似乎与flatMap
类似,但我似乎无法遵循它背后的逻辑。
答案 0 :(得分:32)
Bergi的答案很棒。但另一种思考方式是更具体一点。 Ramda确实需要在其文档中包含一个非列表示例,因为列表并没有真正捕获到它。
让我们采取一个简单的功能:
var add3 = (a, b, c) => a + b + c;
这适用于三个数字。但是如果你有容器持有数字怎么办?也许我们有Maybe
个。我们不能简单地将它们加在一起:
const Just = Maybe.Just, Nothing = Maybe.Nothing;
add3(Just(10), Just(15), Just(17)); //=> ERROR!
(好吧,这是Javascript,它实际上不会在这里引发错误,只是尝试连接它不应该的东西......但它肯定不能做你想要的!)
如果我们能够将该功能提升到容器的水平,它将使我们的生活更轻松。 Bergi指出lift3
的内容在Ramda中使用liftN(3, fn)
实现,而光泽lift(fn)
只使用所提供函数的arity。所以,我们可以这样做:
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
的看法。它需要一个在某些值的水平上起作用的函数,并将其提升到一个在这些值的容器级别起作用的函数。
答案 1 :(得分:10)
感谢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
)以及Apply#ap
做什么R.ap
实施以及Array
与Apply
规范R.lift
Apply
规范在fantasy-land中,对象在定义ap
方法时实现Apply
规范(该对象还必须通过定义map
方法来实现Functor
规范)。
ap
方法具有以下签名:
ap :: Apply f => f a ~> f (a -> b) -> f b
在fantasy-land's type signature notation中:
=>
声明类型约束,因此上面签名中的f
引用类型Apply
~>
声明了方法声明,因此ap
应该是Apply
上声明的函数,它包含一个我们称为{{1}的值(我们将在下面的示例中看到,a
的某些fantasy-land's implementations与此签名不一致,但这个想法是相同的)我们假设我们有两个对象ap
和v
(u
),因此这个表达式是有效的v = f a; u = f (a -> b)
,这里有一些注意事项:
v.ap(u)
和v
都实施u
。 Apply
包含一个值,v
包含一个函数,但它们具有相同的界面' u
的{{1}} Apply
和R.ap
Array
和函数a
不知道a -> b
,该函数只会转换值Apply
。它是将值和函数放在容器中的a
和将它们提取出来的Apply
,调用值上的函数并将它们放回去。ap
' s Ramda
R.ap
的签名有两种情况:
R.ap
:这与上一节Apply f => f (a → b) → f a → f b
的签名非常相似,不同之处在于Apply#ap
与ap
的对比方式Apply#ap
)和params的顺序。R.ap
:如果我们将[a → b] → [a] → [b]
替换为Apply f
,这是版本,请记住,值和函数必须包含在上一节中的同一容器中吗?这就是为什么当Array
与R.ap
一起使用时,第一个参数是一个函数列表,即使你只想应用一个函数,也把它放在一个数组中。让我们看一个例子,我使用ramada-fantasy
中的Array
来实现Maybe
,这里的一个不一致是Maybe#ap
& #39;签名是:Apply
。似乎其他一些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
的数组示例中,将arity为3的函数传递给R.lift
:R.lift
,它如何与三个数组{{1}一起使用}}?另请注意,它并非咖喱。
实际上在source code of R.liftN
内(var madd3 = R.lift((a, b, c) => a + b + c);
代表),传入的函数是自动咖喱,然后它遍历值(在我们的例子中,三个数组) ),减少到一个结果:在每次迭代中,它使用curried函数和一个值(在我们的例子中,一个数组)调用[1, 2, 3], [1, 2, 3], [1]
。用文字解释很难,让我们看看代码中的等价物:
R.lift
一旦理解了计算ap
的表达式,示例就会变得清晰。
这是另一个例子,在const R = require('ramda');
const Maybe = require('ramda-fantasy').Maybe;
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
:
R.lift
Scott Sauyet在评论中提出的一个更好的例子(他提供了一些见解,我建议你阅读它们)会更容易理解,至少它指出了读者Apply
计算笛卡儿的方向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 }
的产品。
R.lift
希望这有帮助。
答案 2 :(得分:7)
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);
}
}
现在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)
。