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

Haskell: Почему типы Maybe и Lither ведут себя по-разному, когда используются как Monads?

Я пытаюсь разобраться с обработкой ошибок в Haskell. Я нашел статью " 8 способов сообщать об ошибках в Haskell", но я смущен, почему, возможно, и все ведут себя по-другому.

Например:

import Control.Monad.Error

myDiv :: (Monad m) => Float -> Float -> m Float
myDiv x 0 = fail "My divison by zero"
myDiv x y = return (x / y)

testMyDiv1 :: Float -> Float -> String
testMyDiv1 x y =
    case myDiv x y of
        Left e  -> e
        Right r -> show r

testMyDiv2 :: Float -> Float -> String
testMyDiv2 x y =
    case myDiv x y of
        Nothing -> "An error"
        Just r  -> show r

Вызов testMyDiv2 1 0 дает результат "An error", но вызов testMyDiv1 1 0 дает:

"*** Exception: My divison by zero

(Обратите внимание на отсутствие закрывающей цитаты, указав, что это не строка, а исключение).

Что дает?

4b9b3361

Ответ 1

Короткий ответ заключается в том, что класс Monad в Haskell добавляет операцию fail к оригинальной математической идее монадов, что делает несколько спорным, как сделать тип Either в (Haskell) Monad, потому что есть многие способы сделать это.

Существует несколько реализаций, которые совершают разные действия. 3 основных подхода, о которых я знаю:

  • fail = Left. Это похоже на то, что большинство людей ожидает, но на самом деле это невозможно сделать в строгом Haskell 98. Экземпляр должен быть объявлен как instance Monad (Either String), который не является законным в H98, поскольку он упоминает конкретный тип для одного из Either (в GHC расширение FlexibleInstances заставит компилятор принять его).
  • Игнорировать fail, используя реализацию по умолчанию, которая просто вызывает error. Это то, что происходит в вашем примере. Преимущество этой версии заключается в том, что она совместима с H98, но недостатком является то, что она довольно удивительна для пользователя (с неожиданностью, приходящей во время выполнения).
  • Реализация fail вызывает некоторый другой класс для преобразования String в любой тип. Это делается в модуле MTL Control.Monad.Error, который объявляет instance Error e => Monad (Either e). В этой реализации fail msg = Left (strMsg msg). Этот снова является законным H98, и снова изредка удивляет пользователей, потому что он вводит другой тип класса. В отличие от последнего примера, однако, сюрприз приходит во время компиляции.

Ответ 2

Я предполагаю, что вы используете monads-fd.

$ ghci t.hs -hide-package mtl
*Main Data.List> testMyDiv1 1 0
"*** Exception: My divison by zero
*Main Data.List> :i Either
...
instance Monad (Either e) -- Defined in Control.Monad.Trans.Error
...

В transformers пакет, где monads-fd получает экземпляр, мы видим:

instance Monad (Either e) where
    return        = Right
    Left  l >>= _ = Left l
    Right r >>= k = k r

Итак, нет определения для Fail what-so-ever. В общем, fail обескуражен, так как не всегда гарантированно терпеть неудачу в монаде (многие люди хотели бы удалить fail из класса Monad).

EDIT: я должен добавить, что это, конечно, не ясно. fail было намерено оставить в качестве вызова по умолчанию error. Пинг для haskell-кафе или сопровождающего может стоить.

EDIT2: экземпляр mtl был перемещен на базу, этот шаг включает удаление определения fail = Left и обсуждение того, почему это решение было принято. Предположительно, они хотят, чтобы люди использовали ErrorT больше, когда монад терпит неудачу, тем самым сохраняя fail для чего-то более катастрофического, например, неправильного совпадения шаблонов (ex: Just x <- e где e -->* m Nothing).