rad*_*row 10 monads haskell functional-programming freer-monad
我曾几次听到这个词,但我仍然不知道所谓的“ Freer Monad”到底是什么。这个名字让我想到了Free Monads,但我看不出它们之间的实际联系。我在hackage上找到了一些库:http ://hackage.haskell.org/package/freer ,但是那里的示例对我没有太大帮助。
所以我的问题是:
我知道这是一个旧线程,但我想我还是会回答它以防万一
\n\n\n什么是所谓的“Freer Monad”
\n
根据原始论文Freer Monads,More Extensible Effects, “Freer Monad”本质上是一个 Free Monad,没有 Free Monad 必要的函子约束。
\n自由单子基本上是单子结构的本质;“最小”的东西仍然是一个单子。在这篇文章中可以找到一个非常好的实用解释方法。本文还表明“正常”自由 monad 需要 Functor 约束。
\n然而,在每个函数中添加函子约束通常是相当乏味的(有时甚至实现起来很奇怪),事实证明,通过“将函子功能移动”到构造函数的参数中,以便Impure实现方可以改变输出本身的类型(因此没有通用函子),可以摆脱这个约束。这是通过使用GADTs:(Freer Monads论文中的示例)来完成的
data Free f a = Pure a\n | Impure (f (Free f a))\n\ninstance Functor f => Monad (Free f) where \nRun Code Online (Sandbox Code Playgroud)\n变成
\ndata FFree f a where\n Pure :: a \xe2\x86\x92 FFree f a\n Impure :: f x \xe2\x86\x92 (x \xe2\x86\x92 FFree f a) \xe2\x86\x92 FFree f a\n\ninstance Monad (FFree f) where \n [...]\n Impure fx k\xe2\x80\x99 >>= k = Impure fx (k\xe2\x80\x99 >>> k)\nRun Code Online (Sandbox Code Playgroud)\n这基本上让后面的实现选择如何执行fmap固定[双关语]到适当的“输出/包装类型”的操作。\n所以根本的区别本质上是可用性和通用性。
由于存在一些混乱:FFree是 Freer monad 并对应于Effpackage 中freer-simple。
\n\n对他们来说很好的用例
\n
Freer monad 和 Free monad 一样适合构建 DSL。
\n例如考虑一个类型
\ndata Lang r where\n LReturn :: Var -> Lang Int\n LPrint :: IntExpr -> Lang ()\n LAssign :: Var -> IntExpr -> Lang ()\n LRead :: Var -> Lang Int\n\nRun Code Online (Sandbox Code Playgroud)\n这告诉我有几个操作需要在Lang:中执行return x print x assign x y read y。
我们在这里使用 GADT,以便我们还可以指定各个操作将有什么输出。如果我们在 DSL 中编写函数,这会非常方便,因为可以检查它们的输出。
\n添加一些方便的函数(可以实际导出):
\ndata Lang r where\n LReturn :: Var -> Lang Int\n LPrint :: IntExpr -> Lang ()\n LAssign :: Var -> IntExpr -> Lang ()\n LRead :: Var -> Lang Int\n\nRun Code Online (Sandbox Code Playgroud)\n(这已经使用 编写freer)
现在我们可以像这样使用它们:(假设 IntExpr 包含变量和整数)
\nlReturn :: Member Lang effs\n => Var -> Eff effs Int\nlReturn = send . LReturn\n\nlPrint :: Member Lang effs\n => IntExpr -> Eff effs ()\nlPrint = send . LPrint\n\nlAssign :: Member Lang effs\n => Var -> IntExpr -> Eff effs ()\nlAssign v i = send $ LAssign v i\n\nlRead :: Member Lang effs\n => Var -> Eff effs Int\nlRead = send . LRead\n\nRun Code Online (Sandbox Code Playgroud)\n现在,这些函数使您能够拥有可以以不同方式解释的 DSL。为此,需要一个具有特定类型的解释器effs(这是一个自由单子“实例的类型级别列表”)
因此,freer我们采用了更自由的单子的想法并将其打包到效果系统中。
这个解释器可能看起来像这样:
\nsomeFunctionPrintingAnInt = do\n lAssign (Var "a") (IE_Int 12)\n lPrint (IE_Var $ Var "a")\nRun Code Online (Sandbox Code Playgroud)\n该run...部分指定了单子的初始“状态”。该go部分是解释器本身,解释不同的可能动作。
请注意,我们可以在同一个 do 块中使用函数get和 ,tell即使它们是不同 monad 的一部分,这使我们
\n\n我还想知道与免费的 monad 和经典的 mtl 堆栈相比,它们有什么优势。
\n
该实现允许使用“单子堆栈”不同部分的单子操作,而无需使用lifting。
关于实施:
\n为了理解这一点,我们从一个高抽象层次来看待它:\n我们的 DSL 的辅助功能是send在Eff effs需要的地方Member Lang effs。
因此,Member约束只是声明中Lang类型级列表中的一种方式。(基本上是类型级别)effsMember Lang effselem
monadEff具有“询问” Membermonad 类型级别列表的 s 是否可以处理当前值的功能(请记住,操作只是随后解释的值)。如果是,则执行它们的解释,如果不是,则将问题移交给列表中的下一个 monad。
当花一些时间在代码库上时,这会变得更加直观和容易理解freer-simple。