2011-12-18 68 views
1

至少我认为这是怎么回事。失败处理在错误monad

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错误,没有 “错误:” 字符串追加。

+1

这不可能与你在这里介绍的代码; 'eval testExpr'在'Either' monad中评估,并且永远不会接近'IO'。你遗漏的代码必须有错误;例如,您的评估代码的另一部分可能会直接或间接地调用'error',这会冒充IO异常。 – ehird 2011-12-18 18:49:10

+0

谢谢,我已经用完整代码更新了帖子 – 2011-12-18 19:03:54

+0

原来的代码中可以看到问题,我只是错过了;我已经发布了一个修复:) – ehird 2011-12-18 19:14:40

回答

3

您使用的版本是basefail不再定义为在the latest version of the Either e monad中返回Left,而是使用默认定义(which calls error,它引发只能在IO中捕获的异常)。

我不知道为什么这改变了。

+0

Base-4.3.1.0。这肯定会解释它,因为我刚刚更新到最新的Haskell平台。如果是这样的话,我应该用什么来代替Either? – 2011-12-18 19:11:22

1

啊,我现在看到问题了!

要导入Control.Monad.Error,但使用Either单子,其fail清晰的通话error而不是返回Left

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

在旧版本的mtl(monad变换器库)中,任一个的fail方法确实都会返回Left。但是,问题在于当eError类的实例时,这只允许Either e成为单子。我认为这被认为是特别不可取的,因为fail通常被认为是一个错误;许多人认为它应该从Monad类型类中移出。

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

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

你也可以定义自己的错误类型,并在那种情况下,我建议定义自己的单子,使用或者:

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

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

要么的单子实例会工作得很好,在这里;唯一改变的是它的fail的定义。请注意,您需要使用GeneralizedNewtypeDeriving扩展名来派生这些实例。

你当然可以在这里使用字符串而不是EvalError,但是这并不比简单的ErrorT;使用具有自定义错误类型的自己的monad的优点是,您不必定义Error的实例,这需要为noMsg/strMsg定义“全部捕获”错误值。

+0

谢谢。这并不是真正的严谨代码,但我正在努力在学习时“做得很好”,并且在“库”中使用通用“失败”并决定使用它的实现方式似乎很不错。是否有任何问题,只是为了这个目的而制作我自己的错误monad?像这样:http://hpaste.org/55436。 – 2011-12-18 19:33:12

+1

'不确定'只是'ErrorT标识字符串',这是我的答案使用'eval'(而不是任何monad)。总的来说,我不会打扰像这样的通用代码。如果需要的话,它可以很容易地适应其他monad运行,即使是具体的monad也是如此;定义一个单一的,规范的monadic堆栈通常会更清晰,而不是仅仅根据需要对类型类进行分层。 – ehird 2011-12-18 19:38:50

+0

'失败'是不喜欢的,因为它不能为大多数单子明智地定义,并且不是单子数学*定义*的一部分。它只在Monad类型类中,所以你可以说'do {Right x < - m; ...}并在模式匹配失败时得到错误消息;在旧版本的Haskell中,这也增加了一个MonadZero约束,但Haskell 98将“失败”转化为Monad,表面上是为了让初学者更简单,他们可能不明白为什么这样的非全模式匹配'块迫使他们添加约束到他们的类型。 – ehird 2011-12-18 19:40:29

相关问题