安装 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. .Net 大型分布式基础服务架构横向演变概述(转)

    一. 业务背景 构建具备高可用,高扩展性,高性能,能承载高并发,大流量的分布式电子商务平台,支持用户,订单,采购,物流,配送,财务等多个项目的协作,便于后续运营报表,分析,便于运维及监控. 二. 基础 ...

  2. 在Linux上git pull线上仓库代码时,出现error: Your local changes to the following files would be overwritten by merge

    在Windows上工作时未出现过该问题,于是通过命令: git diff 查看差异,得到结果: diff --git a/start_crons.sh b/start_crons.sh old mod ...

  3. springboot打包

    springboot项目运行package命令,默认打出来的jar包只有几kb.想要打出可执行的jar包,加入插件: <build> <plugins> <plugin& ...

  4. 关于chrome控制台警告:Synchronous XMLHttpRequest on the main thread

    Synchronous XMLHttpRequest on the main thread is deprecated because of its detrimental effects to th ...

  5. GNU coreutils

    内核实用程序,针对文本及文件操作.涉及到102条linux命令.命令列表:cp.install.ln.mv.ls.echo…… 常见选项 1.退出状态 2.备份选项 cp, install, ln, ...

  6. spring事务传播实现源码分析

    转载. https://blog.csdn.net/qpfjalzm123/article/details/83717367 本文只是对spring事务传播实现的流程进行简单的分析,如有不对之处请指出 ...

  7. Webpack配置及使用

    ##如何将js模块化 ### module.exports() ### module.require() ### 自定义文件,进入时需要./ ### npm下载得到文件,不需要./ ##如果使用第三方 ...

  8. iar stm32 启动代码片段分析

    今天查看了 iar 上面的启动文件,好奇堆栈指针到底是什么时候赋值的,所以就仔细的阅读了代码和相关手册,找到了答案. 首先,芯片启动后,会从ROM的首地址处进行执行,那么我们从 linker 里面找找 ...

  9. Eclipse svn 项目 星号

    上图: 明明没有文件修改过就是无脑显示有提交的文件,有强迫症受不了. 而且每次提交你都点开看有没有漏掉啥文件.很麻烦.也很难受 .很容易漏提交 把本地文件跟SVN同步对比一下. 果然有文件. 添加规则 ...

  10. pychram 2018-01 安装pyQT5报错

    pychram 2018-01 安装pyQT5报错