(如何)我可以使这个单子绑定尾递归吗?

2024-01-19

我有一个名为 Desync 的 monad -

[<AutoOpen>]
module DesyncModule =

    /// The Desync monad. Allows the user to define in a sequential style an operation that spans
    /// across a bounded number of events. Span is bounded because I've yet to figure out how to
    /// make Desync implementation tail-recursive (see note about unbounded recursion in bind). And
    /// frankly, I'm not sure if there is a tail-recursive implementation of it...
    type [<NoComparison; NoEquality>] Desync<'e, 's, 'a> =
        Desync of ('s -> 's * Either<'e -> Desync<'e, 's, 'a>, 'a>)

    /// Monadic return for the Desync monad.
    let internal returnM (a : 'a) : Desync<'e, 's, 'a> =
        Desync (fun s -> (s, Right a))

    /// Monadic bind for the Desync monad.
    let rec internal bind (m : Desync<'e, 's, 'a>) (cont : 'a -> Desync<'e, 's, 'b>) : Desync<'e, 's, 'b> =
        Desync (fun s ->
            match (match m with Desync f -> f s) with
            //                              ^--- NOTE: unbounded recursion here
            | (s', Left m') -> (s', Left (fun e -> bind (m' e) cont))
            | (s', Right v) -> match cont v with Desync f -> f s')

    /// Builds the Desync monad.
    type DesyncBuilder () =
        member this.Return op = returnM op
        member this.Bind (m, cont) = bind m cont

    /// The Desync builder.
    let desync = DesyncBuilder ()

它允许使用计算表达式以看似顺序的方式编写跨多个游戏周期执行的游戏逻辑的实现。

不幸的是,当用于持续无限数量游戏周期的任务时,它会因 StackOverflowException 崩溃。即使它没有崩溃,它最终也会出现像这样的笨拙的堆栈跟踪 -

InfinityRpg.exe!InfinityRpg.GameplayDispatcherModule.desync@525-20.Invoke(Nu.SimulationModule.Event<Prime.EitherModule.Either<Microsoft.FSharp.Core.Unit,Microsoft.FSharp.Core.Unit>,Nu.SimulationModule.Screen> _arg10) Line 530   F#
Prime.exe!Prime.DesyncModule.bind@20<Nu.SimulationModule.Event<Prime.EitherModule.Either<Microsoft.FSharp.Core.Unit,Microsoft.FSharp.Core.Unit>,Nu.SimulationModule.Screen>,Nu.SimulationModule.World,Microsoft.FSharp.Core.Unit,Nu.SimulationModule.Event<Prime.EitherModule.Either<Microsoft.FSharp.Core.Unit,Microsoft.FSharp.Core.Unit>,Nu.SimulationModule.Screen>>.Invoke(Nu.SimulationModule.World s) Line 24    F#
Prime.exe!Prime.DesyncModule.bind@20<Nu.SimulationModule.Event<Prime.EitherModule.Either<Microsoft.FSharp.Core.Unit,Microsoft.FSharp.Core.Unit>,Nu.SimulationModule.Screen>,Nu.SimulationModule.World,Microsoft.FSharp.Core.Unit,Microsoft.FSharp.Core.Unit>.Invoke(Nu.SimulationModule.World s) Line 21    F#
Prime.exe!Prime.DesyncModule.bind@20<Nu.SimulationModule.Event<Prime.EitherModule.Either<Microsoft.FSharp.Core.Unit,Microsoft.FSharp.Core.Unit>,Nu.SimulationModule.Screen>,Nu.SimulationModule.World,Microsoft.FSharp.Core.Unit,Microsoft.FSharp.Core.Unit>.Invoke(Nu.SimulationModule.World s) Line 21    F#
Prime.exe!Prime.DesyncModule.bind@20<Nu.SimulationModule.Event<Prime.EitherModule.Either<Microsoft.FSharp.Core.Unit,Microsoft.FSharp.Core.Unit>,Nu.SimulationModule.Screen>,Nu.SimulationModule.World,Microsoft.FSharp.Core.Unit,Microsoft.FSharp.Core.Unit>.Invoke(Nu.SimulationModule.World s) Line 21    F#
Prime.exe!Prime.DesyncModule.bind@20<Nu.SimulationModule.Event<Prime.EitherModule.Either<Microsoft.FSharp.Core.Unit,Microsoft.FSharp.Core.Unit>,Nu.SimulationModule.Screen>,Nu.SimulationModule.World,Microsoft.FSharp.Core.Unit,Microsoft.FSharp.Core.Unit>.Invoke(Nu.SimulationModule.World s) Line 21    F#
Prime.exe!Prime.DesyncModule.bind@20<Nu.SimulationModule.Event<Prime.EitherModule.Either<Microsoft.FSharp.Core.Unit,Microsoft.FSharp.Core.Unit>,Nu.SimulationModule.Screen>,Nu.SimulationModule.World,Microsoft.FSharp.Core.Unit,Microsoft.FSharp.Core.Unit>.Invoke(Nu.SimulationModule.World s) Line 21    F#
Prime.exe!Prime.DesyncModule.bind@20<Nu.SimulationModule.Event<Prime.EitherModule.Either<Microsoft.FSharp.Core.Unit,Microsoft.FSharp.Core.Unit>,Nu.SimulationModule.Screen>,Nu.SimulationModule.World,Microsoft.FSharp.Core.Unit,Microsoft.FSharp.Core.Unit>.Invoke(Nu.SimulationModule.World s) Line 21    F#
Prime.exe!Prime.DesyncModule.bind@20<Nu.SimulationModule.Event<Prime.EitherModule.Either<Microsoft.FSharp.Core.Unit,Microsoft.FSharp.Core.Unit>,Nu.SimulationModule.Screen>,Nu.SimulationModule.World,Microsoft.FSharp.Core.Unit,Microsoft.FSharp.Core.Unit>.Invoke(Nu.SimulationModule.World s) Line 21    F#
Prime.exe!Prime.Desync.step<Nu.SimulationModule.Event<Prime.EitherModule.Either<Microsoft.FSharp.Core.Unit,Microsoft.FSharp.Core.Unit>,Nu.SimulationModule.Screen>,Nu.SimulationModule.World,Microsoft.FSharp.Core.Unit>(Prime.DesyncModule.Desync<Nu.SimulationModule.Event<Prime.EitherModule.Either<Microsoft.FSharp.Core.Unit,Microsoft.FSharp.Core.Unit>,Nu.SimulationModule.Screen>,Nu.SimulationModule.World,Microsoft.FSharp.Core.Unit> m, Nu.SimulationModule.World s) Line 71 F#
Prime.exe!Prime.Desync.advanceDesync<Nu.SimulationModule.Event<Prime.EitherModule.Either<Microsoft.FSharp.Core.Unit,Microsoft.FSharp.Core.Unit>,Nu.SimulationModule.Screen>,Nu.SimulationModule.World,Microsoft.FSharp.Core.Unit>(Microsoft.FSharp.Core.FSharpFunc<Nu.SimulationModule.Event<Prime.EitherModule.Either<Microsoft.FSharp.Core.Unit,Microsoft.FSharp.Core.Unit>,Nu.SimulationModule.Screen>,Prime.DesyncModule.Desync<Nu.SimulationModule.Event<Prime.EitherModule.Either<Microsoft.FSharp.Core.Unit,Microsoft.FSharp.Core.Unit>,Nu.SimulationModule.Screen>,Nu.SimulationModule.World,Microsoft.FSharp.Core.Unit>> m, Nu.SimulationModule.Event<Prime.EitherModule.Either<Microsoft.FSharp.Core.Unit,Microsoft.FSharp.Core.Unit>,Nu.SimulationModule.Screen> e, Nu.SimulationModule.World s) Line 75 F#
Nu.exe!Nu.Desync.advance@98<Prime.EitherModule.Either<Microsoft.FSharp.Core.Unit,Microsoft.FSharp.Core.Unit>,Nu.SimulationModule.Screen>.Invoke(Nu.SimulationModule.Event<Prime.EitherModule.Either<Microsoft.FSharp.Core.Unit,Microsoft.FSharp.Core.Unit>,Nu.SimulationModule.Screen> event, Nu.SimulationModule.World world) Line 100 F#
Nu.exe!Nu.Desync.subscription@104-16<Prime.EitherModule.Either<Microsoft.FSharp.Core.Unit,Microsoft.FSharp.Core.Unit>,Nu.SimulationModule.Screen>.Invoke(Nu.SimulationModule.Event<Prime.EitherModule.Either<Microsoft.FSharp.Core.Unit,Microsoft.FSharp.Core.Unit>,Nu.SimulationModule.Screen> event, Nu.SimulationModule.World world) Line 105    F#
Nu.exe!Nu.World.boxableSubscription@165<Prime.EitherModule.Either<Microsoft.FSharp.Core.Unit,Microsoft.FSharp.Core.Unit>,Nu.SimulationModule.Screen>.Invoke(object event, Nu.SimulationModule.World world) Line 166 F#

我希望通过使绑定函数的 Left case 成为尾递归来解决问题。但是,我不确定两件事 -

1)如果可以的话,并且 2)实际将如何完成。

