我正在阅读 Graham Hutton 的《Haskell 编程》一书,以下展示的思路流程让我感到困惑。
他使用下面的例子来激励使用单子,并展示应用函子在除法操作中的不足,其中返回类型是 Maybe
,以处理可能出现的 除以零 错误情况。
给定:
data Expr = Val Int | Div Expr Expr
safediv :: Int -> Int -> Maybe Int
safediv _ 0 = Nothing
safediv n m = Just (n `div` m)
eval :: Expr -> Maybe Int
eval (Val n) = pure n --type: Just(n)?
eval (Div x y) = pure safediv <*> eval x <*> eval y --type: Maybe(Maybe Int)?
他接着解释道:
然而,这个定义并不是类型正确的。特别地,函数safediv的类型为Int->Int->Maybe Int,在这种情况下需要一个类型为Int->Int->Int的函数。
即使用自定义定义的函数代替pure safediv也没有帮助,因为这个函数需要有类型Maybe(Int->Int->Int),它没有提供任何方式来指示第二个整数参数为零时的失败。(X)
结论是函数eval不符合适用函子捕捉的有副作用编程模式。适用函子的样式限制我们将纯函数应用于有副作用的参数:eval不符合这种模式,因为用于处理结果值的函数safediv不是纯函数,但本身可能失败。
我不是Haskell程序员,但从eval(Div x y)的类型中看来,它似乎是Maybe(Maybe Int) - 这可以简单地被“压缩”,是吗?(类似于Scala中的flatten或Haskell中的join)。这里真正的问题是什么?
无论x,y是否为Just(s)/Nothing(s),safediv似乎都能正确计算 - 这里唯一的问题是可以适当转换的返回类型。作者如何从他的论点得出这个结论是我难以理解的问题。
适用函子的样式限制我们将纯函数应用于有副作用的参数。
还有,为什么上面标记为(X)的段落会做出这样的声明,当问题似乎只是返回类型不匹配。
我了解适用函子可以用于更有效地链接计算,其中一个的结果不会影响另一个 - 但在这种情况下,我实在不明白失败会发生在哪里,如果只是简单的返回类型修复就能解决问题:
eval (Div x y) = join(pure safediv <*> eval x <*> eval y)
而且 safediv
必须是纯的吗?据我所知,它也可以是类型 F[Maybe]
或 F[Either]
,不是吗?我可能错过了什么吗?我能看到他的思路,但我不确定这是否是达到目的的正确示例。
join
(除了来自Applicative Functor的其他内容之外),它就是一个Monad。 - Will Ness