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

Зачем использовать такой особый тип функции в монадах?

Новое в Haskell, и я пытаюсь понять эту вещь Монады. Оператор монадического связывания - >>= - имеет очень своеобразную подпись типа:

(>>=) :: Monad m => m a -> (a -> m b) -> m b

Чтобы упростить, заменим Maybe на m:

(>>=) :: Maybe a -> (a -> Maybe b) -> Maybe b

Однако обратите внимание, что определение могло быть записано тремя различными способами:

(>>=) :: Maybe a -> (Maybe a -> Maybe b) -> Maybe b
(>>=) :: Maybe a -> (      a -> Maybe b) -> Maybe b
(>>=) :: Maybe a -> (      a ->       b) -> Maybe b

Из трех один в центре является наиболее асимметричным. Тем не менее, я понимаю, что первый из них не имеет смысла, если мы хотим избежать (то, что LYAH вызывает шаблонный код). Однако из следующих двух я предпочел бы последний. Для Maybe это будет выглядеть так:

Если это определено как:

(>>=) :: Maybe a -> (a -> b) -> Maybe b

instance Monad Maybe where
   Nothing  >>= f = Nothing
   (Just x) >>= f = return $ f x

Здесь a -> b - обычная функция. Кроме того, я не вижу ничего опасного, потому что Nothing ловит исключение перед приложением функции, поэтому функция a -> b не будет вызываться, если не получена Just a.

Так что, возможно, есть что-то, что не кажется мне очевидным, что привело к тому, что определение (>>=) :: Maybe a -> (a -> Maybe b) -> Maybe b было предпочтительнее более простого определения (>>=) :: Maybe a -> (a -> b) -> Maybe b? Есть ли какая-то неотъемлемая проблема, связанная с (что я считаю) более простым определением?

4b9b3361

Ответ 1

Это гораздо более симметрично, если вы думаете в терминах следующей производной функции (от Control.Monad):

(>=>) :: Monad m => (a -> m b) -> (b -> m c) -> (a -> m c)
(f >=> g) x = f x >>= g

Причина, по которой эта функция значительна, заключается в том, что она подчиняется трем полезным уравнениям:

-- Associativity
(f >=> g) >=> h = f >=> (g >=> h)

-- Left identity
return >=> f = f

-- Right identity
f >=> return = f

Это законы категории, и если вы переводите их на использование (>>=) вместо (>=>), вы получите три закона монады:

(m >>= g) >>= h = m >>= \x -> (g x >>= h)

return x >>= f = f x

m >>= return = m

Так что это действительно не (>>=), это элегантный оператор, а (>=>) - симметричный оператор, который вы ищете. Однако причина, по которой мы обычно думаем в терминах (>>=), состоит в том, что это то, что обозначение do desugars to.

Ответ 2

Рассмотрим одно из общих применений ошибок Maybe monad: обработки. Скажем, я хотел благополучно разделить два номера. Я мог бы написать эту функцию:

safeDiv :: Int -> Int -> Maybe Int
safeDiv _ 0 = Nothing
safeDiv n d = n `div` d

Затем со стандартной монадой Maybe я мог бы сделать что-то вроде этого:

foo :: Int -> Int -> Maybe Int
foo a b = do
  c <- safeDiv 1000 b
  d <- safeDiv a c  -- These last two lines could be combined.
  return d          -- I am not doing so for clarity.

Обратите внимание, что на каждом шаге safeDiv может выйти из строя, но на обоих шагах safeDiv принимает Int s, а не Maybe Int s. Если >>= имела эту подпись:

(>>=) :: Maybe a -> (a -> b) -> Maybe b

Вы могли бы скомпоновать функции вместе, а затем дать ему либо Nothing, либо Just, либо либо развернуть Just, пройти весь конвейер, но и перевернуть его в Just, или он просто передал бы Nothing, по существу, нетронутым. Это может быть полезно, но это не монада. Чтобы это было полезно, мы должны быть в состоянии провалиться посередине и что эта подпись дает нам:

(>>=) :: Maybe a -> (a -> Maybe b) -> Maybe b

Кстати, что-то с созданной вами подписи существует:

flip fmap :: Maybe a -> (a -> b) -> Maybe b

Ответ 3

Более сложная функция с a -> Maybe b является более общей и более полезной и может быть использована для реализации простой. Это не работает наоборот.

Вы можете построить функцию a -> Maybe b из функции f :: a -> b:

f' :: a -> Maybe b
f' x = Just (f x)

Или, в терминах return (который Just для Maybe):

f' = return . f

Другой способ не обязательно возможен. Если у вас есть функция g :: a -> Maybe b и вы хотите использовать ее с "простым" связыванием, вам сначала нужно преобразовать ее в функцию a -> b. Но это обычно не работает, потому что g может возвращать Nothing, где функция a -> b должна возвращать значение b.

Таким образом, как правило, "простая" привязка может быть реализована с точки зрения "сложной", но не наоборот. Кроме того, сложная привязка часто полезна и не может сделать многое невозможным. Поэтому, используя более общие мозаики связывания, применимы к большему количеству ситуаций.

Ответ 4

Проблема с альтернативной сигнатурой типа для (>>=) заключается в том, что она только случайно работает для монады Maybe, если вы попробуете ее с другой монадой (то есть List monad), вы увидите, что она разбивается на тип b для общего случая. Подпись, которую вы предоставили, не описывает монадическое связывание, и законы монады не могут не соответствовать этому определению.

import Prelude hiding (Monad, return)

-- assume monad was defined like this
class Monad m where
  (>>=)  :: m a -> (a -> b) -> m b
  return :: a -> m a

instance Monad Maybe where
  Nothing  >>= f = Nothing
  (Just x) >>= f = return $ f x

instance Monad [] where
  m >>= f   =  concat (map f m)
  return x  =  [x]

Ошибка с ошибкой типа:

    Couldn't match type `b' with `[b]'
      `b' is a rigid type variable bound by
          the type signature for >>= :: [a] -> (a -> b) -> [b]
          at monadfail.hs:12:3
    Expected type: a -> [b]
      Actual type: a -> b
    In the first argument of `map', namely `f'
    In the first argument of `concat', namely `(map f m)'
    In the expression: concat (map f m)

Ответ 5

Вещь, которая делает монаду монадой, - это то, как работает "join" . Напомним, что соединение имеет тип:

join :: m (m a) -> m a

"join" is is "интерпретирует" действие монады, которое возвращает действие монады в терминах действия монады. Таким образом, вы можете подумать, что он отрывает слой монады (или, лучше всего, вытаскивает материал во внутренний слой во внешний слой). Это означает, что "m" формирует "стек", в смысле "стека вызовов". Каждый "m" представляет собой контекст, и "join" позволяет нам объединять контексты вместе в порядке.

Итак, что это связано с привязкой? Напомним:

(>>=) :: m a -> (a -> m b) -> m b

А теперь рассмотрим, что для f:: a → m b и ma:: m a:

fmap f ma :: m (m b)

То есть результат применения f непосредственно к a в ma является (m (m b)). Мы можем применить к этому присоединение, чтобы получить m b. Короче говоря,

ma >>= f = join (fmap f ma)