MonadCont 类型类

  1. class Monad m => MonadCont m where
  2. callCC :: ((a -> m b) -> m a) -> m a
  3. instance MonadCont (ContT r m) where
  4. callCC = ContT.callCC
  • class Monad m => MonadCont m where

    MonadState 是个类型类,它为 ContT 等封装了CPS函数的 Monad 定义了通用接口。

    MonadState 只包含 callCC 一个函数。该函数为CPS函数提供了显式的流程控制功能。
  • instance MonadCont (ContT r m) where

    callCC = ContT.callCC

    对于 ContT 这个Monad转换器来说,callCC 函数的定义由 ContT 模块来提供。注意这里点运算符的含义不是函数的合成而是受限名字。

    Hackage - Where is the MonadReader implementation for ReaderT defined?

ContT Monad转换器

  1. newtype ContT r m a = ContT { runContT :: (a -> m r) -> m r }
  2. instance Monad (ContT r m) where
  3. return x = ContT ($ x)
  4. m >>= k = ContT $ \c -> runContT m (\x -> runContT (k x) c)
  • newtype ContT r m a = ContT { runContT :: (a -> m r) -> m r }

    ContT 类型是个 newtype,也就是对现有类型的封装。该类型有三个类型参数:内部 Monad 类型参数 m,中间结果类型参数 a 以及最终结果类型参数 r。

    ContT r m a 类型封装了一个CPS函数:\k -> m r(通常形式为 \k -> k a),通过 runContT 字段可以从 ContT 类型中取出这个函数。
  • instance Monad (ContT r m) where

    ContT r m 是一个 Monad。

    对比 Monad 类型类的定义,可知 return 函数的类型签名为:

    return :: a -> ContT r m a

    大致相当于 a -> (a -> m r) -> m r

    而 bind 操作符的类型签名为:

    (>>=) :: ContT r m a -> (a -> ContT r m b) -> ContT r m b

    大致相当于 (a -> m r) -> m r -> (a -> (b -> m r) -> m r) -> (b -> m r) -> m r
  • return x = ContT ($ x)

    return 函数把 a 类型的值 x 封装进了 ContT Monad 中。

    return x = ContT $ \k -> k x

    这里 x 的类型为 a, k 的类型为 a -> m r,k x 的类型为 m r,

    \k -> k x 的类型为 (a -> m r) -> m r,

    ContT $ \k -> k x 也就是 ContT ($ x) 的类型为 ContT r m a。
  • m >>= k = ContT $ \c -> runContT m (\x -> runContT (k x) c)

    对比函数签名,可知 m 的类型为 ContT r m a,大致相当于 (a -> m r) -> m r。

    而 k 的类型为 a -> ContT r m b,大致相当于 a -> (b -> m r) -> m r。

    x 的类型为 a,c 的类型为 b -> m r。

    k x 的类型 ContT r m b,

    runContT (k x) 的类型为 (b -> m r) -> m r,

    runContT (k x) c 的类型为 m r。

    \x -> runContT (k x) c 的类型为 a -> m r,

    runContT m 的类型为 (a -> m r) -> m r,

    runContT m (\x -> runContT (k x) c) 的类型为 m r。

    \c -> runContT m (\x -> runContT (k x) c) 的类型为 (b -> m r) -> m r,

    ContT $ \c -> runContT m (\x -> runContT (k x) c) 的类型为 ContT r m b。
  • m >>= k = ContT $ \c -> runContT m (\x -> runContT (k x) c)

    bind 操作符组合两个封装了CPS函数的 Cont Monad,计算结果仍然是一个封装了CPS函数的 Cont Monad。

    这里假设 m = ContT c1 where c1 f1 = f1 a,k = \a -> ContT c2 where c2 f2 = f2 b。

    bind 操作符具体计算流程如下:

    首先通过 runContT m 把封装在 bind 操作符的左操作数 m 中的CPS函数 c1 取出来,

    c1 函数将把类型为 a 的运算结果 x 传递给 f1,也就是 (\x -> runContT (k x) c) 这个函数,得到 runContT (k x) c 这个运算结果。

    然后 runContT (k x) 再把 bind 操作符的右操作数 k 中所封装的CPS函数 c2 取出来。

    c2 函数将把类型为 b 的运算结果 y 传递给 f2, 也就是最外层的函数 c,得到最终运算结果 c y。

    最后通过 ContT $ \c -> c y 这一形式,把计算结果重新封装进了 ContT Monad。
  1. 证明ContT符合Monad法则:
  2. 1. return a >>= f f a
  3. return a >>= f
  4. ContT (\k -> k a) >>= f
  5. ContT $ \c -> runContT (ContT (\k -> k a)) (\x -> runContT (f x) c)
  6. ContT $ \c -> (\k -> k a) (\x -> runContT (f x) c)
  7. ContT $ \c -> (\x -> runContT (f x) c) a
  8. ContT $ \c -> runContT (f a) c
  9. ContT $ runContT (f a)
  10. f a
  11. 2. m >>= return m
  12. m >>= return
  13. ContT $ \c -> runContT m (\x -> runContT (return x) c)
  14. ContT $ \c -> runContT m (\x -> runContT (ContT $ \k -> k x) c)
  15. ContT $ \c -> runContT m (\x -> (\k -> k x) c)
  16. ContT $ \c -> runContT m (\x -> c x)
  17. ContT $ \c -> runContT m c
  18. ContT $ runContT m
  19. m
  20. 3. (m >>= f) >>= g m >>= (\x -> f x >>= g)
  21. (m >>= f) >>= g
  22. (ContT $ \c -> runContT m (\x -> runContT (f x) c)) >>= g
  23. ContT $ \c -> runContT (ContT $ \c -> runContT m (\x -> runContT (f x) c)) (\x -> runContT (g x) c)
  24. ContT $ \c -> (\c -> runContT m (\x -> runContT (f x) c)) (\x -> runContT (g x) c)
  25. ContT $ \c -> runContT m (\x -> runContT (f x) (\x -> runContT (g x) c))
  26. m >>= (\x -> f x >>= g)
  27. ContT $ \c -> runContT m (\x -> runContT ((\x -> f x >>= g) x) c)
  28. ContT $ \c -> runContT m (\x -> runContT (f x >>= g) c)
  29. ContT $ \c -> runContT m (\x -> runContT (ContT $ \c -> runContT (f x) (\x -> runContT (g x) c)) c)
  30. ContT $ \c -> runContT m (\x -> (\c -> runContT (f x) (\x -> runContT (g x) c)) c)
  31. ContT $ \c -> runContT m (\x -> runContT (f x) (\x -> runContT (g x) c))

