Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/haskell/9.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Haskell将非确定性与错误处理相结合_Haskell_Monads_Monad Transformers_Non Deterministic - Fatal编程技术网

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

Haskell将非确定性与错误处理相结合,haskell,monads,monad-transformers,non-deterministic,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 "

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

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
假设现在我想扩展这个解释器来处理非确定性。例如,我可以添加一个术语
Choice-term-term
,该术语可以对其第一个或第二个参数求值

data Term = Con Int | Div Term Term | Choice Term Term
然后,我可以将一个具体的评估表示为
[orry Error Int]
,其中列表中的每个项目都表示一个可能的评估。但是,我正在努力如何在不修改
Con
Div
案例的情况下,将
Choice
案例添加到我的
eval
函数中

我尝试的是: 我所期望的:
[左“除零”,右1]


将非确定性和错误处理结合起来的正确方法是什么?

问题的根源是MonadPlus的
实例

它不依赖于基本monad
m
MonadPlus
实例。相反,它需要错误
e
中的
Monoid
实例

而且
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']
我们可以尝试的是定义我们自己的monad,它具有我们想要的
MonadPlus
实例(同时重用
ExceptT
派生的所有其他实例,以避免样板文件)


OopsT
MonadPlus
实例的一个潜在的令人不安的方面是,它似乎不满足本节中提到的
v>>mzero=mzero
定律。例如:

ghci> (mzero :: OopsT Char [] Int)
OopsT {runOopsT = ExceptT []}
ghci> throwError 'c' >> (mzero :: OopsT Char [] Int)
OopsT {runOopsT = ExceptT [Left 'c']}

也许我们可以使用等效的
替代
实例,这似乎不需要该定律?

您可以看看monad transformers。这或多或少用于创建单子上下文的“堆栈”。@WillemVanOnsem谢谢您的评论。实际上,我们的目标是使用转换器将错误monad和列表monad组合起来。这实际上就是我在
runEval
中所做的:我使用
runExceptT
,这意味着Haskell将有效地使用monad
ExceptT Error[/Int
进行计算。然而,正如我的问题所解释的,它并没有像我期望的那样工作。目前,你的
eval
确实存在
ExceptT Error[]Int
,但你想要的是
ListT(ExceptT Error)Int
,我认为。@ArtemPelenitsyn With
ListT(ExceptT Error)
一个错误就会停止计算
runEval
会有一个类似于
Term->Error[Int]
的签名。很好的解释。请问:
Oops
的预期完整形式是什么?@Safron我不确定你所说的“完整形式”是什么意思
Oops
是一个常见模式的示例:一个辅助的newtype,我们有选择地在其上派生或定义实例。我们从底层类型派生了很多有用的实例(从
Functor
MonadTrans
),这节省了我们的工作,并且必须手动定义两个实例(
Alternative
MonadPlus
),以满足我们的需要。对,我认为这是某种简化,但现在我明白了,它可能只是“可能失败的东西”的同义词,也就是“oops”。我的错:-)一个问题:runEval$Div(Div(Con 1)(Con 0))(Choice(Con 1)(Con 2))的结果是什么?结果中有一两处错误吗?(一个合法的实例应该有2)一个相关的SO问题和一个来自Haskell Wikibook的条目,讨论
备选方案
MonadPlus
> let t = Con 1 `Div` (Choice (Con 0) (Con 1))
> runEval t
[Left "Division by zero"]
(Monad m, Monoid e) => MonadPlus (ExceptT e m)
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']
{-# 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]
ghci> (mzero :: OopsT Char [] Int)
OopsT {runOopsT = ExceptT []}
ghci> throwError 'c' >> (mzero :: OopsT Char [] Int)
OopsT {runOopsT = ExceptT [Left 'c']}