Как интерпретировать bind/>>= экземпляра функции?

Я пытаюсь улучшить мое понимание Applicativeс и Monads путем реализации их экземпляров функций в Javascript. Мои знания Хаскелла ограничены, и я надеюсь, что мой вопрос имеет смысл вообще.

Вот мои реализации 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 Функция выглядит довольно странно. Почему f а также g вложили наоборот? Как конкретно Monad поведение (определяет следующий эффект в соответствии с предыдущим) отражено в этой реализации? На самом деле g(f(x)) (x) выглядит как композиция функции с перевернутыми аргументами, где g это бинарная функция

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

2 ответа

Решение

Значения в экземпляре монады для функций имеют тип 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;

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

Тем не менее 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 один.

Другие вопросы по тегам