在错误的monad中处理失败

时间:2011-12-18 18:45:45

标签: haskell

至少我认为这是正在发生的事情。

Main.hs:

module Main (
    main
) where

import Arithmetic
import Data.Maybe
import Data.Either
import Control.Monad.Error

testExpr :: Expr Float
testExpr = 
        (MultExpr "*"
            (AddExpr "XXX"
                (NumExpr 1) 
                (AddExpr "-"
                    (NumExpr 24)
                    (NumExpr 21)
                )
            ) 
            (NumExpr 5) 
        )

main :: IO ()
main = do 
    putStrLn $ case eval testExpr of
            Left msg -> "Error: " ++ msg
            Right result -> show result

Arithmetic.hs:

{-# LANGUAGE GADTs #-}

module Arithmetic  where

type Op = String

data Expr a where
    NumExpr :: Float -> Expr Float
    AddExpr :: Op -> Expr Float -> Expr Float -> Expr Float
    MultExpr :: Op -> Expr Float -> Expr Float -> Expr Float

eval :: (Monad m) => Expr Float -> m Float
eval (NumExpr n) = return n
eval (AddExpr "+" e1 e2) = evalBin (+) e1 e2
eval (AddExpr "-" e1 e2) = evalBin (-) e1 e2
eval (AddExpr "%" e1 e2) = evalBin (%) e1 e2
eval (AddExpr _ _ _ ) = fail "Invalid operator. Expected +, - or %"
eval (MultExpr "*" e1 e2) = evalBin (*) e1 e2
eval (MultExpr "/" e1 e2) = evalBin (/) e1 e2
eval (MultExpr _ _ _ ) = fail "Invalid operator. Expected * or /"

evalBin :: (Monad m) => (Float -> Float -> Float) -> Expr Float -> Expr Float -> m Float
evalBin op e1 e2 = do 
  v1 <- eval e1
  v2 <- eval e2
  return $ op v1 v2

infixl 6 %
(%) :: Float -> Float -> Float
a % b = a - b * (fromIntegral $ floor (a / b))

但是,当eval失败时,我在IO中收到错误,没有附加“Error:”字符串。

2 个答案:

答案 0 :(得分:3)

您使用的是base的哪个版本? fail不再定义为在the latest version of the Either e monad中返回Left,而是使用默认定义(which calls error,它会抛出只能在IO中捕获的异常)。

我不知道为什么会改变。

答案 1 :(得分:1)

啊,我现在看到了问题!

您要导入Control.Monad.Error,但使用Either monad,其fail定义调用error而不是返回Left

您需要做的是将eval testExpr更改为runIdentity . runErrorT $ eval testExpr。您需要导入Data.Functor.Identity

在旧版本的mtl(monad变换器库)中,Either的fail方法确实返回Left。但是,问题是当Either ee类的实例时,这只允许Error成为monad。我认为这被认为是特别不受欢迎的,因为fail通常被认为是一个错误;许多人认为它应该被移出Monad类型类。

您当然可以选择完全不同的错误处理方法,但这是与您最新版本的库最接近的类似方法。

我建议你在算术模块中专门化你的代码,直接使用ErrorTthrowError;作为奖励,这也可以让你捕捉到你在翻译中丢失的错误。

您也可以定义自己的错误类型,在这种情况下,我建议您定义自己使用Either的monad:

newtype Eval a = Eval { runEval :: Either EvalError a }
  deriving (Functor, Applicative, Monad)

evalError :: EvalError -> Eval a
evalError e = Eval (Left e)

要么monad实例在这里工作得很好;唯一改变的是fail的定义。请注意,您需要GeneralizedNewtypeDeriving扩展名才能派生这些实例。

你当然可以在这里使用String而不是EvalError,但是这比简单的ErrorT没有任何好处;使用自己的monad和自定义错误类型的优点是您不必定义Error的实例,这需要为noMsg / {定义“catch-all”错误值{1}}。