Хаскель Монад связать оператор путаницы - PullRequest
30 голосов
/ 02 ноября 2011

Хорошо, так что я не программист на Haskell, но я абсолютно заинтригован многими идеями, стоящими за Haskell, и стремлюсь изучить его. Но я застрял на первом месте: я не могу обернуть голову вокруг монад, которые кажутся довольно фундаментальными. Я знаю, что на SO есть миллион вопросов, объясняющих монады, поэтому я собираюсь немного подробнее рассказать о том, что меня беспокоит:

Я прочитал эту прекрасную статью ( введение в Javascript ) и подумал, что полностью понимаю монады. Затем я прочитал статью в Википедии о Монадах и увидел это:

Операция связывания полиморфного типа (M t) → (t → M u) → (M u), которую Haskell представляет с помощью инфиксного оператора >> =. Его первый аргумент - это значение в монадическом типе, второй аргумент - это функция, которая отображает базовый тип первого аргумента в другой монадический тип, и его результат находится в этом другом монадическом типе.

Хорошо, в статье, которую я цитировал, bind была функцией, которая принимала только один аргумент. Википедия говорит два. То, что я думал Я понял о Монадах, было следующим:

  1. Цель Monad - взять функцию с различными типами ввода и вывода и сделать ее составной. Это достигается путем объединения типов ввода и вывода с одним монадическим типом.
  2. Монада состоит из двух взаимосвязанных функций: связывание и единица. Bind принимает несоставляемую функцию f и возвращает новую функцию g, которая принимает монадический тип в качестве входных данных и возвращает монадический тип. г составно. Функция unit принимает аргумент ожидаемого типа и заключает его в монадический тип. Затем это можно передать g или любой композиции функций, например g.

Но должно быть что-то не так, потому что моя концепция привязки принимает один аргумент: функцию. Но (согласно Википедии) привязка Хаскелла на самом деле принимает два аргумента! Где моя ошибка?

Ответы [ 3 ]

25 голосов
/ 02 ноября 2011

Вы не ошиблись. Ключевая идея для понимания здесь - это карри - то, что функцию Хаскелла с двумя аргументами можно рассматривать двумя способами. Первый - это просто функция двух аргументов. Если у вас есть, например, (+), это обычно рассматривается как принятие двух аргументов и их добавление. Другой способ увидеть это как производитель машин для подбора. (+) - это функция, которая принимает число, скажем 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)

, которая преобразует функцию ввода в легко составленную версию, как упоминалось в статье. Они эквивалентны, как и (+), как я объяснил ранее.

20 голосов
/ 02 ноября 2011

Позвольте мне разрушить ваши убеждения относительно Монад. Я искренне надеюсь, что вы понимаете, что я не пытаюсь быть грубым; Я просто пытаюсь избежать чепухи.

Цель Monad - взять функцию с различными типами ввода и вывода и сделать ее составной. Это достигается путем объединения типов ввода и вывода с одним монадическим типом.

Не совсем. Когда вы начинаете предложение «Целью монады», вы уже не на том пути. Монады не обязательно имеют «цель». Monad - это просто абстракция, классификация, которая применяется к определенным типам, а не к другим. Цель Monad абстракции - просто абстракция.

Монада состоит из двух взаимосвязанных функций: связывание и единица.

Да и нет. Комбинация bind и unit достаточна для определения монады, но комбинация join, fmap и unit одинаково достаточна. Последнее, по сути, является способом, которым монады обычно описываются в теории категорий.

Bind принимает несоставляемую функцию f и возвращает новую функцию g, которая принимает монадический тип в качестве входных данных и возвращает монадический тип.

Опять не совсем. Монадическая функция f :: a -> m b идеально подходит для определенных типов. Я могу посткомпоновать его с помощью функции g :: m b -> c, чтобы получить g . f :: a -> c, или я могу предварительно скомпоновать его с помощью функции h :: c -> a, чтобы получить f . h :: c -> m b.

Но вы правильно поняли вторую часть: (>>= f) :: m a -> m b. Как уже отмечалось, функция Haskell bind принимает аргументы в обратном порядке.

г является составным.

Ну да. Если g :: m a -> m b, то вы можете предварительно скомпоновать его с помощью функции f :: c -> m a, чтобы получить g . f :: c -> m b, или вы можете пост-скомпоновать его с помощью функции h :: m b -> c, чтобы получить h . g :: m a -> c. Обратите внимание, что c может иметь форму m v, где 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
9 голосов
/ 02 ноября 2011

Статья, на которую вы ссылаетесь, основана на статье 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 выполняет автоматическое каррирование, поэтому вызов 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's JS, after all
var f = function(x) { return bind(bind(x)(cube))(sine); }

f([3, ""]); // [0.956375928404503, "cube was called.sine was called."]

Надеюсь, что это помогает, а не вводит больше путаницы - дело в том, что эти два определения связывания эквивалентны, различаются только синтаксисом вызова.

...