安装 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]]]]

应用实践

Why free monads matter

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的更多相关文章

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

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

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

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

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

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

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

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

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

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

  6. Haskell语言学习笔记(24)MonadWriter, Writer, WriterT

    MonadWriter 类型类 class (Monoid w, Monad m) => MonadWriter w m | m -> w where writer :: (a,w) -& ...

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

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

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

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

  9. Haskell语言学习笔记(84)Concurrent

    Control.Concurrent Prelude> import Control.Concurrent Prelude Control.Concurrent> Control.Conc ...

随机推荐

  1. SCCM2012 R2实战系列之十二:解决OSD分发时间过长的问题

    对于SCCM 2012 R2的初学者来说,能够成功分发操作系统可能已经是非常兴奋了.但在企业中会遇到客户提出的各种各样苛刻的需求.所以在平时实验过程中多站点客户的角度想问题,尽可能的贴近企业实际生产环 ...

  2. dropdownlist 绑定方法

    this.ddlUnit.SelectedValue = id; //注意大小写,如果选中失败会显示默认值. ListItem item=ddlUnit.Items.FindByValue(id); ...

  3. Mysql 64位解压版的安装

    先下载解压版的mysql 下载地址 https://dev.mysql.com/downloads/file/?id=474496 解压 进到里面新建这个文件夹和文件 打开my.ini文件(用文本编辑 ...

  4. Hive UDF作业

    说到这次作业,看似简单的几个步骤,对于我这样的菜鸟来说可真是一波三折啊.下面来说说这次的步骤和我遇到的问题. 首先准备工作,搭建好hive环境,保证hadoop集群是启动的.这个就不多说了. 第一步: ...

  5. 匿名内部类中不能修改int变量时、final int i 不能改变i的值时、或 i++线程不安全。使用AtomicInteger;

    在匿名内部类或某某情况下中引入的变量必须是Final最终型的:这时还想要去修改这个变量就需要使用到AtomicInteger这个类了: AtomicInteger CarSize = new Atom ...

  6. linux守护进程与&的区别

    1.默认情况下,进程是在前台运行的,这时就把shell占据了(有很多日志打印输出),我们无法进行其他操作.所以对于没有交互的进程,很多时候我们希望将其在后台启动,可以在启动参数的时候加一个&实 ...

  7. mybatis基于注解形式的多数据源

    最近在做一个系统管理项目,需要使用到多数据源,尝试了注解形式和xml形式的多数据源配置,以下是基于注解形式的Mybatis多数据源配置. 1.application.yml 配置文件 database ...

  8. dns 域名地址

    Public DNS+ 是属于 腾讯云旗下的公共 DNS 服务.拥有 80 多条国内线路和 4 条海外线路,有 BGP Anycast 技术,也是国内首家支持谷歌 ECS (edns-client-s ...

  9. NoSQL学习1

    MongoDB使用C++语言编写的一个基于分布式的文件存储的开源数据库.可以在承受高负载的情况下,保证服务器的性能. MongoDB将数据存储成为一个文档,数据结构有键值对组成.类似于JSON,字段值 ...

  10. 剑指Offer(三):从尾到头打印链表

    说明: 1.本系列是根据<剑指Offer>这个系列做的一个小笔记. 2.直接动力是因为师兄师姐找工作很难,而且机械出生的我面试算法更难. 3.刚开始准备刷LeetCode.LintCode ...