2016-09-17 2 views
6

Учитывая следующее:Как я могу передать функции поднятому R.divide?

var average = R.lift(R.divide)(R.sum, R.length) 

Каким образом это работает как реализация pointfree из 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]) 

Проверка документации он говорит:

«лифтов» функцию арностью> 1, так что он может «карту над» списком, функции или другие объект, который удовлетворяет спецификации FantasyLand Apply.

И это не похоже на очень полезное описание, по крайней мере, для меня. Я пытаюсь построить интуицию относительно использования lift. Надеюсь, кто-то может это предоставить.

ответ

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. Функции также являются аппликативными функторами! Они известны как Apply s в спецификации Fantasy Land.

Рассмотрим тип 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 и lift3). Я бы очень хотел удалить converge из Ramda, поскольку это не обязательно.


Обратите внимание, что я намеренно избегал R.lift в этом ответе. Он имеет бессмысленную подпись типа и сложную реализацию из-за решения поддерживать функции любой арности. С другой стороны, функции подъема специфичности святилища имеют четкие подписи и тривиальные реализации.

+3

Очень красивый! Я бы хотел, чтобы этот ответ поднялся в сообщение в блоге! –

+0

Когда '(b -> c -> d) -> (a -> b) -> (a -> c) -> (a -> d)' реализуется как комбинатор, он выглядит как 'bird = f = > g => h => x => f (g (x)) (h (x)) '. Он похож на 'psi = f => g => x => y => f (g (x)) (g (y))'. Интересно, как называется эта птица? – ftor

+0

Чтобы ответить на мой собственный вопрос: это «скворец», который представляет собой состав аппликативных 'ap' функций' const ap = f => g => x => f (x) (g (x)) 'и Состав 'const comp = f => g => x => f (g (x))': 'const starling_ = comp (comp (ap)) (comp)'. – ftor

0

Как мне трудно понять ту же проблему, я решил заглянуть в исходный код Рамды. Будут писать blogpost об этом в будущем. Между тем, я сделал прокомментированный сущность, как шаг за шагом работает Ramda's 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! 
Смежные вопросы