2011-12-18 15 views
1

少なくとも、私はそれが起こっていると思います。間違ったモナドで処理が失敗する

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'モナドで評価され、決して 'IO'の近くにはありません。省略したコードに何か間違っている必要があります。たとえば、評価コードの別の部分が直接的または間接的に「エラー」を呼び出す可能性があり、IO例外としてバブルアップします。 – ehird

+0

ありがとう、私は完全なコードで投稿を更新しました –

+0

問題は元のコードでも見えていたことが分かりました。私は修正を投稿しました:) – ehird

答えて

3

どのバージョンのbaseをお使いですか? failは、をthe latest version of the Either e monadに戻すようには定義されていません。代わりに、デフォルト定義(which calls error、これはIOでしか捕捉できない例外をスローします)を使用します。

なぜこれが変更されたのかわかりません。

+0

ベース-4.3.1.0。それは私が最新のHaskell Platformに更新したので、間違いなくそれを説明するでしょう。そうであれば、どちらかの代わりに何を使うべきですか? –

1

ああ、私は今問題を見る!

あなたはControl.Monad.Errorをインポートしますが、そのfail定義ではなくLeftを返すよりもerrorを呼び出すEitherモナドを使用しています。

eval testExprrunIdentity . runErrorT $ eval testExprに変更する必要があります。 Data.Functor.Identityをインポートする必要があります。

旧バージョンのmtl(モナドトランスライブラリ)では、いずれかのfailメソッドが実際にLeftを返しました。しかし、問題は、eErrorクラスのインスタンスである場合にのみEither eがモナドであることを許可したことです。 failは一般的に間違いであると考えられているので、これは特に望ましくないと考えられていたと私は信じている。多くの人がモナドのタイプキャンプから移動しなければならないと考えています。

もちろん、別のエラー処理方法を完全に選択することもできますが、これは、ライブラリの最新バージョンで既に動作しているものと最も類似しています。

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の代わりにStringを使用することもできますが、これは単純なErrorTよりも利点がありません。独自のエラータイプを持つ独自のモナドを使用する利点は、noMsg/strMsgの "catch-all"エラー値を定義する必要があるErrorのインスタンスを定義する必要がないことです。

+0

ありがとうございます。これは本当に深刻なコードではありませんが、私が学んだように「うまくやる」ことをしようとしています。そして、「ライブラリ」の一般的な「失敗」を使用して、それが使用されている実装を決定するのに良いようです。その目的のために私自身のエラーモナドを作るだけで何か問題はありますか?このように:http://hpaste.org/55436。 –

+1

'Uncertain'は' ErrorT Identity String'です。私の答えは(どちらかのモナドの代わりに) 'eval'を使っています。一般的に、私はこのようなコードを作成するのは気にしません。コンクリートモナドであっても必要ならば、他のモナドで走るように簡単に適応させることができます。通常は、必要に応じて型板を重ねるのではなく、単一の標準的なモナディックスタックを定義することがより明確になります。 – ehird

+0

'fail'は、大部分のモナドに対して賢明に定義することができず、モナドの数学*定義*の一部ではないため、嫌いです。 Monad型のクラスだけで、 'do {Right x < - m; ...} 'を実行し、パターンマッチが失敗した場合はエラーメッセージを返します。 Haskellの古いバージョンではこれもMonadZeroの制約が追加されましたが、Haskell 98はMonadに '失敗 'して、表面的には初心者の方が簡単になりました。 '彼らのタイプに制約を加えるようにブロックしました。 – ehird

関連する問題