lift liftIO 函数

  1. instance MonadTrans (ContT r) where
  2. lift m = ContT (m >>=)
  3. instance (MonadIO m) => MonadIO (ContT r m) where
  4. liftIO = lift . liftIO

lift m 将封装在内部 Monad m 的值封装进了 ContT Monad 之中。

lift m = ContT $ \k -> m >>= k

这里 m 的类型为 m a, k 的类型为 a -> m r,m >> k 的类型为 m r,

\k -> m >>= k 的类型为 (a -> m r) -> m r,

ContT $ \k -> m >>= k 也就是 ContT (m >>=) 的类型为 ContT r m a。

  1. 证明 ContT lift 函数的定义符合 lift 的法则。
  2. 1. lift . return return
  3. lift . return $ a
  4. lift (m a)
  5. ContT (m a >>=)
  6. ContT $ \k -> m a >>= k
  7. ContT $ \k -> k a
  8. return a
  9. 2. lift (m >>= f) lift m >>= (lift . f)
  10. 假设 m = n a 并且 f a = n b
  11. 于是 m >>= f = n b
  12. lift (m >>= f)
  13. lift (n b)
  14. ContT (n b >>=)
  15. ContT $ \k -> n b >>= k
  16. ContT $ \k -> k b
  17. return b
  18. lift m >>= (lift . f)
  19. ContT (n a >>=) >>= \x -> ContT (f x >>=)
  20. ContT (\k -> k a) >>= \x -> ContT (\k -> f x >>= k)
  21. ContT $ \c -> runContT $ ContT (\k -> k a) (\x -> runContT $ ContT (\k -> f x >>= k) c)
  22. ContT $ \c -> (\k -> k a) (\x -> (\k -> f x >>= k) c)
  23. ContT $ \c -> (\x -> (\k -> f x >>= k) c) a
  24. ContT $ \c -> (\k -> f a >>= k) c
  25. ContT $ \c -> (\k -> n b >>= k) c
  26. ContT $ \c -> (\k -> k b) c
  27. ContT $ \c -> c b
  28. return b
  1. Prelude Control.Monad.Trans.Cont Control.Monad.Trans> f a = if even a then Just (a `div` 2) else Nothing
  2. Prelude Control.Monad.Trans.Cont Control.Monad.Trans> c = lift . f :: Int -> ContT Int Maybe Int
  3. Prelude Control.Monad.Trans.Cont Control.Monad.Trans> runContT (c 3) return
  4. Nothing
  5. Prelude Control.Monad.Trans.Cont Control.Monad.Trans> runContT (c 4) f
  6. Just 1
  7. Prelude Control.Monad.Trans.Cont Control.Monad.Trans> c = lift getLine :: ContT r IO String
  8. Prelude Control.Monad.Trans.Cont Control.Monad.Trans> runContT c print
  9. abc
  10. "abc"

