Haskell 将不确定性与错误处理相结合

标签 haskell monads monad-transformers non-deterministic

假设我正在创建一个可以抛出错误的简单解释器,例如

type Error = String

data Term = Con Int | Div Term Term

eval :: (MonadError Error m) => Term -> m Int
eval (Con a) = return a
eval (Div u v) = do
  a <- eval u
  b <- eval v
  if b == 0 then
    throwError "Division by zero"
  else
    return $ a `div` b
具体错误处理程序的典型选择是 Either Error .
runEval :: Term -> Either Error Int
runEval = eval
现在假设我想扩展这个解释器来处理非确定性。例如,我可以添加一个术语 Choice Term Term可以评估其第一个或第二个参数。
data Term = Con Int | Div Term Term | Choice Term Term
然后我可以将具体的评估表示为 [Either Error Int] ,其中列表中的每个项目代表一个可能的评估。但是,我正在努力如何添加 Choice给我的案例 eval功能无需修改ConDiv案件。
我试过的:
eval :: (MonadError Error m, MonadPlus m) => Term -> m Int
-- The Con and Div cases remain unchanged.
eval (Choice u v) = do
  w <- return u `mplus` return v  -- pick either u or v
  eval w

runEval :: Term -> [Either Error Int]
runEval = runExceptT . eval
但是,这只会返回第一个可能的结果,并且不会回溯。
> let t = Con 1 `Div` (Choice (Con 0) (Con 1))
> runEval t
[Left "Division by zero"]
我所期望的:[Left "Division by zero", Right 1] .
结合非确定性和错误处理的正确方法是什么?

最佳答案

问题的根源是MonadPlus ExceptT 的实例.

(Monad m, Monoid e) => MonadPlus (ExceptT e m)
它不会搭载在 MonadPlus 上基础 monad 的实例 m .取而代之的是,它需要一个 Monoid来自错误 e 的实例.
mplus不返回所有失败和成功的集合。取而代之的是,它返回第一个成功或所有失败的幺半群组合:
ghci> throwError ['a'] `mplus` throwError ['b'] :: Except String ()
ExceptT (Identity (Left "ab"))
ghci> throwError ['a'] `mplus` throwError ['b'] `mplus` return () :: Except String ()
ExceptT (Identity (Right ()))
ghci> return 'a' `mplus` return 'b' :: ExceptT () [] Char
ExceptT [Right 'a']
我们可以尝试的是定义我们自己的具有 MonadPlus 的 monad我们想要的实例(同时重用从 ExceptTderiving 的所有其他实例,以避免样板)。
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
import Control.Applicative
import Control.Monad
import Control.Monad.Trans
import Control.Monad.Except

newtype OopsT e m a = OopsT { runOopsT :: ExceptT e m a }
    deriving stock (Show)
    deriving newtype (Show,Functor,Applicative,Monad,MonadError e,MonadTrans)

-- We delegate on the Alternative/Monadplus instance of the base monad m
instance MonadPlus m => Alternative (OopsT e m) where
    empty = OopsT (ExceptT empty)       
    OopsT (ExceptT xs) <|> OopsT (ExceptT ys) = OopsT (ExceptT (xs <|> ys)

instance MonadPlus m => MonadPlus (OopsT e m) where
    mzero = empty       
    mplus = (<|>)

runEval :: Term -> [Either Error Int]
runEval = runExceptT . runOopsT . eval
现在它似乎按预期工作:
ghci> let t = Con 1 `Div` (Choice (Con 0) (Con 1))
ghci> runEval t
[Left "Division by zero",Right 1]
MonadPlus 的一个潜在问题OopsT 的实例就是好像不满足v >> mzero = mzero documentation中提到的法律.例如:
ghci> (mzero :: OopsT Char [] Int)
OopsT {runOopsT = ExceptT []}
ghci> throwError 'c' >> (mzero :: OopsT Char [] Int)
OopsT {runOopsT = ExceptT [Left 'c']}
也许我们可以使用等效的 Alternative例如,这似乎不需要该法律?

关于Haskell 将不确定性与错误处理相结合,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/64632558/

相关文章:

haskell - 创建方便类型

haskell - 将 haskell 中的函数列表封装在一个函数中

未找到 Haskell 模块。我的项目的文件结构有什么问题?

haskell - Monads:确定是否可以进行任意转换

haskell - 状态 Monad 的范围

Kotlin 将 List<Triple<String, String, String> 变异为 Triple<List<String>, List<String>, List<String>> 的优雅方式

haskell - 箭头/HXT 和类型签名

haskell - Haskell 中的 ApplicativeDo

haskell - ProxyFast/ProxyCorrect 的 MonadTransControl 实例

haskell - 与 monad 中的值进行模式匹配