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 的 >>= 里面了。