函数式编程的 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,只有RaiseReturn,所以你需要实现这两种情况。 使用deriving 机制的另一个原因——除了节省编写代码——是Haskell 将生成一个正确处理数据优先级和嵌套的实例。所以像show (Return (Con 4)) 这样的东西会正确地产生字符串"Return (Con 4)" 而不是"Return Con 4"(假设Term 有一个正确的Show 实例)。 【参考方案1】:

添加的Show 实例无法工作有两个原因:

    没有约束表明M aaShow 的一个实例;和 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) 有两个数据构造函数,RaiseReturn

简单的解决办法就是说

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 变体一:异常的主要内容,如果未能解决你的问题,请参考以下文章

Monads - 定义,法律和例子

高阶函数式编程三:在 Kotlin 中“实现”应用函子(Applicative)

Swift函数式编程十三(函子适用函子单子)

Swift函数式编程十三(函子适用函子单子)

Swift函数式编程十三(函子适用函子单子)

函数式编程进阶:应用函子