为什么我可以将函数传递给提升的R.divide?
How come I can pass functions to a lifted R.divide?
鉴于以下情况:
var average = R.lift(R.divide)(R.sum, R.length)
为什么它可以作为 average
的无点实现?我不明白为什么我可以传递 R.sum
和 R.length
当它们是函数时,因此,我无法将提升的 R.divide
映射到函数 R.sum
和 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
。两行的计算结果相同:
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])
查看它说的文档:
"lifts" a function of arity > 1 so that it may "map over" a list, Function or other object that satisfies the FantasyLand Apply spec.
至少对我来说,这似乎不是一个非常有用的描述。我正在尝试建立关于 lift
用法的直觉。希望有人能提供一下。
首先很酷的是a -> b
可以支持map
。是的,函数是仿函数!
让我们考虑map
的类型:
map :: Functor f => (b -> c) -> f b -> f c
让我们用 Array
替换 Functor f => f
来给我们一个具体的类型:
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
函数应用于原始函数的 return 值。我们可以使用 a -> b
糖重写签名:
map :: (b -> c) -> (a -> b) -> (a -> c)
注意到什么了吗? compose
的类型是什么?
compose :: (b -> c) -> (a -> b) -> a -> c
所以 compose
只是 map
专用于 Function 类型!
第二个很酷的事情是 a -> b
可以支持 Fantasy Land 规范中的 ap
. Functions are also applicative functors! These are known as Applys。
让我们考虑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
值的函数,而 return 是一个从 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的要求,所以我们可以把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
和 lift3
)。我实际上想从 Ramda 中删除 converge
,因为没有必要。
请注意,我有意避免在此答案中使用 R.lift
。由于决定支持任何 arity 的功能,它具有无意义的类型签名和复杂的实现。另一方面,Sanctuary 特定于 arity 的提升函数具有清晰的类型签名和简单的实现。
由于我很难理解同样的问题,所以我决定看一下 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!
鉴于以下情况:
var average = R.lift(R.divide)(R.sum, R.length)
为什么它可以作为 average
的无点实现?我不明白为什么我可以传递 R.sum
和 R.length
当它们是函数时,因此,我无法将提升的 R.divide
映射到函数 R.sum
和 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
。两行的计算结果相同:
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])
查看它说的文档:
"lifts" a function of arity > 1 so that it may "map over" a list, Function or other object that satisfies the FantasyLand Apply spec.
至少对我来说,这似乎不是一个非常有用的描述。我正在尝试建立关于 lift
用法的直觉。希望有人能提供一下。
首先很酷的是a -> b
可以支持map
。是的,函数是仿函数!
让我们考虑map
的类型:
map :: Functor f => (b -> c) -> f b -> f c
让我们用 Array
替换 Functor f => f
来给我们一个具体的类型:
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
函数应用于原始函数的 return 值。我们可以使用 a -> b
糖重写签名:
map :: (b -> c) -> (a -> b) -> (a -> c)
注意到什么了吗? compose
的类型是什么?
compose :: (b -> c) -> (a -> b) -> a -> c
所以 compose
只是 map
专用于 Function 类型!
第二个很酷的事情是 a -> b
可以支持 Fantasy Land 规范中的 ap
. Functions are also applicative functors! These are known as Applys。
让我们考虑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
值的函数,而 return 是一个从 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的要求,所以我们可以把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
和 lift3
)。我实际上想从 Ramda 中删除 converge
,因为没有必要。
请注意,我有意避免在此答案中使用 R.lift
。由于决定支持任何 arity 的功能,它具有无意义的类型签名和复杂的实现。另一方面,Sanctuary 特定于 arity 的提升函数具有清晰的类型签名和简单的实现。
由于我很难理解同样的问题,所以我决定看一下 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!