Haskell Monad связывает путаницу оператора
Хорошо, поэтому я не программист Haskell, но меня совершенно заинтриговало множество идей, стоящих за Haskell, и я изучаю его. Но я застрял в квадрате: я, похоже, не могу склонить голову над Монадами, которые кажутся довольно фундаментальными. Я знаю, что есть миллион вопросов о SO, которые просят объяснить Монады, поэтому я буду немного более конкретным в отношении того, что меня беспокоит:
Я прочитал эту замечательную статью (введение в Javascript) и подумал, что полностью понял Монад. Затем я прочитал запись в Википедии в Monads и увидел это:
Операция связывания полиморфного типа (M t) → (t → M u) → (M u), которую Haskell представляет оператором infix → =. Его первый аргумент - это значение в монадическом типе, его второй аргумент - это функция, которая отображает из основного типа первого аргумента в другой монадический тип, а его результат - в другом монадическом типе.
Хорошо, в статье, которую я привел, bind была функцией, которая принимала только один аргумент. Википедия говорит два. То, что я думал, что я понял о Монадах, было следующим:
- Цель Monad - использовать функцию с различными типами ввода и вывода и сделать ее составной. Он делает это, обертывая типы ввода и вывода одним монадическим типом.
- Монада состоит из двух взаимосвязанных функций: bind и unit. Bind принимает неконсолидируемую функцию f и возвращает новую функцию g, которая принимает монадический тип в качестве входных данных и возвращает монадический тип. g является композиционным. Функция unit принимает аргумент типа, ожидаемого f, и обертывает его в монадическом типе. Затем это можно передать в g или любой композиции таких функций, как g.
Но должно быть что-то не так, потому что моя концепция связывания принимает один аргумент: функцию. Но (согласно Википедии) Haskell bind фактически принимает два аргумента! Где моя ошибка?
Ответы
Ответ 1
Вы не ошибаетесь. Ключевая идея понять здесь - это каррирование - что функцию Хаскеля двух аргументов можно увидеть двумя способами. Первая - это просто функция двух аргументов. Если у вас есть, например, (+)
, это обычно рассматривается как принятие двух аргументов и их добавление. Другой способ увидеть это как производитель дополнительных машин. (+)
- это функция, которая принимает число, например x
, и делает функцию, которая добавит x
.
(+) x = \y -> x + y
(+) x y = (\y -> x + y) y = x + y
Когда речь идет о монадах, иногда, вероятно, лучше, как упомянуто выше, подумать о =<<
, перевернутой версии >>=
. Есть два способа взглянуть на это:
(=<<) :: (a -> m b) -> m a -> m b
который является функцией двух аргументов и
(=<<) :: (a -> m b) -> (m a -> m b)
который преобразует входную функцию в легко составленную версию в качестве упомянутой статьи. Они эквивалентны, как и (+)
, как я объяснял ранее.
Ответ 2
Позвольте мне разрушить ваши убеждения относительно Монад. Я искренне надеюсь, что вы понимаете, что я не пытаюсь быть грубым; Я просто пытаюсь избежать слов.
Цель Monad - взять функцию с различными типами ввода и вывода и сделать ее составной. Это достигается путем объединения типов ввода и вывода с одним монадическим типом.
Не совсем. Когда вы начинаете предложение с "Целью монады", вы уже не в ту ногу. Монады не обязательно имеют "цель". Monad
- это просто абстракция, классификация, которая применяется к определенным типам, а не к другим. Цель абстракции Monad
- это просто абстракция.
Монада состоит из двух взаимосвязанных функций: связывание и единица.
И да и нет. Комбинация bind
и unit
достаточна для определения монады, но комбинация join
, fmap
и unit
одинаково достаточна. Последнее, по сути, является способом, которым монады обычно описываются в теории категорий.
Bind принимает несоставляемую функцию f и возвращает новую функцию g, которая принимает монадический тип в качестве входных данных и возвращает монадический тип.
Опять не совсем. Монадическая функция f :: a → mb
идеально компонуется с определенными типами. Я могу посткомпоновать его с помощью функции g :: mb → c
чтобы получить g. f :: a → c
g. f :: a → c
, или я могу предварительно составить его с помощью функции h :: c → a
чтобы получить f. h :: c → mb
f. h :: c → mb
.
Но вы правильно поняли вторую часть: (>>= f) :: ma → mb
. Как уже отмечали другие, функция bind
Haskell принимает аргументы в обратном порядке.
г составно.
Ну да. Если g :: ma → mb
, то вы можете предварительно составить его с помощью функции f :: c → ma
чтобы получить g. f :: c → mb
g. f :: c → mb
, или вы можете посткомпоновать его с помощью функции h :: mb → c
чтобы получить h. g :: ma → c
h. g :: ma → c
. Обратите внимание, что c
может иметь форму mv
где m
- монада. Я полагаю, когда вы говорите "компонуемый", вы имеете в виду, что "вы можете составлять произвольно длинные цепочки функций этой формы", что в некотором роде верно.
Функция unit принимает аргумент ожидаемого типа и заключает его в монадический тип.
Примерный способ сказать это, но да, это примерно так.
Этот [результат применения unit
к некоторому значению] может быть затем передан g или любой композиции функций, например g.
Опять да. Хотя обычно Haskell не идиоматичен, чтобы вызывать unit
(или в Haskell, return
) и затем передавать это (>>= f)
.
-- instead of
return x >>= f >>= g
-- simply go with
f x >>= g
-- instead of
\x -> return x >>= f >>= g
-- simply go with
f >=> g
-- or
g <=< f
Ответ 3
Статья, ссылка на которую вы ссылаетесь, основана на статье sigfpe, в которой используется перевернутое определение bind:
Во-первых, я перевернул определение bind
и записал его как слово "bind", тогда как он обычно записывается как оператор >>=
. Итак, bind f x
обычно записывается как x >>= f
.
Итак, Haskell bind
принимает значение, заключенное в монаду, и возвращает функцию, которая принимает функцию, а затем вызывает ее с извлеченным значением. Возможно, я использую неточную терминологию, поэтому, возможно, лучше с кодом.
У вас есть:
sine x = (sin x, "sine was called.")
cube x = (x * x * x, "cube was called.")
Теперь, переведя ваш JS-связывание (Haskell выполняет автоматическое currying, поэтому вызов bind f
возвращает функцию, которая берет кортеж, а затем сопоставление шаблонов заботится о распаковке ее в x
и s
, я надеюсь, что это понятно ):
bind f (x, s) = (y, s ++ t)
where (y, t) = f x
Вы можете видеть, как он работает:
*Main> :t sine
sine :: Floating t => t -> (t, [Char])
*Main> :t bind sine
bind sine :: Floating t1 => (t1, [Char]) -> (t1, [Char])
*Main> (bind sine . bind cube) (3, "")
(0.956375928404503,"cube was called.sine was called.")
Теперь отмените аргументы bind
:
bind' (x, s) f = (y, s ++ t)
where (y, t) = f x
Вы можете ясно видеть, что он все еще делает то же самое, но с немного другим синтаксисом:
*Main> bind' (bind' (3, "") cube) sine
(0.956375928404503,"cube was called.sine was called.")
Теперь у Haskell есть синтаксический трюк, который позволяет использовать любую функцию в качестве инфиксного оператора. Поэтому вы можете написать:
*Main> (3, "") `bind'` cube `bind'` sine
(0.956375928404503,"cube was called.sine was called.")
Теперь переименуйте bind'
в >>=
((3, "") >>= cube >>= sine
), и у вас есть то, что вы искали. Как вы можете видеть, с этим определением вы можете эффективно избавиться от отдельного оператора композиции.
Перевод новой вещи обратно в JavaScript приведет к чему-то вроде этого (обратите внимание, что опять-таки, я просто отклоняю порядок аргументов):
var bind = function(tuple) {
return function(f) {
var x = tuple[0],
s = tuple[1],
fx = f(x),
y = fx[0],
t = fx[1];
return [y, s + t];
};
};
// ugly, but it JS, after all
var f = function(x) { return bind(bind(x)(cube))(sine); }
f([3, ""]); // [0.956375928404503, "cube was called.sine was called."]
Надеемся, что это поможет, а не вводит больше путаницы - дело в том, что эти два определения привязки эквивалентны, но отличаются только синтаксисом вызовов.