Haskell: Почему типы Maybe и Either ведут себя по-разному при использовании в качестве монад? - PullRequest
13 голосов
/ 26 сентября 2010

Я пытаюсь разобраться с обработкой ошибок в 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

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

Что дает?

Ответы [ 2 ]

15 голосов
/ 27 сентября 2010

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

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

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

Полагаю, вы используете 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 вообще.В целом, fail не рекомендуется, так как не всегда гарантируется, что в монаде произойдет сбой (многие люди хотели бы видеть fail удаленным из класса Monad).

РЕДАКТИРОВАТЬ: я должен добавить, чтоконечно, неясно, что fail предполагалось оставить как вызов по умолчанию error.Пинг в haskell-cafe или сопровождающего может стоить того.

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

...