Как сохранить информацию при сбое?

Я пишу код, который использует монадный преобразователь StateT для отслеживания некоторой информации о состоянии (логирование и т. д.).

Монада, которую я передаю StateT, очень проста:

data CheckerError a = Bad {errorMessage :: Log} | Good a
    deriving (Eq, Show)


instance Monad CheckerError where
    return x = Good x

    fail msg = Bad msg

    (Bad msg) >>= f = Bad msg
    (Good x) >>= f = f x

type CheckerMonad a = StateT CheckerState CheckerError a

Это просто вариант Left и Right.

Меня беспокоит определение fail. В своих вычислениях я получаю много информации внутри этой монады, и я хотел бы сохранить эту информацию даже в случае сбоя. В настоящее время единственное, что я могу сделать, это преобразовать все в String и создать экземпляр Bad с String, переданным в качестве аргумента для fail.

Я хотел бы сделать что-то вроде:

fail msg = do
    info <- getInfoOutOfTheComputation
    return $ Bad info

Однако все, что я пробовал до сих пор, дает ошибки типа, вероятно, потому, что это смешивает разные монады.

Могу ли я каким-либо образом реализовать fail, чтобы сохранить нужную мне информацию, не преобразовывая ее всю в String?

Я не могу поверить, что лучшее, чего может достичь Haskell, это использовать show+read для передачи всей информации в виде строки в fail.


person Bakuriu    schedule 17.04.2014    source источник
comment
Краткий ответ: fail не для этого, поэтому, конечно, это не работает.   -  person Carl    schedule 17.04.2014
comment
@Carl Тогда покажи мне, как мне этого добиться. Что я хочу: 1) способ остановить вычисления, когда произойдет что-то плохое 2) сохранить информацию о состоянии, полученную до этого момента. Все примеры, которые я видел, которые могут достичь 1, используют fail и не достигают числа 2. В настоящее время единственное, что я придумал, это: prettyFail = do state <- get; fail $ show state реализация Show и Read во всей информации о состоянии, что я не думаю, что это элегантно решение (хотя оно прекрасно работает).   -  person Bakuriu    schedule 17.04.2014
comment
fail предназначен только в качестве временной меры для поддержки сбоев сопоставления с образцом в нотации do.   -  person    schedule 17.04.2014


Ответы (1)


Ваша монада CheckerError очень похожа на монаду Either. Я буду использовать монаду Either (и ее аналог монадного преобразователя ErrorT) в моем ответе.

В преобразователях монад есть одна тонкость: порядок имеет значение. Эффекты во «внутренней» монаде имеют приоритет над эффектами, вызванными «внешними» слоями. Рассмотрим эти два альтернативных определения CheckerMonad:

import Control.Monad.State
import Control.Monad.Error

type CheckerState = Int     -- dummy definitions for convenience
type CheckerError = String

type CheckerMonad a = StateT CheckerState (Either String) a

type CheckerMonad' a = ErrorT String (State CheckerState) a

В CheckerMonad Either является внутренней монадой, и это означает, что сбой сотрет все состояние. Обратите внимание на тип этой функции запуска:

runCM :: CheckerMonad a -> CheckerState -> Either CheckerError (a,CheckerState)
runCM m s = runStateT m s

Вы либо терпите неудачу, либо возвращаете результат вместе с состоянием до этого момента.

В CheckerMonad' State является внутренней монадой. Это означает, что состояние будет сохранено даже в случае сбоев:

runCM' :: CheckerMonad' a -> CheckerState -> (Either CheckerError a,CheckerState)
runCM' m s = runState (runErrorT m) s

Возвращается пара, которая содержит состояние до этого момента, а также либо ошибку, либо результат.

Требуется немного практики, чтобы развить интуицию в том, как правильно упорядочивать преобразователи монад. Диаграмма в разделе Жонглирование шрифтами на эта страница Викиучебника является хорошей отправной точкой.

Кроме того, лучше избегать использования fail напрямую, потому что в языке это считается чем-то вроде бородавки. Вместо этого используйте специализированные функции для выдачи ошибок, предоставляемые преобразователем ошибок. При работе с ErrorT или другим экземпляром MonadError используйте throwError.

sillycomp :: CheckerMonad' Bool
sillycomp = do
    modify (+1)
    s <- get 
    if s == 3
        then throwError "boo"
        else return True

*Main> runCM' sillycomp 2
Loading package transformers-0.3.0.0 ... linking ... done.
Loading package mtl-2.1.2 ... linking ... done.
(Left "boo",3)

*Main> runCM' sillycomp 3
(Right True,4)

ErrorT иногда неудобно использовать, потому что, в отличие от Either, он требует Error ограничение на тип ошибки. Класс типов Error заставляет вас определять два конструктора ошибок noMsg и strMsg, которые могут иметь или не иметь смысла для вашего типа.

Вы можете использовать EitherT из пакет either, который позволяет использовать любой тип ошибки. При работе с EitherT< /a> используйте функцию left для выдачи ошибок.

person danidiaz    schedule 17.04.2014