Monad Transformers
组合两个 Monad 的 Monad
本文为 Monad Transformers 的学习笔记。
困境
有时候需要校验输入内容。比如验证是否为符合要求的密码
1 | import Data.Char |
获取输入
1 | -- 因为需要携带值,所以这里使用 Maybe String 而不是 Bool |
校验
1 | askPassphrase :: IO () |
代码中使用 isJust
判断是否合法。在数量比较少的情况下不是什么问题。通常使用 Maybe Monad 是不需要手动判断是否为 Just
。因为有 >>=
。
所以这里是否能使用 Maybe Monad 来避免手动检查呢。
假设去点 isJust
的三行判断代码。从新编写一个函数
1 | askPassphrase :: IO () |
这个 f
的类型是什么呢?
首先 askPassphrase
的类型是 IO ()
。所以 f
需要返回 IO ()
而 f
又接受一个 String
类型的参数。
所以 f
的类型应该是
1 | f :: String -> IO () |
但是这样不能用于 maybe_value >>= f
因为 >>=
的类型是
1 | Prelude> :t (>>=) |
所以要使用 >>=
,f
的类型必须是
1 | f :: String -> Maybe String |
显然两个类型冲突了,所以写不出来。
所以需要一个能够转换 Maybe Monad 和 IO Monad 的类型。
这个就是 Monad Transformers
定义
定义 Maybe Monad 的 Monad Transformer MaybeT
1 | newtype MaybeT m a = MaybeT { runMaybeT :: m (Maybe a) } |
其中 m 可以为任意 Monad
Monad Transformer 本身也是 Monad 。所以需要实现 return
和 >>=
1 | instance Monad m => Monad (MaybeT m) where |
Just
将 a 转化为 Maybe a
类型
return
将 Maybe a
转化为 m (Maybe a)
类型
MaybeT
将 m (Maybe a)
转化为 MaybeT m (Maybe a)
类型。
接着实现 >>=
1 | -- (>>=) 定义中的 m 为 MaybeT m |
为了使用方便,顺便实现相应的 MonadPlus 和 MonadTrans
1 | instance Monad m => MonadPlus (MaybeT m) where |
改写
有了 MaybeT
代码可以这么写
1 | getValidPassphrase :: MaybeT IO String |
没有了检查的过程,因为全都藏在 MaybeT
的 >>=
里面了。