2016-10-19 9 views
7

Я пытаюсь улучшить свое понимание Applicative s и Monad s путем реализации их экземпляров функций в Javascript. Мои знания о Haskell ограничены, и я надеюсь, что мой вопрос имеет смысл.Как интерпретировать bind/>> = экземпляра функции?

Вот мои реализации fmap, <*> и >>= для Functor, Applicative и Monad классов типов в Javascript:

const fmap = f => g => x => f(g(x)); // B combinator 
const apply = f => g => x => f(x) (g(x)); // S combinator 
const bind = f => g => x => g(f(x)) (x); // ? 

Я не уверен, является ли bind правильный перевод реализации Haskell:

(>>=) :: (r -> a) -> (a -> (r -> b)) -> r -> b 

instance Monad ((->) r) where 
f >>= k = \ r -> k (f r) r 

При условии, что bind верен, как оно интерпретируется? Я знаю, что Applicative может выполнять эффективные вычисления. Я также знаю, что Monad дополнительно позволяет вам определить следующий эффект в соответствии с результатом предыдущего.

Я могу видеть последовательности (нетерпеливый порядка оценки в Javascript):

  • apply: f(x) ... g(x) ... lambda(result of g) ... результат lambda
  • bind: f(x) ... g(result of f) ... lambda(x) ... результат lambda

Однако, то bind весело ction выглядит довольно странно. Почему f и g вложены наоборот? Как определяется конкретное поведение Monad (определяет следующий эффект в соответствии с предыдущим), отраженный в этой реализации? Фактически g(f(x)) (x) выглядит как составная функция с перевернутыми аргументами, где g является двоичной функцией.

Когда я применяю apply/bind с унарной и двоичной функцией, они дают тот же результат. Это не имеет большого смысла.

+0

Посмотрите на [этот пример] (http://stackoverflow.com/q/40026018/1048572) для полезного применения 'bind' (известный как' chain' в JS) – Bergi

ответ

5

Значения в экземпляре монады для функций имеют тип r -> a для определенного фиксированного типа r. Функция (a -> (r -> b)), присвоенная (>>=), позволяет вам выбрать следующую функцию для возврата, учитывая результат от текущего значения (функция r -> a). f r имеет тип a и k (f r) имеет тип r -> b, который является следующей функцией для применения.

В вашем коде g(f(x)) есть функция, которая ожидает один аргумент типа r. Вызывающий из bind может выбрать эту функцию на основе значения, возвращаемого предыдущей функцией, например.

var inc = x => x + 1; 
var f = bind(inc)(function(i) { 
    if(i <= 5) { return x => x * 2; } 
    else { return x => x * 3; } 
}); 

Функция будет дано x в качестве входных данных и может выбрать следующий этап вычисления, основываясь на результате inc(x) например,

f(2) //4; 
f(5) //15; 
+0

Спасибо, это так очевидно Теперь! Когда я пытаюсь передать икомы Haskell в Javascript, я часто сталкиваюсь с одной и той же проблемой: многие знания о икомах Haskell скрыты в их приложении, а не только в их реализациях или типах подписи. – ftor

+0

Теперь следующее вычисление зависит от предыдущего результата, но полностью игнорирует этот результат. Является ли это способом 'bind', как правило, работает или просто соответствует вашему примеру? Извините за все вопросы! – ftor

+0

@ftor - Извините, ответ был неправильным, и ваши изменения были неправильным решением. 'bind' использует только' f (x) ', чтобы выбрать следующую функцию, но поставляет на нее исходный вход. – Lee

4

Несколько сносок к ответу Ли:

Однако функция bind выглядит довольно странно. Почему f и g вложены наоборот?

Потому что bind назад. Сравните (>>=) и его переворачивается версия :

(>>=) :: Monad m => m a -> (a -> m b) -> m b 
(=<<) :: Monad m => (a -> m b) -> m a -> m b 

Или в вашем конкретном примере:

(>>=) :: (r -> a) -> (a -> (r -> b)) -> (r -> b) 
(=<<) :: (a -> (r -> b)) -> (r -> a) -> (r -> b) 

Хотя на практике мы склонны использовать (>>=) чаще, чем из-за того, как (>>=), синтаксически говоря, кредитует сама по себе хорошо для типа трубопроводов монады часто используются для построения, с теоретической точки зрения - самый естественный способ его написания. В частности, параллели и различия с fmap/(<$>) и (<*>) гораздо более очевидны:

(<$>) :: Functor f  => (a -> b) -> f a -> f b 
(<*>) :: Applicative f => f (a -> b) -> f a -> f b 
(=<<) :: Monad f  => (a -> f b) -> f a -> f b 

Когда я применяю apply/bind с одинарной и бинарной функции, они дают одинаковый результат. Это не имеет большого смысла.

Это случайный факт об экземплярах функции. Давайте соберем специализированные подписи бок о бок:

(<*>) :: (r -> (a -> b)) -> (r -> a) -> (r -> b) 
(=<<) :: (a -> (r -> b)) -> (r -> a) -> (r -> b) 

Monad выходит за рамки Applicative, предоставляя средства для определения следующего эффекта в соответствии с предыдущими результатами (в отличие от «предыдущего эффекта» - Applicative может сделать это уже). Эффект в этом случае состоит из функции, которая генерирует значения, заданные аргументом типа r. Теперь, поскольку функции с несколькими аргументами (то есть функции, возвращающие функции) могут быть перевернуты, бывает, что нет существенной разницы между (r -> (a -> b)) и (a -> (r -> b)) (flip может тривиально изменить один на другой), что делает экземпляр Monad для (->) r полностью эквивалентным до Applicative один.

+0

Очень полезно, спасибо! – ftor

+1

@ftor Добро пожаловать. Глядя снова на мой ответ, я заметил, что я забыл о значительном переполнении, слишком быстро прочитав ваш вопрос.Вы сказали, что разница между «Monad» и «Applicative» заключается в том, что с «Monad» вы можете определить «следующий эффект в соответствии с предыдущим». «Аппликативный» уже позволяет это. То, что добавляет Monad, определяет эффекты от предыдущих * результатов * (т. Е. Базовые значения в функторе, соответствующие 'a' в' Monad a => a -> m b'). – duplode