(How) can I make this monadic bind tail-recursive?

后端 未结 1 1836
悲哀的现实
悲哀的现实 2021-02-08 14:00

I have this monad called Desync -

[]
module DesyncModule =

    /// The Desync monad. Allows the user to define in a sequential style an operatio         


        
相关标签:
1条回答
  • 2021-02-08 14:18

    It seems to me your monad is a State with error handling.

    It's basically ErrorT< State<'s,Either<'e,'a>>> but the error branch binds again which is not very clear to me why.

    Anyway I was able to reproduce your Stack Overflow with a basic State monad:

    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
    

    As stated in the comments one way to solve this is to use a StateT<'s,Cont<'r,'a>>.

    Here's an example of the solution. At the end there is a test with the zipIndex function which blows the stack as well when defined with a normal State monad.

    Note you don't need to use the Monad Transformers from FsControl (now FSharpPlus), I use them because it's easier for me since I write less code but you can always create your transformed monad by hand.

    0 讨论(0)
提交回复
热议问题