Первая прохладная вещь заключается в том, что 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
в этом ответе. Он имеет бессмысленную подпись типа и сложную реализацию из-за решения поддерживать функции любой арности. С другой стороны, функции подъема специфичности святилища имеют четкие подписи и тривиальные реализации.
Очень красивый! Я бы хотел, чтобы этот ответ поднялся в сообщение в блоге! –
Когда '(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
Чтобы ответить на мой собственный вопрос: это «скворец», который представляет собой состав аппликативных '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