什么是更自由的单子?

rad*_*row 10 monads haskell functional-programming freer-monad

我曾几次听到这个词,但我仍然不知道所谓的“ Freer Monad”到底是什么。这个名字让我想到了Free Monads,但我看不出它们之间的实际联系。我在hackage上找到了一些库:http ://hackage.haskell.org/package/freer ,但是那里的示例对我没有太大帮助。

所以我的问题是:

  • 什么是更自由的单子?
  • 如何使用它们?
  • 什么时候应该使用它们?
  • 与经典monad和免费monad相比,它们提供什么优势?

Fab*_*der 5

我知道这是一个旧线程,但我想我还是会回答它以防万一

\n
\n

什么是所谓的“Freer Monad”

\n
\n

根据原始论文Freer Monads,More Extensible Effects, “Freer Monad”本质上是一个 Free Monad,没有 Free Monad 必要的函子约束。

\n

自由单子基本上是单子结构的本质;“最小”的东西仍然是一个单子。在这篇文章中可以找到一个非常好的实用解释方法。本文还表明“正常”自由 monad 需要 Functor 约束。

\n

然而,在每个函数中添加函子约束通常是相当乏味的(有时甚至实现起来很奇怪),事实证明,通过“将函子功能移动”到构造函数的参数中,以便Impure实现方可以改变输出本身的类型(因此没有通用函子),可以摆脱这个约束。这是通过使用GADTs:(Freer Monads论文中的示例)来完成的

\n
data Free f a = Pure a\n              | Impure (f (Free f a))\n\ninstance Functor f => Monad (Free f) where \n
Run Code Online (Sandbox Code Playgroud)\n

变成

\n
data 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)\n
Run Code Online (Sandbox Code Playgroud)\n

这基本上让后面的实现选择如何执行fmap固定[双关语]到适当的“输出/包装类型”的操作。\n所以根本的区别本质上是可用性和通用性。

\n

由于存在一些混乱:FFree是 Freer monad 并对应于Effpackage 中freer-simple

\n
\n

对他们来说很好的用例

\n
\n

Freer monad 和 Free monad 一样适合构建 DSL。

\n

例如考虑一个类型

\n
data Lang r where\n    LReturn     :: Var -> Lang Int\n    LPrint      :: IntExpr -> Lang ()\n    LAssign     :: Var -> IntExpr -> Lang ()\n    LRead       :: Var -> Lang Int\n\n
Run Code Online (Sandbox Code Playgroud)\n

这告诉我有几个操作需要在Lang:中执行return x print x assign x y read y

\n

我们在这里使用 GADT,以便我们还可以指定各个操作将有什么输出。如果我们在 DSL 中编写函数,这会非常方便,因为可以检查它们的输出。

\n

添加一些方便的函数(可以实际导出):

\n
data Lang r where\n    LReturn     :: Var -> Lang Int\n    LPrint      :: IntExpr -> Lang ()\n    LAssign     :: Var -> IntExpr -> Lang ()\n    LRead       :: Var -> Lang Int\n\n
Run Code Online (Sandbox Code Playgroud)\n

(这已经使用 编写freer

\n

现在我们可以像这样使用它们:(假设 IntExpr 包含变量和整数)

\n
lReturn :: 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\n
Run Code Online (Sandbox Code Playgroud)\n

现在,这些函数使您能够拥有可以以不同方式解释的 DSL。为此,需要一个具有特定类型的解释器effs(这是一个自由单子“实例的类型级别列表”)

\n

因此,freer我们采用了更自由的单子的想法并将其打包到效果系统中。

\n

这个解释器可能看起来像这样:

\n
someFunctionPrintingAnInt = do\n    lAssign (Var "a") (IE_Int 12)\n    lPrint (IE_Var $ Var "a")\n
Run Code Online (Sandbox Code Playgroud)\n

run...部分指定了单子的初始“状态”。该go部分是解释器本身,解释不同的可能动作。

\n

请注意,我们可以在同一个 do 块中使用函数get和 ,tell即使它们是不同 monad 的一部分,这使我们

\n
\n

我还想知道与免费的 monad 和经典的 mtl 堆栈相比,它们有什么优势。

\n
\n

该实现允许使用“单子堆栈”不同部分的单子操作,而无需使用lifting。

\n

关于实施:

\n

为了理解这一点,我们从一个高抽象层次来看待它:\n我们的 DSL 的辅助功能是sendEff effs需要的地方Member Lang effs

\n

因此,Member约束只是声明中Lang类型级列表中的一种方式。(基本上是类型级别)effsMember Lang effselem

\n

monadEff具有“询问” Membermonad 类型级别列表的 s 是否可以处理当前值的功能(请记住,操作只是随后解释的值)。如果是,则执行它们的解释,如果不是,则将问题移交给列表中的下一个 monad。

\n

当花一些时间在代码库上时,这会变得更加直观和容易理解freer-simple

\n

  • 我很高兴你回答这个问题,即使已经快两年了。解释清楚且内容丰富,谢谢!我将其标记为已接受,但我很乐意看到其他解释。 (2认同)