callCC 函数

  1. callCC :: ((a -> ContT r m b) -> ContT r m a) -> ContT r m a
  2. callCC f = ContT $ \c -> runContT (f (\x -> ContT $ \_ -> c x)) c

CallCC 是 Call With Current Continuation(对当前延续函数进行调用)的缩写,它为 ContT Monad 提供了退出的手段。

CallCC 有一个参数 f,CallCC f 返回一个 ContT Monad。

f 的类型是 (a -> ContT r m b) -> ContT r m a,也就是说 f 本身是个函数,它的返回值类型和 CallCC f 相同,都是一个ContT Monad。

f 通常采用 \exit -> do ... 这种形式。

  1. Prelude Control.Monad.Trans.Cont Control.Monad.Trans> runContT (callCC (\exit -> exit 1) :: ContT Int Maybe Int) return
  2. Just 1
  3. Prelude Control.Monad.Trans.Cont Control.Monad.Trans> runContT (callCC (\exit -> do{exit 1; return 2}) :: ContT Int Maybe Int) return
  4. Just 1

也就是说只要 CallCC 的参数采用 \exit -> do {...; exit a; ...} 这种形式,

那么该函数所返回的 ContT Monad 将无视 exit a 后面的处理流程,无条件地将 a 传递给外围函数。

下面看看 CallCC 函数是如何做到这一点的。

  • callCC :: ((a -> ContT r m b) -> ContT r m a) -> ContT r m a

    callCC f = ContT $ \c -> runContT (f (\x -> ContT $ \_ -> c x)) c

    ContT $ \c -> runContT (f (\x -> ContT $ \_ -> c x)) c 类型为 ContT r m a

    \c -> runContT (f (\x -> ContT $ \_ -> c x)) c 类型为 (a -> m r) -> m r

    c 的类型为 a -> m r,

    runContT (f (\x -> ContT $ \_ -> c x)) 的类型为 (a -> m r) -> m r,

    f (\x -> ContT $ \_ -> c x) 的类型为 ContT r m a,

    f 的类型为 (a -> ContT r m b) -> ContT r m a,

    \x -> ContT $ \_ -> c x 的类型为 a -> ContT r m b,x 的类型为 a,

    ContT $ \_ -> c x 的类型为 ContT r m b,

    \_ -> c x 的类型为 (b -> m r) -> m r,

    _ 的类型为 b -> m r,

    c x 的类型为 m r。
  • callCC f = ContT $ \c -> runContT (f (\x -> ContT $ \_ -> c x)) c

    这里假设 f = \exit -> exit a >>= k。

    callCC f where f = \exit -> exit a >>= k

    = ContT $ \c -> runContT (f (\x -> ContT $ \_ -> c x)) c

    = ContT $ \c -> runContT ((\exit -> exit a >>= k) (\x -> ContT $ \_ -> c x)) c

    = ContT $ \c -> runContT ((\x -> ContT $ \_ -> c x) a >>= k) c

    = ContT $ \c -> runContT ((ContT $ \_ -> c a) >>= k) c
  • m >>= k = ContT $ \c -> runContT m (\x -> runContT (k x) c)

    (ContT $ \_ -> c a) >>= k

    = ContT $ \c -> runContT (ContT $ \_ -> c a) (\x -> runContT (k x) c)

    = ContT $ \c -> (\_ -> c a) (\x -> runContT (k x) c)

    = ContT $ \c -> c a
  • callCC f where f = \exit -> exit a >>= k

    = ContT $ \c -> runContT ((ContT $ \_ -> c a) >>= k) c

    = ContT $ \c -> runContT (ContT $ \c -> c a) c

    = ContT $ \c -> (\c -> c a) c

    = ContT $ \c -> c a
  • callCC f = ContT $ \c -> runContT (f (\x -> ContT $ \_ -> c x)) c

    这里假设 f = \exit -> exit a >>= k >>= h。

    f = \exit -> exit a >>= k >>= h

    = \exit -> exit a >>= (\x -> k x >> h)

    = \exit -> exit a >>= k' where k' = \x -> k x >> h

    callCC f where f = \exit -> exit a >>= k >>= h

    = callCC f where f = \exit -> exit a >>= k' where k' = \x -> k x >> h

    = ContT $ \c -> c a

