(后来的访问者:这个问题的两个答案都提供了很好的见解,如果你感兴趣的话,你应该把它们都读出来,我只能将其作为SO的限制除外)
我在网上找到关于延续monad的所有讨论,他们要么提到它如何与一些简单的例子一起使用,要么他们解释它是一个基本的构建块,如 {{3}中的这篇文章}
我想知道这个范围之外是否有适用性。我的意思是,在continuation monad中包装递归函数或相互递归是否有意义?它有助于提高可读性吗?
这是从stackoverflow.com/questions/1157364/intercept-wm-delete-window-on-x11取得的延续模式的F#版本:
type ContinuationMonad() =
member this.Bind (m, f) = fun c -> m (fun a -> f a c)
member this.Return x = fun k -> k x
let cont = ContinuationMonad()
仅仅是为了学术兴趣,例如帮助理解monad或计算构建者?或者是否有一些现实世界的适用性,增加了类型安全性,还是避免了难以解决的典型编程问题呢?
即Mother of all monads is the continuation monad表明处理异常很复杂,但它并没有解释它试图解决的问题,而且这些例子并没有说明为什么它需要特定的这个monad 。不可否认,我只是不明白它的作用,但它可能是一个宝库!
(注意:我对理解延续monad的工作原理并不感兴趣,我认为我对它有一个很好的掌握,我只是不知道它解决了什么编程问题。)
答案 0 :(得分:8)
所有monad的母亲"东西不是纯粹的学术。 Dan Piponi引用了Andrzej Filinski的Representing Monads,这是一篇相当不错的论文。它的结果是,如果您的语言具有分隔的延续(或者可以使用call/cc
和单个可变状态模仿它们),那么您可以透明地添加任何对任何代码的monadic效果。换句话说,如果您有分隔连续而没有其他副作用,您可以实现(全局)可变状态或异常或回溯非确定性或协作并发。您可以通过定义一些简单的函数来完成其中的每一项。没有全球转型或任何需要。此外,您只需在使用时支付副作用。事实证明,Schemers完全正确call/cc
具有高度表现力。
如果你的语言没有分隔的延续,你可以通过延续monad(或者更好的双管延续monad)获得它们。当然,如果你打算以monadic风格写作 - 其中是全球转型 - 为什么不从一开始就使用所需的monad呢?对于Haskellers,这通常是我们所做的,但是,在许多情况下使用continuation monad仍然有好处(虽然隐藏起来)。一个很好的例子是Maybe
/ Option
monad,它除了只有一种类型的异常外,就像有例外。基本上,这个monad捕获了返回"错误代码的模式"并在每次函数调用后检查它。这正是典型定义所做的,除了"函数调用"我的意思是计算的每个(monadic)步骤。可以说,这是非常低效的,特别是在绝大多数时间没有错误时。如果你将Maybe
反映到continuation monad中,虽然你必须支付CPSed代码的成本(GHC Haskell处理得非常好),你只需付费来检查"错误代码"在重要的地方,即catch
陈述。在Haskell中,{dan}提到的Codensity
monad是一个更好的选择,因为Haskellers想要的 last 事物是为了使任意效果在代码中透明地交错。
正如danidiaz所提到的,使用基本上连续的monad或某些变体,更容易或更有效地实现许多monad。回溯搜索就是一个例子。虽然不是回溯中最新的东西,但我最喜欢使用它的一篇论文是Typed Logical Variables in Haskell。其中使用的技术也用于有线硬件描述语言。同样来自Koen Claesson的是A Poor Man's Concurrency Monad。此示例中对概念的更现代用法包括:Haskell中A Monad for Deterministic Parallelism和可伸缩I / O管理器Combining Events And Threads For Scalable Network Services中的确定性并行性monad。我确信我可以找到Scala中使用的类似技术。如果没有提供,您可以使用continuation monad在F#中实现异步工作流。事实上,Don Syme references与我刚引用的文章完全相同。如果您可以序列化函数但不具有连续性,则可以使用continuation monad来获取它们,并执行由Seaside等系统流行的序列化延续类型的Web编程。即使没有可序列化的延续,您也可以使用该模式(与async基本相同)至少避免回调,同时在本地存储延续并仅发送密钥。
最终,Haskellers之外的人很少使用任何容量的monad,正如我前面提到的,Haskellers倾向于使用比继续monad更多的可控制monad,尽管他们在内部使用它们相当多。然而,继续monad或continuation monad之类的东西,尤其是异步编程,变得不那么罕见了。随着C#,F#,Scala,Swift甚至Java开始采用支持monadic或至少monadic风格的编程,这些想法将得到更广泛的使用。如果Node开发人员对此更熟悉,也许他们会意识到你可以拥有自己的蛋糕,并且在事件驱动的编程方面也可以吃它。
答案 1 :(得分:6)
为了提供更直接的F#特定答案(尽管Derek已经涵盖了这一点), continuation monad 几乎捕获了异步工作流工作的核心。
continuation monad是一个函数,当给定一个continuation时,最终用结果调用continuation(它可能永远不会调用它,或者它也可能反复调用它):
type Cont<'T> = ('T -> unit) -> unit
F#异步计算有点复杂 - 它们继续(在成功的情况下),异常和取消延续,并且还包括取消令牌。使用略微简化的定义,F#核心库使用(参见the full definition here):
type AsyncParams =
{ token : CancellationToken
econt : exn -> unit
ccont : exn -> unit }
type Async<'T> = ('T -> unit) * AsyncParams -> unit
正如您所看到的,如果忽略AsyncParams
,它几乎就是延续monad。在F#中,我认为&#34;经典&#34; monads作为灵感而不是直接实现机制更有用。这里,continuation monad提供了一个如何处理某些类型的计算的有用模型 - 并且有许多额外的异步特定方面,核心思想可用于实现异步计算。
我认为这与monad在经典学术作品或Haskell中的使用方式有很大的不同,在那里他们倾向于使用&#34;&#34;&#34;并且可能以各种方式组成,以构建更复杂的monad,捕获更复杂的行为。
这可能仅仅是我个人的观点,但是我说延续monad本身并不实用,但它是一些非常实用的想法的基础。 (就像lambda演算本身并不是真正有用,但它可以被看作是很好的实用语言的灵感来源!)
答案 2 :(得分:2)
我肯定发现使用continuation monad实现的递归函数与使用显式递归实现的递归函数相比更容易。例如,给定此树类型:
type 'a Tree =
| Node of 'a * 'a Tree * 'a Tree
| Empty
这是在树上写下自下而上折叠的一种方法:
let rec fold e f t = cont {
match t with
| Node(a,t1,t2) ->
let! r1 = fold e f t1
let! r2 = fold e f t2
return f a r1 r2
| Empty -> return e
}
这显然类似于天真的折叠:
let rec fold e f t =
match t with
| Node(a,t1,t2) ->
let r1 = fold e f t1
let r2 = fold e f t2
f a r1 r2
| Empty -> return e
除了在深树上调用时,天真的折叠会打击堆栈,因为它不是尾递归,而使用continuation monad写的折叠则不会。你当然可以使用显式延续来编写相同的东西,但在我看来,它们增加的混乱程度会分散算法的结构(并将它们放在适当位置并不是完全可靠的):
let rec fold e f t k =
match t with
| Node(a,t1,t2) ->
fold e f t1 (fun r1 ->
fold e f t2 (fun r2 ->
k (f r1 r2)))
| Empty -> k e
请注意,为了实现此目的,您需要修改ContinuationMonad
的定义以包含
member this.Delay f v = f () v