Haskell Free Monad的学习记录
看了一些博客,但是感觉都讲的好高大上,没看看懂,决定写写自己对Free Monad的理解。本人是学生,仅在课程中粗浅的使用到了Free Monad,因此写的很简单,都是个人的理解,大佬轻喷。
感觉可以从三个角度理解Free Monad:
- 这个Free Monad有什么用
- Free Monad的解耦能力
- 对Free Monad 的bind操作的理解
Free Moand有什么用?
个人感觉是两个用:
Free理解成“免费”的意思,它可以让我们在定义了一个Functor 后,直接当成Monad来使用。
这里证明当 e
是functor时, Free e a
是Monad。
Free Monad的解耦能力
Free Monad一般都有3部分的内容:
- 对外的构造函数 (输入形式自定,输出一个Free Monad)
- Free Monad的具体逻辑
- 解释器
-- 这里的是对Free Monad的基本的定义
data Free e a
= Pure a
| Free (e (Free e a))
instance (Functor e) => Functor (Free e) where
fmap f (Pure x) = Pure $ f x
fmap f (Free g) = Free $ fmap (fmap f) g
instance (Functor e) => Applicative (Free e) where
pure = Pure
(<*>) = ap
instance (Functor e) => Monad (Free e) where
Pure x >>= f = f x
Free g >>= f = Free $ h <$> g
where
h x = x >>= f
-- 这里的是Free e a 中的 e a
data EvalOp a
= ReadOp (Env -> a)
| ErrorOp Error
| KvGetOp Val (Val -> a)
| KvPutOp Val Val a
-- 定义Free e a 中e 作为一个Functor的操作
instance Functor EvalOp where
fmap f (ReadOp c) = ReadOp $ f . c
fmap f (KvGetOp key k) = KvGetOp key $ f . k
fmap f (KvPutOp key val m) = KvPutOp key val $ f m
fmap _ (ErrorOp e) = ErrorOp e
-- 对外的构造函数,可以从给定的形式转为Free Monad
evalKvGet :: Val -> EvalM Val
evalKvGet key = Free $ KvGetOp key $ \val -> pure val
evalKvPut :: Val -> Val -> EvalM ()
evalKvPut key val = Free $ KvPutOp key val $ pure ()
askEnv :: EvalM Env
askEnv = Free $ ReadOp $ \env -> pure env
如下是一个解释器的例子:
-- 一个解释器的例子,拿到一个Free Monad进行具体的操作
runEval :: EvalM a -> Either Error a
runEval = fmap fst $ runEval' envEmpty stateInitial
where
runEval' :: Env -> State -> EvalM a -> (Either Error a, State)
runEval' _ s (Pure x) = (pure x, s)
runEval' r s (Free (ReadOp k)) = runEval' r s $ k r
runEval' r s (Free (KvGetOp key k)) =
case lookup key s of
Nothing -> (Left $ "Invalid key: " ++ show key, s)
Just val -> runEval' r s $ k val
runEval' r s (Free (KvPutOp key val m)) =
let s' = (key, val) : filter ((/= key) . fst) s
in runEval' r s' m
runEval' _ s (Free (ErrorOp e)) = (Left e, s)
在这一整套的逻辑下:
- 构造函数负责解析外界的内容为一个Free Monad。
- Free Monad 中的内容为对要执行的操作的抽象。
- 比如我需要执行一个“读取环境信息”的操作时,就写一个
ReadOp
的操作,来代表在此处需要进行一个“读取环境信息”的操作。
- 比如我需要执行一个“读取环境信息”的操作时,就写一个
- 解释器负责解析Free Monad中的内容,将其转为实际的操作。
- Free Monad仅有抽象的操作行为,但是不会设计具体的实现,好处就是允许针对同一个Free Monad编写不同的解释器,使其在不同的使用环境中产生不同的效果。
- 同样是
ReadOp
,可以在一个解释器中被实际的执行为返回一个变量的值,可以在另一个解释器中被执行为读取特定的文件。
对Free Monad 的bind(>>=)操作的理解
我第一次学Free Monad的时候怎么都没看懂 >>=
写的那么怪异的原因,后来问了问ChatGPT才大概看懂。(这么抽象的写法属实是加深了我对Haskell 就是奇技淫巧的刻板印象