从以上推导过程可以看出 CallCC 的参数如果采用 \exit -> do {...; exit a; ...} 这种形式,

该函数所返回的 ContT Monad 确实会无视 exit a 后面的处理流程,无条件地将 a 传递给外围函数。

ContT Monad转换器的其他函数

  1. evalContT :: (Monad m) => ContT r m r -> m r
  2. evalContT m = runContT m return
  3. mapContT :: (m r -> m r) -> ContT r m a -> ContT r m a
  4. mapContT f m = ContT $ f . runContT m
  5. withContT :: ((b -> m r) -> (a -> m r)) -> ContT r m a -> ContT r m b
  6. withContT f m = ContT $ runContT m . f
  7. resetT :: (Monad m) => ContT r m r -> ContT r' m r
  8. resetT = lift . evalContT
  9. shiftT :: (Monad m) => ((a -> m r) -> ContT r m r) -> ContT r m a
  10. shiftT f = ContT (evalContT . f)
  11. liftLocal :: (Monad m) => m r' -> ((r' -> r') -> m r -> m r) ->
  12. (r' -> r') -> ContT r m a -> ContT r m a
  13. liftLocal ask local f m = ContT $ \c -> do
  14. r <- ask
  15. local f (runContT m (local (const r) . c))

Cont Monad

  1. cont :: ((a -> r) -> r) -> Cont r a
  2. cont f = ContT (\c -> Identity (f (runIdentity . c)))
  3. runCont :: Cont r a -> (a -> r) -> r
  4. runCont m k = runIdentity (runContT m (Identity . k))
  5. evalCont :: Cont r r -> r
  6. evalCont m = runIdentity (evalContT m)
  7. mapCont :: (r -> r) -> Cont r a -> Cont r a
  8. mapCont f = mapContT (Identity . f . runIdentity)
  9. withCont :: ((b -> r) -> (a -> r)) -> Cont r a -> Cont r b
  10. withCont f = withContT ((Identity .) . f . (runIdentity .))
  11. reset :: Cont r r -> Cont r' r
  12. reset = resetT
  13. shift :: ((a -> r) -> Cont r r) -> Cont r a
  14. shift f = shiftT (f . (runIdentity .))

Cont Monad 是 ContT Monad(转换器) 的一个特例。

