Haskell:从Maybe Monad理解Monad
Monad的原始定义
-- Monad的原始定义
class Monad m where
return :: a -> m a
(>>=) :: m a -> (a -> m b) -> m b
(>>) :: m a -> m b -> m b
x >> y = x >>= \_ -> y
fail :: String -> m a
fail msg = error msg
return
就是给原始类型套一个构造性类型的壳,比如在Maybe
中return x = Just x
就是把a
包装成Maybe a
。
fail
用error
实现,很好理解,是为了将报错作为一个副作用进行,从而保证函数在报错的情况下仍然能返回一个类型正确的值。
以下通过Maybe
中的Monad
实现,解释一下>>=
和>>
为什么设计成这样,在实际使用时有什么效果。
Monads are just applicative functors that support >>=. (The >>= function is pronounced as bind.)
Maybe的Monad实现
-- Maybe 的 Monad instance
instance Monad Maybe where
return x = Just x
Nothing >>= f = Nothing
Just x >>= f = f x
fail _ = Nothing
-- 此时
-- Just x >> y = y
-- Nothing >> _ = Nothing
return
用来套壳,>>=
用来实现连续传值(下面详细讲),fail
使Maybe
可以用自己的方式(返回Nothing
)实现异常处理。
理解>>=
考虑>>=
在大部分时间的使用方式:Just 1 >>= fun1 >>= fun2 >>= ... >>= funn
,即一个值通过函数求出结果,再把结果作为下一个函数的输入。当我们使用Maybe
时,我们肯定希望每一步的值都是用Just
包裹的,安全的值。所以,>>=
的第一个输入类型是Maybe a
,最终输出类型是Maybe b
,这一点是很合理的。
主要的问题是,参数f
的类型为什么是a -> mb
?
- 为什么不是
m a -> m b
?因为我们希望f
的输入值本身在类型上没有问题。a -> mb
使得f
的输入是一个纯值,这个纯值的安全性由>>=
的implement保证。此时,输入类型不允许maybe,就保证输入值是有意义的(不是Nothing
)。此时,f
只需要处理函数功能上的异常(比如除法div
函数不允许输入0),而m a
可能存在的异常(Nothing
)之前已经由>>=
处理了:>>=
在解绑Just
的时候如果发现异常(Nothing
)就会直接停止求值而返回Nothing
。 - 为什么不是
a -> b
?因为我们希望返回值反映f
函数内部功能上的异常:针对功能的异常处理是f
内部逻辑的一部分。比如除法div
函数,0
是一个类型正常的输入值,但是不能正确执行除法,所以依托Maybe
类型返回Nothing
.
在理清>>=
的类型为什么设计之后,注意到>>
的实现,也能知道>>
的目的:在一系列处理值中,一旦有一个失败就返回Nothing
。
总结
Maybe
就是为了表示异常而设立的一种结构,而Monad
创立的作用就是减少很多重复的异常处理代码。Maybe Monad
把函数连续调用时的异常传递的判断逻辑隔离了出来,从而减少了很多代码量,也能使得设计函数时更注意函数本身的逻辑和安全,而不用过分考虑输出值。