如果在这里不可能使绑定尾递归,是否有某种方法可以重组我的 monad 以使其成为尾递归?

编辑 3(包含之前的编辑):这里是实现异步组合器的附加代码,我将用它来演示堆栈溢出 -

module Desync =

    /// Get the state.
    let get : Desync<'e, 's, 's> =
        Desync (fun s -> (s, Right s))

    /// Set the state.
    let set s : Desync<'e, 's, unit> =
        Desync (fun _ -> (s, Right ()))

    /// Loop in a desynchronous context while 'pred' evaluate to true.
    let rec loop (i : 'i) (next : 'i -> 'i) (pred : 'i -> 's -> bool) (m : 'i -> Desync<'e, 's, unit>) =
        desync {
            let! s = get
            do! if pred i s then
                    desync {
                        do! m i
                        let i = next i
                        do! loop i next pred m }
                else returnM () }

    /// Loop in a desynchronous context while 'pred' evaluates to true.
    let during (pred : 's -> bool) (m : Desync<'e, 's, unit>) =
        loop () id (fun _ -> pred) (fun _ -> m)

    /// Step once into a desync.
    let step (m : Desync<'e, 's, 'a>) (s : 's) : 's * Either<'e -> Desync<'e, 's, 'a>, 'a> =
    match m with Desync f -> f s

    /// Run a desync to its end, providing e for all its steps.
    let rec runDesync (m : Desync<'e, 's, 'a>) (e : 'e) (s : 's) : ('s * 'a) =
        match step m s with
        | (s', Left m') -> runDesync (m' e) e s'
        | (s', Right v) -> (s', v)

这是 Either 的实现 -

[<AutoOpen>]
module EitherModule =

    /// Haskell-style Either type.
    type Either<'l, 'r> =
        | Right of 'r
        | Left of 'l

最后,这是一行简单的代码,它将产生堆栈溢出 -

open Desync
ignore <| runDesync (desync { do! during (fun _ -> true) (returnM ()) }) () ()

在我看来,你的 monad 是一个具有错误处理功能的状态。

基本上是 ErrorT< State<'s,Either<'e,'a>>> 但错误分支再次绑定,我不太清楚为什么。

无论如何,我能够使用基本的 State monad 重现您的 Stack Overflow:

type State<'S,'A> = State of ('S->('A * 'S))

module State =
    let run (State x) = x :'s->_
    let get() = State (fun s -> (s , s))  :State<'s,_>
    let put x = State (fun _ -> ((), x))  :State<'s,_>
    let result a = State(fun s -> (a, s))
    let bind (State m) k = State(fun s -> 
                                    let (a, s') = m s
                                    let (State u) = (k a) 
                                    u s')     :State<'s,'b>

    type StateBuilder() =
        member this.Return op = result op
        member this.Bind (m, cont) = bind m cont

    let state = StateBuilder()

    let rec loop (i: 'i) (next: 'i -> 'i) (pred: 'i -> 's -> bool) (m: 'i -> State<'s, unit>) =
        state {
            let! s = get()
            do! if pred i s then
                    state {
                        do! m i
                        let i = next i
                        do! loop i next pred m }
                else result () }

    let during (pred : 's -> bool) (m : State<'s, unit>) =
        loop () id (fun _ -> pred) (fun _ -> m)

// test
open State
ignore <| run (state { do! during (fun c -> true) (result ()) })  () // boom

正如评论中所述,解决此问题的一种方法是使用StateT<'s,Cont<'r,'a>>.

这是一个例子solution https://gist.github.com/gusty/db59b8ad482bdc44ca56。最后有一个使用 zipIndex 函数的测试,当使用正常的 State monad 定义时,该函数也会破坏堆栈。

请注意,您不需要使用 FsControl(现在是 FSharpPlus)中的 Monad Transformers,我使用它们是因为它对我来说更容易,因为我编写的代码较少,但您始终可以手动创建转换后的 monad。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

(如何)我可以使这个单子绑定尾递归吗? 的相关文章

  • 证明 Applicative 和 Monad 的序列定义的等价性

    我怎样才能正确地证明这一点 sequenceA Traversable t Applicative f gt t f a gt f t a sequenceA pure sequenceA x xs pure lt gt x lt gt s
  • “Alternative”中的模式匹配

    我有一个函数 它的参数进行模式匹配以生成计算StateT Maybe 可以这么说 此计算在运行时可能会失败 在这种情况下 我希望当前的模式匹配分支失败 我非常怀疑是否有可能有类似的东西 compute Int gt StateT Maybe
  • F# 中的异步 EF 查询

    在使用 EF6 的 C 中 我可以轻松地进行如下异步操作 using var context new MyDbContext var item await context SomeEntities Where e gt e Id 1 Fir
  • 在 F# 中的 Choice 之上构建 Either(或 Result)

    我根据 Scott Wlaschin 中的信息构建了一个成功 失败的 monadblog http fsharpforfunandprofit com posts computation expressions wrapper types
  • 双前向/后向管道操作符是否有记录?

    我记得读过有关双管道运算符的内容 gt 和 Example let print a b sprintf O O a b 1 2 gt print val it string 1 2 双 向前 向后 管道运算符记录在以下列表中MSDN 上的
  • FParsec:如何组合解析器以便它们以任意顺序匹配

    任务是找到特定的键值对并解析它们 这些对可以按任何顺序出现 我的部分工作尝试 open FParsec type Parser lt a gt Parser lt a unit gt type Status Running Done typ
  • 是 F# 映射上的迭代还是集合中序遍历?

    AFAIK F Map 和 set 被实现为红黑树 所以我猜这些的迭代将是有序遍历 我做了一些测试 迭代结果总是排序的 但我想确定一下 是按顺序遍历吗 MSDN 上的文档非常适合解决这个问题 例如 返回值Set toSeq http msd
  • F# 2010 Seq.generate_using

    Visual Studio 2010 中的 Seq generate using 是否有替代 解决方法 FSharp PowerPack dll 不适用于 2010 AFAIK 很抱歉 2010 年的 PowerPack 尚未上市 我不记得
  • 何时在 F# 中使用区分联合与记录类型

    在继续讨论复杂的示例之前 我试图先弄清楚 F 的基础知识 我正在学习的材料介绍了区分联合和记录类型 我已经审阅了两者的材料 但我仍然不清楚为什么我们要使用其中之一而不是另一个 我创建的大多数玩具示例似乎都可以在两者中实现 记录似乎非常接近我
  • GHC 可以为 monad 转换器派生 Functor 和 Applicative 实例吗?

    我正在尝试实施MaybeT本着mtl图书馆 使用这个非编译解决方案 LANGUAGE FlexibleInstances MultiParamTypeClasses UndecidableInstances import Control M
  • F# 中类型约束的顺序

    这适用于 F 4 0 type Something lt a b when b gt seq lt b gt gt 这不会 type Something lt b when b gt seq lt b gt a gt 类型名称中出现意外的符
  • f# 运行总计序列

    好吧 这看起来应该很容易 但我就是不明白 如果我有一个数字序列 如何生成由运行总计组成的新序列 例如 对于序列 1 2 3 4 我想将其映射到 1 3 6 10 以适当的功能方式 Use List scan https msdn micro
  • 方案中的尾递归幂函数

    我在方案中编写尾递归幂函数时遇到问题 我想使用辅助函数来编写该函数 我知道我需要一个参数来保存累计值 但在那之后我就陷入了困境 我的代码如下 define pow tr a b define pow tr h result if b 0 r
  • F# 对于 OO 或命令式来说缺少什么? [关闭]

    就目前情况而言 这个问题不太适合我们的问答形式 我们希望答案得到事实 参考资料或专业知识的支持 但这个问题可能会引发辩论 争论 民意调查或扩展讨论 如果您觉得这个问题可以改进并可能重新开放 访问帮助中心 help reopen questi
  • 该表达式的类型为 int,但此处与 unit 类型一起使用

    我试图在 F 中获得与此 vb net 代码完全相同的 非功能性的 Function FastPow ByVal num As Double ByVal exp As Integer As Double Dim res As Double
  • 将属性应用于返回值 - 在 F# 中

    在 C 中 可以将属性应用于方法的返回 return DynamicAttribute public object Xyz return new ExpandoObject 这在 F 中可能吗 背景 我想要一个用 F 编写的库的方法 该方法
  • 在 Deedle 系列中算得上独一无二

    我想对我的数据框中的系列有一个概述 例如 pandas 的唯一值计数 我不知道是否有内置函数可以实现这一点 到目前为止 我已经完成了一个函数来获取不同特征的数量 我可以设法完成这项工作 我的问题只是关于内置功能 let unique s D
  • 在 F# 类型提供程序中发出生成的类型

    我创建了一个简单的生成类型提供程序 它采用重新组织类型的程序集的路径 将它们置于类型提供程序命名空间下 如果您愿意 可以说是内部化 相关代码的链接在这里https github com colinbull Playground https
  • 我应该在 Prolog 和一般情况下避免尾递归吗?

    我正在阅读 立即学习 Prolog 在线书籍 以获取乐趣 我正在尝试编写一个谓词 该谓词遍历列表的每个成员并向其添加一个 使用累加器 我已经在没有尾递归的情况下轻松完成了 addone addone X Xs Y Ys Y is X 1 a
  • 将 F# 类型保存到数据库

    A lot http gorodinski com blog 2013 02 17 domain driven design with fsharp and eventstore f 文章数推荐 http fsharpforfunandpr

随机推荐