我一直在努力更好地理解 Haskell 中的 Monad,因此我开始阅读 Philip Wadler 的论文 Monads for function programming。为了更好地内化这些原则,并让自己接触尽可能多的 Haskell 代码,我决定在阅读本文时编写代码并测试所有示例。
马上 2.2 变体一:异常给我带来了一些麻烦。这是我的代码。
data Term = Con Int | Div Term Term
data M a = Raise Exception | Return a
type Exception = String
answer, error_ :: Term
answer = (Div (Div (Con 1972) (Con 2)) (Con 23))
error_ = (Div (Con 1) (Con 0))
eval :: Term -> M Int
eval (Con a) = Return a
eval (Div t u) = case eval t of
Raise e -> Raise e
Return a ->
case eval u of
Raise e -> Raise e
Return b ->
if b == 0
then Raise "divide by zero"
else Return (a `div` b)
我可以将代码加载到 GHCi 中,但是当我尝试运行时
eval answer
它抛出错误
No instance for (Show (M Int)) arising from a use of ‘print’
In a stmt of an interactive GHCi command: print it
我阅读了this帖子,其中解释了为什么调用“print”,并得出结论,也许我需要为 M a 添加一个实例。但是当我添加
instance Show (M a) where
show (M a) = show a
对于我的代码,我尝试重新加载文件,但出现此错误。
Not in scope: data constructor ‘M’
这起初让我感到困惑,但this帖子解释说我正在定义类型构造函数而不是数据构造函数。
无论如何,我觉得我正在掉入一个兔子洞,可能有也可能没有解决方案,我想我应该在这里发布问题。我的代码看起来与他的代码逐行相同。我需要进行哪些更改才能使代码在 GHCi 中运行?
添加的
Show
实例将无法工作,原因有两个:
a
的 M a
是 Show
的实例;和M
数据类型没有数据构造函数M
,它有一个Raise
和一个Return
。因此,一个简单的实现如下所示:
instance Show a => Show (M a) where
show (Raise a) = "Raise " ++ show a
show (Return a) = "Return " ++ show a
但是我们可以省去这个麻烦,让 Haskell 自动派生出
Show
的实例:
data M a = Raise Exception | Return a deriving Show
如果您想手动执行此操作,则必须执行以下操作
instance (Show a) => Show (M a) where
show (Raise e) = "Raise " ++ show e
show (Return a) = "Return " ++ show a
您的类型 (
M a
) 有两个数据构造函数,Raise
和 Return
。
最简单的解决方案就是直接说
data M a = Raise Exception | Return a
deriving (Show)
或者,您也可以在每次想要在 ghci 中检查
M a
值时手动解构它们:
case eval answer of { Raise e -> "An error occurred: " ++ e; Return x -> show x }
-- instead of 'eval answer'
但这很快就会变得烦人。