2011-12-18 112 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

相關問題