鉴于以下内容:
var average = R.lift(R.divide)(R.sum, R.length)
为什么这会作为average
的无点实现?我不明白为什么我可以通过R.sum
和R.length
当它们是函数时,因此,我无法将提升的R.divide
映射到函数R.sum
和{{} 1}}与以下示例不同:
R.length
在上面的例子中,var sum3 = R.curry(function(a, b, c) {return a + b + c;});
R.lift(sum3)(xs)(ys)(zs)
,xs
和ys
中的值在非确定性上下文中求和,在这种情况下,提升函数应用于给定计算中的值上下文。
进一步说明,我理解应用提升函数就像连续地对每个参数使用zs
。这两行评估为相同的输出:
R.ap
检查文档说明:
"升降机" arity的功能> 1,以便它可以"映射"满足FantasyLand Apply规范的列表,函数或其他对象。
至少对我而言,这似乎不是一个非常有用的描述。我试图建立一个关于R.ap(R.ap(R.ap([tern], [1, 2, 3]), [2, 4, 6]), [3, 6, 8])
R.lift(tern)([1, 2, 3], [2, 4, 6], [3, 6, 8])
用法的直觉。我希望有人可以提供。
答案 0 :(得分:13)
第一个很酷的事情是a -> b
可以支持map
。是的,功能是仿函数!
让我们考虑一下map
:
map :: Functor f => (b -> c) -> f b -> f c
让我们用Functor f => f
替换Array
给我们一个具体的类型:
map :: (b -> c) -> Array b -> Array c
让我们这次用Functor f => f
替换Maybe
:
map :: (b -> c) -> Maybe b -> Maybe c
相关性很明显。我们将Functor f => f
替换为Either a
,以测试二进制类型:
map :: (b -> c) -> Either a b -> Either a c
我们经常将a
到b
的函数类型表示为a -> b
,但这实际上只是Function a b
的糖。让我们使用长格式并将上面签名中的Either
替换为Function
:
map :: (b -> c) -> Function a b -> Function a c
因此,映射函数为我们提供了一个函数,它将b -> c
函数应用于原始函数的返回值。我们可以使用a -> b
糖重写签名:
map :: (b -> c) -> (a -> b) -> (a -> c)
注意什么? compose
的类型是什么?
compose :: (b -> c) -> (a -> b) -> a -> c
所以compose
只是map
专门用于函数类型!
第二个很酷的事情是a -> b
可以支持ap
。函数也是 applicative 仿函数!这些被称为Fantasy Land规范中的Apply。
让我们考虑一下ap
:
ap :: Apply f => f (b -> c) -> f b -> f c
让我们用Apply f => f
替换Array
:
ap :: Array (b -> c) -> Array b -> Array c
现在,Either a
:
ap :: Either a (b -> c) -> Either a b -> Either a c
现在,Function a
:
ap :: Function a (b -> c) -> Function a b -> Function a c
什么是Function a (b -> c)
?这有点令人困惑,因为我们混合了两种样式,但它是一个带有a
类型值的函数,并返回从b
到c
的函数。让我们使用a -> b
样式重写:
ap :: (a -> b -> c) -> (a -> b) -> (a -> c)
任何支持map
和ap
的类型都可以“解除”。我们来看看lift2
:
lift2 :: Apply f => (b -> c -> d) -> f b -> f c -> f d
请注意,Function a
符合“应用”的要求,因此我们可以将Apply f => f
替换为Function a
:
lift2 :: (b -> c -> d) -> Function a b -> Function a c -> Function a d
写得更清楚:
lift2 :: (b -> c -> d) -> (a -> b) -> (a -> c) -> (a -> d)
让我们重新审视你的初始表达:
// average :: Number -> Number
const average = lift2(divide, sum, length);
average([6, 7, 8])
做什么? a
([6, 7, 8]
)被赋予a -> b
函数(sum
),生成b
(21
)。 a
函数(a -> c
)也会length
,并生成c
(3
)。现在我们有b
和c
,我们可以将它们提供给b -> c -> d
函数(divide
)以生成d
(7
),这是最终的结果。
因此,因为函数类型可以支持map
和ap
,所以我们免费获得converge
(通过lift
,lift2
和{{ 3}})。我实际上想从Ramda删除converge
,因为没有必要。
请注意,我有意避免在此答案中使用lift3
。由于决定支持任何arity的功能,它具有无意义的类型签名和复杂的实现。另一方面,Sanctuary特定的提升功能具有清晰的类型签名和简单的实现。
答案 1 :(得分:0)
由于我很难理解同样的问题,所以我决定先看看Ramda的源代码。将来会写一篇关于此的博文。与此同时 - 我一直在评论Ramda的lift
如何一步一步地工作。
来自:https://gist.github.com/philipyoungg/a0ab1efff1a9a4e486802a8fb0145d9e
// Let's make an example function that takes an object and return itself.
// 1. Ramda's lift level
lift(zipObj)(keys, values)({a: 1}) // returns {a: 1}
// this is how lift works in the background
module.exports = _curry2(function liftN(arity, fn) {
var lifted = curryN(arity, fn);
return curryN(arity, function() {
return _reduce(ap, map(lifted, arguments[0]), Array.prototype.slice.call(arguments, 1)); // found it. let's convert no 1 to no 2
});
});
// 2. Ramda's reduce level
reduce(ap, map(zipObj, keys))([values])
// first argument is the function, second argument is initial value, and the last one is lists of arguments. If you don't understand how reduce works, there's a plenty of resources on the internet
// 3. Ramda's ap level
ap(map(zipObj, keys), values)
// how ap works in the background
module.exports = _curry2(function ap(applicative, fn) {
return (
typeof applicative.ap === 'function' ?
applicative.ap(fn) :
typeof applicative === 'function' ? //
function(x) { return applicative(x)(fn(x)); } : // because the first argument is a function, ap return this.
// else
_reduce(function(acc, f) { return _concat(acc, map(f, fn)); }, [], applicative)
);
});
// 4. Voilà. Here's the final result.
map(zipObj, keys)({a: 1})(values({a: 1}))
// Hope it helps you and everyone else!