Haskell语言学习笔记(72)Free Monad
安装 free 包
$ cabal install free
Installed free-5.0.2
Free Monad
data Free f a = Pure a | Free (f (Free f a))
instance Functor f => Functor (Free f) where
fmap f = go where
go (Pure a) = Pure (f a)
go (Free fa) = Free (go <$> fa)
instance Functor f => Monad (Free f) where
return = Pure
Free x >>= f = Free (fmap (>>= f) x)
Pure x >>= f = f x
- data Free f a = Pure a | Free (f (Free f a))
Free f a 是一种递归数据结构。它带有两个类型参数:Functor类型参数 f 以及作为递归终点的数据类型 a。
Free 数据类型内可包含一个或多个 f,但只能包含一个 a。 - instance Functor f => Functor (Free f) where
如果 f 是 Functor,那么 Free f 就是 Functor - instance Functor f => Monad (Free f) where
如果 f 是 Functor,那么 Free f 就是 Monad
证明 Free f’ 符合Funtor法则:
1. fmap id ≡ id
即 fmap id m ≡ id m
1.1 m = Pure a 时
fmap id (Pure a) ≡ Pure (id a) ≡ Pure a ≡ id (Pure a)
1.2 m = Free (f' x) 时
fmap id (Free (f' x)) ≡ Free (fmap id (f' x)) ≡ Free (f' (id x)) ≡ Free (f' x) ≡ id (Free (f' x))
2. fmap (f . g) ≡ fmap f . fmap g
即 fmap (f . g) m ≡ (fmap f . fmap g) m
2.1 m = Pure a 时
fmap (f . g) (Pure a) ≡ Pure ((f . g) a) ≡ Pure (f (g a))
(fmap f . fmap g) (Pure a) ≡ fmap f (fmap g (Pure a)) ≡ fmap f (Pure (g a)) ≡ Pure (f (g a))
2.2 m = Free (f' x) 时
fmap (f . g) (Free (f' x)) ≡ Free (fmap (f . g) (f' x)) ≡ Free (f' ((f . g) x) ≡ Free (f' (f (g x)))
(fmap f . fmap g) (Free (f' x)) ≡ fmap f (fmap g (Free (f' x)))
≡ fmap f (Free (fmap g (f' x))) ≡ fmap f (Free (f' (g x)))
≡ Free (fmap f (f' (g x))) ≡ Free (f' (f (g x)))
证明 Free f’ 符合Monad法则:
1. return a >>= f ≡ f a
return a >>= f ≡ Pure a >>= f ≡ f a
2. m >>= return ≡ m
2.1 m = Pure a 时
Pure a >>= return ≡ Pure a >>= Pure ≡ Pure a
2.2 m = Free (f' x) 时
Free (f' x) >>= return
≡ Free (f' x) >>= Pure
≡ Free (fmap (>>= Pure) (f' x))
≡ Free (f' (x >>= Pure))
≡ Free (f' (Free (f' ... (Free (f' (Pure a >>= Pure))) ... )))
≡ Free (f' (Free (f' ... (Free (f' (Pure a))) ... )))
≡ Free (f' x)
3. (m >>= f) >>= g ≡ m >>= (\x -> f x >>= g)
(m >>= f) >>= g
≡ (Free (f' (Free (f' ... (Free (f' (Pure a))) ... ))) >>= f) >>= g
≡ Free (f' (Free (f' ... (Free (f' (Pure a >>= f))) ... ))) >>= g
≡ Free (f' (Free (f' ... (Free (f' (f a))) ... ))) >>= g
≡ Free (f' (Free (f' ... (Free (f' (f a >>= g))) ... )))
m >>= (\x -> f x >>= g)
≡ Free (f' (Free (f' ... (Free (f' (Pure a))) ... ))) >>= (\x -> f x >>= g)
≡ Free (f' (Free (f' ... (Free (f' (Pure a >>= (\x -> f x >>= g)))) ... )))
≡ Free (f' (Free (f' ... (Free (f' ((\x -> f x >>= g) a))) ... )))
≡ Free (f' (Free (f' ... (Free (f' (f a >>= g))) ... )))
Prelude Control.Monad.Free> :t Pure 3
Pure 3 :: Num a => Free f a
Prelude Control.Monad.Free> :t Free (Just (Pure 3))
Free (Just (Pure 3)) :: Num a => Free Maybe a
Prelude Control.Monad.Free> :t Free (Just (Free (Just (Pure 3))))
Free (Just (Free (Just (Pure 3)))) :: Num a => Free Maybe a
Prelude Control.Monad.Free> Free (Just (Free (Just (Pure 3)))) >> Free (Just (Free (Just (Pure 3))))
Free (Just (Free (Just (Free (Just (Free (Just (Pure 3))))))))
Prelude Control.Monad.Free> :t Free [Pure 3]
Free [Pure 3] :: Num a => Free [] a
Prelude Control.Monad.Free> :t Free [Free [Pure 3]]
Free [Free [Pure 3]] :: Num a => Free [] a
Prelude Control.Monad.Free> Free [Free [Pure 3]] >> Free [Free [Pure 3]]
Free [Free [Free [Free [Pure 3]]]]
应用实践
Free Monad 可以用来实现语言解释器。
假设有一种Toy语言,它包含以下三种命令。
output b -- prints a "b" to the console
bell -- rings the computer's bell
done -- end of execution
- output 命令输出数据 b 到控制台,带参数 b。
- bell 命令响铃,不带参数。
- done 命令用于结束程序。
下面通过使用 Free Monad 来实现该语言的解释器。
import Control.Monad.Free
首先定义 Toy 数据类型,它是由三条命令组成的和类型:
data Toy b next =
Output b next
| Bell next
| Done
- 类型参数 b 是通过 output 命令输出到控制台的数据的类型
- 类型参数 next 是下一条命令的类型
要使用 Free Monad,Toy 数据类型必须是 Functor 类型类的实例:
instance Functor (Toy b) where
fmap f (Output x next) = Output x (f next)
fmap f (Bell next) = Bell (f next)
fmap f Done = Done
要避免手动实现 Functor 类型类可以使用语言扩展 DeriveFunctor
{-# LANGUAGE DeriveFunctor #-}
import Control.Monad.Free
data Toy b next =
Output b next
| Bell next
| Done
deriving (Functor)
Toy b 是 Functor,根据定义可得 Free (Toy b) 是 Free Monad。
要使用 Free Monad,所有命令都必须是 Free (Toy b) r 类 型。此时需要使用 liftF 函数。
output :: a -> Free (Toy a) ()
output x = liftF (Output x ())
bell :: Free (Toy a) ()
bell = liftF (Bell ())
done :: Free (Toy a) r
done = liftF Done
要避免这些重复定义可以使用 makeFree
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE FlexibleContexts #-}
import Control.Monad.Free
import Control.Monad.Free.TH
data Toy b next =
Output b next
| Bell next
| Done
deriving (Functor)
makeFree ''Toy
以下定义子例程 subroutine 和程序 program :
subroutine :: Free (Toy Char) ()
subroutine = output 'A'
program :: Free (Toy Char) r
program = do
subroutine
bell
done
以下定义第一个解释器:打印程序的函数 showProgram
showProgram :: (Show a, Show r) => Free (Toy a) r -> String
showProgram (Free (Output a x)) =
"output " ++ show a ++ "\n" ++ showProgram x
showProgram (Free (Bell x)) =
"bell\n" ++ showProgram x
showProgram (Free Done) =
"done\n"
showProgram (Pure r) =
"return " ++ show r ++ "\n"
pretty :: (Show a, Show r) => Free (Toy a) r -> IO ()
pretty = putStr . showProgram
以下定义第二个解释器:解释运行程序的函数 interpret
interpret :: (Show b) => Free (Toy b) r -> IO ()
interpret (Free (Output b x)) = print b >> interpret x
interpret (Free (Bell x)) = print "bell" >> interpret x
interpret (Free Done ) = return ()
interpret (Pure r) = return ()
载入程序,确认运行结果:
*Main> putStr (showProgram program)
output 'A'
bell
done
*Main> pretty (output 'A')
output 'A'
return ()
*Main> pretty (return 'A' >>= output)
output 'A'
return ()
*Main> pretty (output 'A' >>= return)
output 'A'
return ()
*Main> pretty ((output 'A' >> done) >> output 'C')
output 'A'
done
*Main> pretty (output 'A' >> (done >> output 'C'))
output 'A'
done
*Main> interpret program
'A'
"bell"
参考链接
https://github.com/lotz84/haskell/blob/master/docs/free-monad.md
Free monads in 7 easy steps
Haskell语言学习笔记(72)Free Monad的更多相关文章
- Haskell语言学习笔记(88)语言扩展(1)
ExistentialQuantification {-# LANGUAGE ExistentialQuantification #-} 存在类型专用的语言扩展 Haskell语言学习笔记(73)Ex ...
- 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语言学习笔记(79)lambda演算
lambda演算 根据维基百科,lambda演算(英语:lambda calculus,λ-calculus)是一套从数学逻辑中发展,以变量绑定和替换的规则,来研究函数如何抽象化定义.函数如何被应用以 ...
- Haskell语言学习笔记(69)Yesod
Yesod Yesod 是一个使用 Haskell 语言的 Web 框架. 安装 Yesod 首先更新 Haskell Platform 到最新版 (Yesod 依赖的库非常多,版本不一致的话很容易安 ...
- Haskell语言学习笔记(24)MonadWriter, Writer, WriterT
MonadWriter 类型类 class (Monoid w, Monad m) => MonadWriter w m | m -> w where writer :: (a,w) -& ...
- 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语言学习笔记(84)Concurrent
Control.Concurrent Prelude> import Control.Concurrent Prelude Control.Concurrent> Control.Conc ...
随机推荐
- SCCM2012 R2实战系列之十二:解决OSD分发时间过长的问题
对于SCCM 2012 R2的初学者来说,能够成功分发操作系统可能已经是非常兴奋了.但在企业中会遇到客户提出的各种各样苛刻的需求.所以在平时实验过程中多站点客户的角度想问题,尽可能的贴近企业实际生产环 ...
- dropdownlist 绑定方法
this.ddlUnit.SelectedValue = id; //注意大小写,如果选中失败会显示默认值. ListItem item=ddlUnit.Items.FindByValue(id); ...
- Mysql 64位解压版的安装
先下载解压版的mysql 下载地址 https://dev.mysql.com/downloads/file/?id=474496 解压 进到里面新建这个文件夹和文件 打开my.ini文件(用文本编辑 ...
- Hive UDF作业
说到这次作业,看似简单的几个步骤,对于我这样的菜鸟来说可真是一波三折啊.下面来说说这次的步骤和我遇到的问题. 首先准备工作,搭建好hive环境,保证hadoop集群是启动的.这个就不多说了. 第一步: ...
- 匿名内部类中不能修改int变量时、final int i 不能改变i的值时、或 i++线程不安全。使用AtomicInteger;
在匿名内部类或某某情况下中引入的变量必须是Final最终型的:这时还想要去修改这个变量就需要使用到AtomicInteger这个类了: AtomicInteger CarSize = new Atom ...
- linux守护进程与&的区别
1.默认情况下,进程是在前台运行的,这时就把shell占据了(有很多日志打印输出),我们无法进行其他操作.所以对于没有交互的进程,很多时候我们希望将其在后台启动,可以在启动参数的时候加一个&实 ...
- mybatis基于注解形式的多数据源
最近在做一个系统管理项目,需要使用到多数据源,尝试了注解形式和xml形式的多数据源配置,以下是基于注解形式的Mybatis多数据源配置. 1.application.yml 配置文件 database ...
- dns 域名地址
Public DNS+ 是属于 腾讯云旗下的公共 DNS 服务.拥有 80 多条国内线路和 4 条海外线路,有 BGP Anycast 技术,也是国内首家支持谷歌 ECS (edns-client-s ...
- NoSQL学习1
MongoDB使用C++语言编写的一个基于分布式的文件存储的开源数据库.可以在承受高负载的情况下,保证服务器的性能. MongoDB将数据存储成为一个文档,数据结构有键值对组成.类似于JSON,字段值 ...
- 剑指Offer(三):从尾到头打印链表
说明: 1.本系列是根据<剑指Offer>这个系列做的一个小笔记. 2.直接动力是因为师兄师姐找工作很难,而且机械出生的我面试算法更难. 3.刚开始准备刷LeetCode.LintCode ...