在学术用途之外,是否有现实世界中的延续单元适用性



(以后的访问者:这个问题的两个答案都可以提供出色的见解,如果您有兴趣,您应该阅读两者,除了一个限制外,我只能

从我在网上找到有关延续单元的所有讨论中,他们要么提到如何与某些琐碎的例子一起使用,要么解释说这是一个基本的构建基础,就像所有单调的母亲一样是持续的monad 。

我想知道此范围之外是否有适用性。我的意思是,将递归功能或相互递归包裹在持续单元中是有意义的吗?它有助于可读性吗?

这是从此帖子中获取的延续模式的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()

例如,它是有助于理解Monads或计算构建者的学术兴趣吗?或者是否有一些现实世界中的适用性,添加了类型安全性,或者它是否规避了难以解决的典型编程问题?

即,来自瑞安·莱利(Ryan Riley)的Call/cc的延续单元表明,处理异常很复杂,但是它并不能解释它要解决的问题,示例没有说明为什么它需要具体需要这个单一的单调。。诚然,我只是不明白它的作用,但这可能是宝库!

(注意:我不感兴趣地了解延续单元的工作方式,我认为我对此有公平的掌握,我只是看不到它解决了什么编程问题。)

"所有monads"东西的"母亲"不纯粹是学术。Dan Piponi引用了Andrzej Filinski的代表Monads,这是一份相当不错的论文。它的结果是,如果您的语言已经界定了连续性(或者可以用call/cc和一个可变状态模仿它们),那么您可以透明 添加任何任何 monadic效果代码。换句话说,如果您已经界定了连续性,则可以实现(全局)可变状态或异常,或者回溯非确定性或合作的并发。您只需定义一些简单的函数即可完成这些操作。无需全球转型或任何需要的东西。另外,只有在使用副作用时才付费。事实证明,对于call/cc具有高度表达性的计划是完全正确的。

如果您的语言没有界定的连续性,则可以通过延续单元获得它们(或更好的双管连续单元)。当然,如果您要以Monadic风格的方式写作,无论如何;全局变换–为什么不只是一开始就使用所需的单子呢?对于Haskellers而言,这通常是我们要做的,但是在许多情况下,使用延续单元(尽管隐藏了)仍然有好处。一个很好的例子是Maybe/Option MONAD,就像有例外一样,只有一种例外。基本上,此单元捕获了返回"错误代码"并在每个功能调用后对其进行检查的模式。这正是典型的定义所做的,除了"函数调用"我的意思是计算的每个(单元)步骤。可以说,这是非常低效的,尤其是当绝大多数时间没有错误时。如果您将Maybe反映到持续单元中,则必须支付CPSED代码的成本(GHC Haskell非常好处理它非常好),但您仅付费以在重要的地方(即catch语句)中检查"错误代码"。在哈斯克尔(Haskell)中,比丹尼亚兹(Danidiaz)提到的 Codensity monad是一个更好的选择,因为 last haskellers想要的是这样做,以便可以在其代码中透明地交织任意效果。

正如丹尼亚兹(Danidiaz)也提到的那样,使用基本上是持续单调或某些变体可以更容易或更有效地实施许多单子。回溯搜索是一个示例。虽然不是回溯中最新的事情,但我最喜欢的论文之一是在Haskell中键入逻辑变量。其中使用的技术也用于有线硬件说明语言中。同样来自科恩·克莱森(Koen Claesson)的是穷人的并发单元。在此示例中,更多想法的现代用途包括:用于确定性并行性的确定性并行性的单元,用于确定性并行性和可扩展的I/O经理,结合了可扩展网络服务的事件和线程。我敢肯定,我可以在Scala中找到类似的技术。如果没有提供,则可以使用持续单元在f#中实现异步工作流程。实际上,Don Syme的参考文献与我刚提到的完全相同的论文。如果您可以序列化功能但没有连续性,则可以使用持续单调来获取它们,并执行由Seaside之类的系统流行的序列化延续类型。即使没有可序列化的连续性,您也可以使用模式(基本上与异步)至少避免在本地存储连续性并仅发送键时避免回调。

最终,在Haskellers之外的人相对较少的人以任何身份使用了单子,而且我早些时候提到的,Haskellers倾向于使用更多的可连续的单子,尽管他们确实在内部使用了很多。尽管如此,延续的单子或延续的单子像事物一样,尤其是对于异步编程,变得越来越罕见。随着C#,F#,Scala,Swift甚至Java都开始合并支持Monadic或至少Monadic风格的编程,这些想法将变得更广泛地使用。如果节点开发人员对此更加熟悉,也许他们会意识到您可以将蛋糕和有关事件驱动的编程也吃掉。

提供更直接的f#特定答案(即使德里克也已经涵盖了它), continuation monad 几乎都捕获了的核心异步工作流程工作。

连续单元是一种函数,当给出延续时,最终将其调用结果(它可能永远不会称呼它,否则也可以反复称呼它):

type Cont<'T> = ('T -> unit) -> unit

f#异步计算更为复杂 - 它们会继续(如果成功),例外和取消延续,还包括取消令牌。使用稍微简化的定义,F#核心库使用(请参见此处的完整定义):

type AsyncParams =
    { token : CancellationToken
      econt : exn -> unit
      ccont : exn -> unit } 
type Async<'T> = ('T -> unit) * AsyncParams -> unit

如您所见,如果您忽略了AsyncParams,那几乎是持续单元。在f#中,我认为"古典"单子比直接实施机制更有用。在这里,持续单元提供了一个有用的模型,讲述了如何处理某些类型的计算 - 并且有了许多其他异步方面的方面,核心思想可用于实现异步计算。

我认为这与在经典学术作品或Haskell中使用的单调使用截然不同,在那里它们倾向于"原样"使用,并且可能以各种方式构建更复杂的单调,以捕获更复杂的行为。

这可能只是我的个人意见,但我会说延续单元本身并不是实际上有用的,但它是一些非常实用的想法的基础。(就像Lambda演算本身并不是真正有用的一样,但它可以看作是不错的实用语言的灵感!)

我肯定会发现它与使用显式递归实施的递延函数更容易读取使用持续单元实现的递归函数。例如,给定此树类型:

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

除了幼稚的褶皱会在深树上调用时会吹堆,因为它不是尾部递归,而使用持续单元编写的折叠不会。您当然可以使用明确的连续性写同样的东西,但是在我看来,它们会从算法的结构中分散注意力(并且将它们放置在适当的位置):

>
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

相关内容

  • 没有找到相关文章

最新更新