Haskell语言学习笔记(22)MaybeT
Monad Transformers
Monad 转换器用于将两个不同的Monad合成为一个Monad。Monad 转换器本身也是一个 Monad。
MaybeT
MaybeT 这个 Monad 转换器通过将 Maybe Monad 封装进其它 Monad来使两者合二为一。
newtype MaybeT m a = MaybeT { runMaybeT :: m (Maybe a) }
instance (Monad m) => Monad (MaybeT m) where
return = lift . return
x >>= f = MaybeT $ do
v <- runMaybeT x
case v of
Nothing -> return Nothing
Just y -> runMaybeT (f y)
- newtype MaybeT m a = MaybeT { runMaybeT :: m (Maybe a) }
MaybeT 类型是个 newtype,也就是对现有类型的封装。该类型有两个类型参数:内部 Monad 类型 m 以及基础 Monad Maybe 的参数类型 a。
MaybeT m a 封装了一个 m (Maybe a) 类型的值,通过 runMaybeT 字段可以取出这个值。 - instance (Monad m) => Monad (MaybeT m) where
如果 m 是个 Monad,那么 MaybeT m 也是一个 Monad。
对比 Monad 类型类的定义,可知 return 函数的类型签名为:
return :: a -> MaybeT m a
大致相当于 a -> m (Maybe a)
而 bind 操作符的类型签名为:
(>>=) :: MaybeT m a -> (a -> MaybeT m b) -> MaybeT m b
大致相当于 m (Maybe a) -> (a -> m (Maybe b)) -> m (Maybe b) - return = lift . return
return 函数首先将类型为 a 的值封装进内部 Monad m 中,然后通过 lift 函数将它封装进 MaybeT 这个 Monad 转换器之中。
这里左侧的 return 是 MaybeT 这个 Monad 的 return,而右侧的 return 是内部 Monad m 的 return。 - x >>= f = MaybeT $ do
对比函数签名,可知 x 的类型是 MaybeT m a,大致相当于 m (Maybe a)
而 f 的类型是 a -> MaybeT m b,大致相当于 a -> m (Maybe b) - v <- runMaybeT x
对比 x 的类型,可知 v 的类型是 Maybe a
这是因为 runMaybeT 函数让 x 脱离了 MaybeT Monad, 而 <- 运算符又让 runMaybeT x 脱离了内部 Monad m。 - case v of
- Nothing -> return Nothing
这里 return 是内部 Monad m 的 return,所以 return Nothing 的类型是 m (Maybe a)。 - Just y -> runMaybeT (f y)
f 的类型是 a -> MaybeT m b
所以 f y 的类型是 MaybeT m b
而 runMaybeT (f y) 的类型是 m (Maybe b)
Prelude> :m +Control.Monad.Trans.Maybe
Prelude Control.Monad.Trans.Maybe> runMaybeT $ (return 1 :: MaybeT IO Int)
Just 1
Prelude Control.Monad.Trans.Maybe> runMaybeT $ (return 1 :: MaybeT [] Int)
[Just 1]
Prelude Control.Monad.Trans.Maybe> runMaybeT $ (return 1 :: MaybeT IO Int) >>= \x -> return (x * 2)
Just 2
Prelude Control.Monad.Trans.Maybe> runMaybeT $ (return 1 :: MaybeT [] Int) >>= \x -> return (x * 2)
[Just 2]
lift
class MonadTrans t where
lift :: (Monad m) => m a -> t m a
instance MonadTrans MaybeT where
lift = MaybeT . liftM Just
lift 函数的作用为提升已经被封装进内部 Monad 类型 m 的值,让后者进一步被封装进 Monad 转换器 t 类型中。
对于 MaybeT 这个 Monad 转换器,lift 函数的具体实现为:
首先调用 liftM Just 把内嵌在内部 Monad m 中的值装入Maybe Monad,
然后调用 MaybeT 构造器将整个值装入MaybeT Monad。
Prelude Control.Monad.Trans.Maybe Control.Monad.Trans> runMaybeT $ return 1 >>= lift . print
1
Just ()
Prelude Control.Monad.Trans.Maybe Control.Monad.Trans> runMaybeT $ return 1 >>= \x -> do {lift(print x); return (x * 2)}
1
Just 2
Prelude Control.Monad.Trans.Maybe Control.Monad.Trans> runMaybeT $ return 1 >>= lift . (\x -> [x, x + 1])
[Just 1,Just 2]
证明 MaybeT m 符合 Monad 法则。
1. return a >>= f ≡ f a
return a >>= f
≡ (lift . return) a >>= f
≡ lift (m a) >>= f
≡ (MaybeT . liftM Just) (m a) >>= f
≡ MaybeT (m (Just a)) >>= f
≡ MaybeT $ runMaybeT (f a)
≡ f a
2. m >>= return ≡ m
MaybeT (m (Just x)) >>= return
≡ MaybeT $ runMaybeT (return x)
≡ MaybeT (m (Just x))
MaybeT (m Nothing) >>= return
≡ MaybeT $ (return Nothing)
≡ MaybeT (m Nothing)
3. (m >>= f) >>= g ≡ m >>= (\x -> f x >>= g)
(MaybeT (m (Just x)) >>= f) >>= g ≡ f x >>= g
(MaybeT (m Nothing) >>= f) >>= g ≡ MaybeT (m Nothing) >>= g ≡ MaybeT (m Nothing)
MaybeT (m (Just x) >>= (\x -> f x >>= g) ≡ (\x -> f x >>= g) x ≡ f x >>= g
MaybeT (m Nothing) >>= (\x -> f x >>= g) ≡ MaybeT (m Nothing)
liftIO
class (Monad m) => MonadIO m where
liftIO :: IO a -> m a
instance MonadIO IO where
liftIO = id
instance (MonadIO m) => MonadIO (MaybeT m) where
liftIO = lift . liftIO
liftIO 函数的作用为提升已经被封装进 IO Monad 的值,让后者进一步被封装进 Monad 转换器 m 类型中。
(前提是Monad 转换器 m 类型必须内部封装了 IO Monad。)
对于 MaybeT IO 这个 Monad 转换器,liftIO 作用等同于 lift。
Prelude Control.Monad.Trans.Maybe Control.Monad.Trans> runMaybeT $ return 1 >>= liftIO . print
1
Just ()
Prelude Control.Monad.Trans.Maybe Control.Monad.Trans> runMaybeT $ return 1 >>= \x -> do {liftIO(print x); return (x * 2)}
1
Just 2
lift 与 liftIO 的法则
1. lift . return ≡ return
liftIO . return ≡ return
2. lift (m >>= f) ≡ lift m >>= (lift . f)
liftIO (m >>= f) ≡ liftIO m >>= (liftIO . f)
证明 MaybeT 中 lift 函数的定义符合 lift 的法则。
1. lift . return ≡ return
根据 MaybeT 中 return 函数的定义
return ≡ lift . return
2. lift (m >>= f) ≡ lift m >>= (lift . f)
假设 m = n a 并且 f a = n b
于是 m >>= f = n b
lift (m >>= f)
≡ MaybeT . liftM Just $ m >>= f
≡ MaybeT . liftM Just $ n b
≡ MaybeT (n (Just b))
lift m >>= (lift . f)
≡ (MaybeT . liftM Just $ m) >>= (MaybeT . liftM Just . f)
≡ (MaybeT (n (Just a))) >>= (\x -> MaybeT . liftM Just . f $ x)
≡ MaybeT $ runMaybeT $ MaybeT . liftM Just . f $ a
≡ MaybeT $ runMaybeT $ MaybeT . liftM Just $ n b
≡ MaybeT $ runMaybeT $ MaybeT (n (Just b))
≡ MaybeT (n (Just b))
MaybeT 是 Functor 也是 Applicative
instance (Functor m) => Functor (MaybeT m) where
fmap f = mapMaybeT (fmap (fmap f))
mapMaybeT :: (m (Maybe a) -> n (Maybe b)) -> MaybeT m a -> MaybeT n b
mapMaybeT f = MaybeT . f . runMaybeT
instance (Functor m, Monad m) => Applicative (MaybeT m) where
pure = lift . return
mf <*> mx = MaybeT $ do
mb_f <- runMaybeT mf
case mb_f of
Nothing -> return Nothing
Just f -> do
mb_x <- runMaybeT mx
case mb_x of
Nothing -> return Nothing
Just x -> return (Just (f x))
fmap f x
= mapMaybeT (fmap (fmap f)) x
= MaybeT . (fmap (fmap f)) . runMaybeT $ x
= MaybeT . (fmap (fmap f)) $ m (Maybe a)
= MaybeT (n (Maybe b))
Prelude Control.Monad.Trans.Maybe> runMaybeT $ (*2) <$> (return 1 :: MaybeT IO Int)
Just 2
Prelude Control.Monad.Trans.Maybe> runMaybeT $ (*2) <$> (return 1 :: MaybeT [] Int)
[Just 2]
Prelude Control.Monad.Trans.Maybe> runMaybeT $ (*) <$> (return 1 :: MaybeT IO Int) <*> return 2
Just 2
Prelude Control.Monad.Trans.Maybe> runMaybeT $ (*) <$> (return 1 :: MaybeT [] Int) <*> return 2
[Just 2]
MaybeT 是 Alternative 也是 MonadPlus
instance (Functor m, Monad m) => Alternative (MaybeT m) where
empty = MaybeT (return Nothing)
x <|> y = MaybeT $ do
v <- runMaybeT x
case v of
Nothing -> runMaybeT y
Just _ -> return v
instance (Monad m) => MonadPlus (MaybeT m) where
mzero = MaybeT (return Nothing)
mplus x y = MaybeT $ do
v <- runMaybeT x
case v of
Nothing -> runMaybeT y
Just _ -> return v
Prelude Control.Monad.Trans.Maybe Control.Applicative> runMaybeT $ (empty :: MaybeT IO Int) <|> return 2
Just 2
Prelude Control.Monad.Trans.Maybe Control.Monad> runMaybeT $ (mzero :: MaybeT IO Int) `mplus` return 2
Just 2
应用实例
import Control.Monad (guard)
import Control.Monad.Trans (lift)
import Control.Monad.Trans.Maybe (MaybeT(..), runMaybeT)
import Data.Char
funA :: MaybeT IO String
funA = do
lift $ putStrLn "What is your name?"
name <- lift getLine
guard $ not (null name)
return name
funB :: String -> MaybeT IO String
funB name = do
lift $ putStrLn ("hello, " ++ name)
lift $ putStrLn "how old are you?"
age <- lift getLine
guard (all isDigit age)
return age
main :: IO (Maybe String)
main = runMaybeT $ do
a <- funA
funB a
*Main> main
What is your name?
Steven
hello, Steven
how old are you?
20
Just "20"
*Main> main
What is your name?
Steven
hello, Steven
how old are you?
Just ""
*Main> main
What is your name?
Nothing
Haskell语言学习笔记(22)MaybeT的更多相关文章
- Haskell语言学习笔记(88)语言扩展(1)
ExistentialQuantification {-# LANGUAGE ExistentialQuantification #-} 存在类型专用的语言扩展 Haskell语言学习笔记(73)Ex ...
- Haskell语言学习笔记(79)lambda演算
lambda演算 根据维基百科,lambda演算(英语:lambda calculus,λ-calculus)是一套从数学逻辑中发展,以变量绑定和替换的规则,来研究函数如何抽象化定义.函数如何被应用以 ...
- Haskell语言学习笔记(69)Yesod
Yesod Yesod 是一个使用 Haskell 语言的 Web 框架. 安装 Yesod 首先更新 Haskell Platform 到最新版 (Yesod 依赖的库非常多,版本不一致的话很容易安 ...
- Haskell语言学习笔记(20)IORef, STRef
IORef 一个在IO monad中使用变量的类型. 函数 参数 功能 newIORef 值 新建带初值的引用 readIORef 引用 读取引用的值 writeIORef 引用和值 设置引用的值 m ...
- Haskell语言学习笔记(39)Category
Category class Category cat where id :: cat a a (.) :: cat b c -> cat a b -> cat a c instance ...
- Haskell语言学习笔记(72)Free Monad
安装 free 包 $ cabal install free Installed free-5.0.2 Free Monad data Free f a = Pure a | Free (f (Fre ...
- Haskell语言学习笔记(44)Lens(2)
自定义 Lens 和 Isos -- Some of the examples in this chapter require a few GHC extensions: -- TemplateHas ...
- Haskell语言学习笔记(38)Lens(1)
Lens Lens是一个接近语言级别的库,使用它可以方便的读取,设置,修改一个大的数据结构中某一部分的值. view, over, set Prelude> :m +Control.Lens P ...
- Haskell语言学习笔记(92)HXT
HXT The Haskell XML Toolbox (hxt) 是一个解析 XML 的库. $ cabal install hxt Installed hxt-9.3.1.16 Prelude&g ...
随机推荐
- centos6 yum安装最新版mysql5.7
在看来mysql5.7诸多改进介绍后,决定也安装一个试用下:本文将使用rpm的方式来安装. 环境:OS: CentOS6.5 x86_64 最小化安装MEM: 1GCPU: 1 1. 本文连着上一篇安 ...
- bzoj 4556 [Tjoi2016&Heoi2016]字符串——后缀数组+主席树
题目:https://www.lydsy.com/JudgeOnline/problem.php?id=4556 本来只要查 ht[ ] 数组上的前驱和后继就行,但有长度的限制.可以二分答案解决!然后 ...
- Java之 Hashtable
Hashtable是Java中键值对数据结构的实现.您可以使用“键”存储和检索“值”,它是存储值的标识符.显然“关键”应该是独一无二的. java.util.Hashtable扩展Dictionary ...
- ASP.NET Web Pages:表单
ylbtech-.Net-ASP.NET Web Pages:表单 1.返回顶部 1. ASP.NET Web Pages - HTML 表单 表单是 HTML 文档中放置输入控件(文本框.复选框.单 ...
- JSON: JSON 用法
ylbtech-JSON: JSON 用法 1. JSON Object creation in JavaScript返回顶部 1. <!DOCTYPE html> <html> ...
- scrapy 抓取拉勾网数据
其实很简单,却因为一些小问题,折腾不少时间,简要记录一下,以备后需. >> scrapy startproject lagou >> cd lagou >> scr ...
- Django1.7开发博客
转自: http://www.pycoding.com/articles/category/django 基于最新的django1.7写的,通俗易懂,非常适合新手入门. 感谢博主! 参考教程: htt ...
- 不设目标也能通关「马里奥」的AI算法,全靠好奇心学习
在强化学习中,设计密集.定义良好的外部奖励是很困难的,并且通常不可扩展.通常增加内部奖励可以作为对此限制的补偿,OpenAI.CMU 在本研究中更近一步,提出了完全靠内部奖励即好奇心来训练智能体的方法 ...
- Solr如何使用in语法查询
Solr可以用AND.|| 布尔操作符 表示查询的并且, 用OR.&& 布尔操作符 表示或者 用NOT.!.-(排除操作符不能单独与项使用构成查询)表示非 如果要用在查询的时候使用 ...
- 求m-n之间数字的和
unction sum(m,n){ var sum = 0; if(m>n){ for(var i=n; i<=m; i++ ...