Хаскелл: Вычисление "в монаде" - что означает?
Прочитав о монадах, я продолжаю видеть фразы типа "вычисления в монаде Xyz". Что значит для вычисления "в" определенной монаде?
Я думаю, что я хорошо разбираюсь в том, что такое монады: позволяет вычислениям производить выходы, которые обычно имеют какой-то ожидаемый тип, но может альтернативно или дополнительно передавать некоторую другую информацию, такую как состояние ошибки, данные регистрации, состояние и т.д. и позволять таким вычислениям быть скованными.
Но я не понимаю, как вычисление будет считаться "в" монаде. Это относится только к функции, которая производит монадический результат?
Примеры: (поиск "вычисление в" )
Ответы
Ответ 1
Как правило, "вычисление в монаде" означает не только функцию, возвращающую монадический результат, но и такую функцию, которая используется внутри блока do
или как часть второго аргумента для (>>=)
, или что-либо еще эквивалентное тем. Различие имеет отношение к тому, что вы сказали в комментарии:
"Вычисление" происходит в func f, после того, как val извлечен из входной монады, и до того, как результат будет завернут как монада. Я не вижу, как само по себе вычисление "в" монаде; это кажется явно "вне" монады.
Это не плохой способ думать об этом - на самом деле, нотация do
поощряет это, потому что это удобный способ взглянуть на вещи - но это приводит к слегка вводящей в заблуждение интуиции. Нигде ничего не "извлекают" из монады. Чтобы понять, почему, забудьте о (>>=)
- это составная операция, которая существует для поддержки нотации do
. Более фундаментальное определение монады - три ортогональные функции:
fmap :: (a -> b) -> (m a -> m b)
return :: a -> m a
join :: m (m a) -> m a
... где m
- монада.
Теперь подумайте о том, как реализовать (>>=)
с ними: начиная с аргументов типа m a
и a -> m b
, ваш единственный вариант использует fmap
, чтобы получить что-то типа m (m b)
, после чего вы можете использовать join
, чтобы сгладить вложенные "слои", чтобы получить только m b
.
Другими словами, ничто не берется "из" монады - вместо этого подумайте о том, что вычисления будут идти глубже в монаду, а последующие шаги будут свернуты в один слой монады.
Обратите внимание, что законы монады также намного проще с этой точки зрения - по сути, они говорят, что при применении join
не имеет значения до тех пор, пока сохраняется порядок вложения (форма ассоциативности) и что монадический слой, введенный return
, ничего не делает (значение идентичности для join
).
Ответ 2
Означает ли это только функцию, которая производит монадический результат?
Да, короче.
В долгосрочной перспективе это потому, что Monad
позволяет вводить в него значения (через return
), но один раз внутри Monad
они застревают. Вы должны использовать некоторую функцию, например evalWriter
или runCont
, которая является более специфичной, чем Monad
, чтобы вернуть значения "out".
Более того, Monad
(на самом деле, его партнер, Applicative
) является сутью наличия "контейнера" и разрешения вычислений внутри него. То, что дает (>>=)
, возможность делать интересные вычисления "внутри" Monad
.
Таким образом, функции, такие как Monad m => m a -> (a -> m b) -> m b
, позволяют вычислять с и вокруг и внутри Monad
. Такие функции, как Monad m => a -> m a
, позволяют вводить в Monad
. Такие функции, как m a -> a
, позволят вам "избежать" Monad
, за исключением того, что они вообще не существуют (только в определенных). Итак, для беседы нам нравится говорить о функциях, которые имеют такие типы результатов, как Monad m => m a
как "внутри монады".
Ответ 3
Обычно материал монады легче понять, когда начинаешься с "сборных" монад в качестве примера. Представьте, что вы вычисляете расстояние двух точек:
data Point = Point Double Double
distance :: Point -> Point -> Double
distance p1 p2 = undefined
Теперь у вас может быть определенный контекст. Например. одна из точек может быть "незаконной", потому что она выходит за рамки (например, на экране). Итак, вы завершаете существующее вычисление в монаде Maybe
:
distance :: Maybe Point -> Maybe Point -> Maybe Double
distance p1 p2 = undefined
У вас точно такое же вычисление, но с дополнительной функцией, которая может быть "без результата" (закодирована как Nothing
).
Или у вас есть две группы "возможных" точек и нужны их взаимные расстояния (например, чтобы использовать позднее кратчайшее соединение). Тогда монада-список - это ваш "контекст":
distance :: [Point] -> [Point] -> [Double]
distance p1 p2 = undefined
Или точки вводятся пользователем, что делает расчет "недетерминированным" (в том смысле, что вы зависите от вещей во внешнем мире, которые могут измениться), тогда монада IO
является вашим другом:
distance :: IO Point -> IO Point -> IO Double
distance p1 p2 = undefined
Вычисление остается неизменным, но происходит в определенном "контексте", что добавляет некоторые полезные аспекты (сбой, многозначность, недетерминизм). Вы даже можете объединить эти контексты (монадные трансформаторы).
Вы можете написать определение, которое унифицирует приведенные выше определения и работает для любой монады:
distance :: Monad m => m Point -> m Point -> m Double
distance p1 p2 = do
Point x1 y1 <- p1
Point x2 y2 <- p2
return $ sqrt ((x1-x2)^2 + (y1-y2)^2)
Это доказывает, что наш расчет действительно независим от фактической монады, что приводит к формулировкам, поскольку "x вычисляется в (-side) y-монаде".
Ответ 4
Посмотрев на ссылки, которые вы предоставили, кажется, что общее использование "вычислений в" относится к одному монадическому значению. Выдержки:
Нежное введение - здесь мы запускаем вычисление в монаде SM, но вычисление является монадическим значением:
-- run a computation in the SM monad
runSM :: S -> SM a -> (a,S)
Все о монадах - предыдущее вычисление относится к монадическому значению в последовательности:
Функция → - это удобный оператор, который используется для привязки монадического вычисления, которое не требует ввода из предыдущего вычисления в последовательности
Понимание монадов - здесь первое вычисление может относиться к примеру. getLine
, монадическое значение:
(binding) дает внутреннюю идею использования результата вычисления в другом вычислении, не требуя понятия текущих вычислений.
Итак, в качестве аналогии, если я говорю i = 4 + 2
, тогда i
- это значение 6
, но оно равно вычисляется, а именно вычисление 4 + 2
. Кажется, что связанные страницы используют вычисление в этом смысле - вычисление как монадическое значение - по крайней мере, некоторое время, и в этом случае имеет смысл использовать выражение "вычисление в" данной монаде.
Ответ 5
Рассмотрим монаду IO
. Значение типа IO a
представляет собой описание большого (часто бесконечного) числа поведения, где поведение представляет собой последовательность событий IO (чтение, запись и т.д.). Такое значение называется "вычислением"; в этом случае это вычисление в монаде IO
.