安装 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. VMware虚拟机上配置nginx后,本机无法访问问题(转载)

    转自:http://www.server110.com/nginx/201407/10794.html 把nginx装在CentOS上,用本机访问虚拟机的时候却出现了不能访问的问题,查了资料以后,原来 ...

  2. tf.nn.dynamic_rnn

    tf.nn.dynamic_rnn(cell,inputs,sequence_length=None, initial_state=None,dtype=None, parallel_iteratio ...

  3. matplotlib基础知识全面解析

    图像基本知识: 通常情况下,我们可以将一副Matplotlib图像分成三层结构: 1.第一层是底层的容器层,主要包括Canvas.Figure.Axes: 2.第二层是辅助显示层,主要包括Axis.S ...

  4. linux环境下运行程序格式错误的问题,bash: ./helloworld: cannot execute binary file: Exec format error

    在编译完quecOpen的example helloworld之后,我运行此程序,结果报错,详情如下: ricks@ubuntu:~/share/project/ql-ol-sdk/ql-ol-ext ...

  5. C# 线程 在 sleep,suspend 之后 Abort 的方法

    1) 线程在sleep时的Abort     方法:对线程函数用 catch ThreadAbortException ,并return.   示例: [csharp] view plaincopy ...

  6. python文件相关

    文件操作基本流程初探 f = open('chenli.txt') #打开文件 first_line = f.readline() print('first line:',first_line) #读 ...

  7. Best Practice AngularJS

    Best Practice AngularJS /* 用几组简明扼要的代码段及其说明, 展示良好的编程行为, angularjs */ // app.module.js angular .module ...

  8. Java - 31 Java 发送邮件

    Java 发送邮件 使用Java应用程序发送E-mail十分简单,但是首先你应该在你的机器上安装JavaMail API 和Java Activation Framework (JAF) . 你可以在 ...

  9. 输入框占位符placeholder

    占位符placeholder的益处不用多说,但是很不幸的是,在IE8之前的浏览器里是无法实现placeholder这一属性的,所以在需要兼容IE8之前的浏览器的情况下,我们不得不想办法模拟实现plac ...

  10. html内容溢出部分...

    首先标签必须满足不是行内标签 方法一:(单行)此方法没有任何问题 width: 38px;(需要给定宽度) overflow: hidden; white-space: nowrap; text-ov ...