函数式编程的 Monads,2.2 变体一:异常
Posted
技术标签:
【中文标题】函数式编程的 Monads,2.2 变体一:异常【英文标题】:Monads for Functional Programming, 2.2 Variation one: Exceptions 【发布时间】:2018-08-28 06:11:22 【问题描述】:我一直在努力更好地理解 Haskell 中的 Monad,因此我开始阅读 Philip Wadler 的论文 Monads for functional 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”并得出结论,也许我需要为 Show for M a 添加一个实例。但是当我添加
instance Show (M a) where
show (M a) = show a
对于我的代码,我尝试重新加载文件时出现此错误。
Not in scope: data constructor ‘M’
起初这让我很困惑,但 this 帖子解释说我定义的是类型构造函数而不是数据构造函数。
无论如何,我觉得我正在陷入一个可能有也可能没有解决方案的兔子洞,我想我会在这里发布问题。我的代码看起来与他的代码逐行相同。我需要进行哪些更改才能使代码在 GHCi 中运行?
【问题讨论】:
没有数据构造函数M
,只有Raise
和Return
,所以你需要实现这两种情况。
使用deriving
机制的另一个原因——除了节省编写代码——是Haskell 将生成一个正确处理数据优先级和嵌套的实例。所以像show (Return (Con 4))
这样的东西会正确地产生字符串"Return (Con 4)"
而不是"Return Con 4"
(假设Term
有一个正确的Show
实例)。
【参考方案1】:
添加的Show
实例无法工作有两个原因:
-
没有约束表明
M a
的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 <b>deriving Show</b>
【讨论】:
谢谢!那行得通。派生工具非常漂亮,但我明白为什么您可能想要自定义实现。例如,show (Return a)
可能只是 show a
,而 show (Raise a)
大小写为 "Exception: " ++ show a
或其他内容可能是有意义的。【参考方案2】:
如果你想手动完成,你必须做类似的事情
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'
但这很快就会让人讨厌。
【讨论】:
以上是关于函数式编程的 Monads,2.2 变体一:异常的主要内容,如果未能解决你的问题,请参考以下文章