至少我认为这是正在发生的事情。
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:”字符串。
答案 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 e
是e
类的实例时,这只允许Error
成为monad。我认为这被认为是特别不受欢迎的,因为fail
通常被认为是一个错误;许多人认为它应该被移出Monad类型类。
您当然可以选择完全不同的错误处理方法,但这是与您最新版本的库最接近的类似方法。
我建议你在算术模块中专门化你的代码,直接使用ErrorT
和throwError
;作为奖励,这也可以让你捕捉到你在翻译中丢失的错误。
您也可以定义自己的错误类型,在这种情况下,我建议您定义自己使用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}}。