Подтвердить что ты не робот

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 фактически принимает два аргумента! Где моя ошибка?

4b9b3361

Ответ 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."]

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