鉴于以下情况:
var average = R.lift(R.divide)(R.sum, R.length)为什么这作为average的无点实现工作呢?我不明白为什么当R.sum和R.length是函数时,我可以传递它们,因此,与下面的示例不同,我不能在函数R.sum和R.length上映射已解除的R.divide:
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])检查文件上写着:
“解除”一个大于1的函数,以便它可以“映射”满足FantasyLand应用规范的列表、函数或其他对象。
至少对我来说,这似乎不是一个非常有用的描述。我正在尝试建立一个关于lift使用的直觉。我希望有人能提供这个。
发布于 2016-09-17 13:10:19
第一件很酷的事情是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。函数也是应用函子!这些都被称为应用在梦幻之地的规范。
让我们考虑一下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 cFunction 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满足应用的要求,所以我们可以用Function a代替Apply f => f。
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。它有一个无意义的类型签名和复杂的实现,因为它决定支持任何的功能。圣所特有的提升功能,另一方面,有明确的类型签名和琐碎的实现。
发布于 2016-12-31 19:18:39
由于我很难理解同样的问题,所以我决定从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!https://stackoverflow.com/questions/39545916
复制相似问题