Как интерпретировать bind/>>= экземпляра функции?
Я пытаюсь улучшить мое понимание Applicative
с и Monad
s путем реализации их экземпляров функций в 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
один.