应用实例

  1. import Control.Monad.Trans.Cont
  2. import Control.Monad (when)
  3. add :: Int -> Int -> Int
  4. add x y = x + y
  5. square :: Int -> Int
  6. square x = x * x
  7. add_cont :: Int -> Int -> Cont r Int
  8. add_cont x y = return (add x y)
  9. square_cont :: Int -> Cont r Int
  10. square_cont x = return (square x)
  11. pythagoras_cont :: Int -> Int -> Cont r Int
  12. pythagoras_cont x y = do
  13. x_squared <- square_cont x
  14. y_squared <- square_cont y
  15. add_cont x_squared y_squared
  16. pythagoras_cont' :: Int -> Int -> Cont r Int
  17. pythagoras_cont' x y = callCC $ \exit -> do
  18. when (x < 0 || y < 0) $ exit (-1)
  19. x_squared <- square_cont x
  20. y_squared <- square_cont y
  21. add_cont x_squared y_squared
  22. main = do
  23. runCont (pythagoras_cont 3 4) print -- 25
  24. runCont (pythagoras_cont' 3 4) print -- 25
  25. runCont (pythagoras_cont' (-3) 4) print -- -1

How and why does the Haskell Cont monad work?

Haskell语言学习笔记(30)MonadCont, Cont, ContT的更多相关文章

  1. Haskell语言学习笔记(88)语言扩展(1)

    ExistentialQuantification {-# LANGUAGE ExistentialQuantification #-} 存在类型专用的语言扩展 Haskell语言学习笔记(73)Ex ...

  2. Haskell语言学习笔记(79)lambda演算

    lambda演算 根据维基百科,lambda演算(英语:lambda calculus,λ-calculus)是一套从数学逻辑中发展,以变量绑定和替换的规则,来研究函数如何抽象化定义.函数如何被应用以 ...

  3. Haskell语言学习笔记(69)Yesod

    Yesod Yesod 是一个使用 Haskell 语言的 Web 框架. 安装 Yesod 首先更新 Haskell Platform 到最新版 (Yesod 依赖的库非常多,版本不一致的话很容易安 ...

  4. Haskell语言学习笔记(20)IORef, STRef

    IORef 一个在IO monad中使用变量的类型. 函数 参数 功能 newIORef 值 新建带初值的引用 readIORef 引用 读取引用的值 writeIORef 引用和值 设置引用的值 m ...

  5. Haskell语言学习笔记(39)Category

    Category class Category cat where id :: cat a a (.) :: cat b c -> cat a b -> cat a c instance ...

  6. Haskell语言学习笔记(44)Lens(2)

    自定义 Lens 和 Isos -- Some of the examples in this chapter require a few GHC extensions: -- TemplateHas ...

  7. Haskell语言学习笔记(72)Free Monad

    安装 free 包 $ cabal install free Installed free-5.0.2 Free Monad data Free f a = Pure a | Free (f (Fre ...

  8. Haskell语言学习笔记(38)Lens(1)

    Lens Lens是一个接近语言级别的库,使用它可以方便的读取,设置,修改一个大的数据结构中某一部分的值. view, over, set Prelude> :m +Control.Lens P ...

  9. Haskell语言学习笔记(92)HXT

    HXT The Haskell XML Toolbox (hxt) 是一个解析 XML 的库. $ cabal install hxt Installed hxt-9.3.1.16 Prelude&g ...

随机推荐

  1. c# list排序的三种实现方式 (转帖)

    用了一段时间的gridview,对gridview实现的排序功能比较好奇,而且利用C#自带的排序方法只能对某一个字段进行排序,今天demo了一下,总结了三种对list排序的方法,并实现动态传递字段名对 ...

  2. 如何在MFC DLL中向C#类发送消息

    如何在MFC DLL中向C#类发送消息 一. 引言 由于Windows Message才是Windows平台的通用数据流通格式,故在跨语言传输数据时,Message是一个不错的选择,本文档将描述如何在 ...

  3. 使用gulp 合并压缩打包,实时监控文件,实现本地server

    今天不讲webpack,就说说gulp是怎么进行压缩合并打包 首先你的安装gulp : npm install gulp -g --save-dev 然后最基本的你因该知道gulp 的四个方法, gu ...

  4. python下爬某个网页的图片

    #coding=utf-8 import re import urllib def getHtml(url): #获取url对应得源码 page = urllib.urlopen(url) html ...

  5. AngularJS+ThinkPHP实例教程

    总体思路 thinkphp通过RESTful方式提供数据给angular,前端(包括模板页面)全部由angular来接管. 示例 实现一个用户管理模块,走通增删改查4个操作,通过该示例,演示如何在th ...

  6. Exchange 2003服务器中如何在公司资料夹中设置共享行事历

    Exchange 2003服务器中如何在公司资料夹中设置共享行事历 编写人:左丘文 2018-2-23 春节假期归来,开工第一天,感觉还没有从假期中恢复及调整过来.突然想到了我已经荒废了近一年的园子, ...

  7. 设计模式初学者笔记:Abstract Factory模式

    首先啰嗦下创建迷宫所用的Room类.这个类并不直接保存Room四周的构造,而是通过MapSite* _sides[4]这个私有数组成员指向Room四周的构造.那么什么时候将四周构造直接放在Room中, ...

  8. js用法

    属性(attribute) function fn(){ console.log(123) } fn() var a=fn()                 将函数fn()调用结果赋值给a 1.函数 ...

  9. php文件下载(解决文件下载后多几个字节的问题) 与封装成类的例子

    php文件下载比较常见,网上的资料比较多,在此不再强调怎么去实现(因为也是网上看的).下面主要说明的是下载代码的注意点. php下载文件主要是把文件以字节流直接输出,也就是echo fread($fi ...

  10. eclipse如何调试jar包源码

    转载至:http://blog.csdn.net/niclascage/article/details/47451967 引子 深入学习开源代码很多时候都需要去调试别人的jar包.当然如果你能拿别人的 ...