简体   繁体   中英

F# rewrite computation expression

I'm studying continuations because I want to make some interesting use of coroutines... anyway, I want to better understand one implementation I found.

To do so I want to rewrite the implementation without using the computation expression (continuation Monad), but I'm not quite able to do it.

I have this:

type K<'T,'r> = (('T -> 'r) -> 'r)

let returnK x = (fun k -> k x)
let bindK m f = (fun k -> m (fun a -> f a k))
let runK (c:K<_,_>) cont = c cont
let callcK (f: ('T -> K<'b,'r>) -> K<'T,'r>) : K<'T,'r> =
    fun cont -> runK (f (fun a -> (fun _ -> cont a))) cont

type ContinuationBuilder() =
    member __.Return(x) = returnK x
    member __.ReturnFrom(x) =  x
    member __.Bind(m,f) =  bindK m f
    member this.Zero () = this.Return ()

let K = new ContinuationBuilder()

/// The coroutine type from http://fssnip.net/7M
type Coroutine() =
    let tasks = new System.Collections.Generic.Queue<K<unit,unit>>()

    member this.Put(task) =

        let withYield = K {
            do! callcK (fun exit ->
                    task (fun () ->
                        callcK (fun c ->
                            tasks.Enqueue(c())
                            exit ())))
            if tasks.Count <> 0 then
                do! tasks.Dequeue() }
        tasks.Enqueue(withYield)

    member this.Run() =
        runK (tasks.Dequeue()) ignore 

// from FSharpx tests
let ``When running a coroutine it should yield elements in turn``() =
  // This test comes from the sample on http://fssnip.net/7M
  let actual = System.Text.StringBuilder()
  let coroutine = Coroutine()
  coroutine.Put(fun yield' -> K {
    actual.Append("A") |> ignore
    do! yield' ()
    actual.Append("B") |> ignore
    do! yield' ()
    actual.Append("C") |> ignore
    do! yield' ()
  })
  coroutine.Put(fun yield' -> K {
    actual.Append("1") |> ignore
    do! yield' ()
    actual.Append("2") |> ignore
    do! yield' ()
  })
  coroutine.Run()
  actual.ToString() = "A1B2C"

``When running a coroutine it should yield elements in turn``()

So, I want rewrite the Put member of the Coroutine class without using the computation expression K .

I have read of course this and this and several other articles about catamorphisms but it is not quite easy to rewrite this continuation monand as it is to rewrite the Write Monad for example...

I try several ways, this is one of them:

member this.Put(task) =

    let withYield =
        bindK
            (callcK (fun exit ->
                task (fun () ->
                    callcK (fun c ->
                        tasks.Enqueue(c())
                        exit ()))))
            (fun () ->
                if tasks.Count <> 0 
                then tasks.Dequeue()
                else returnK ())
    tasks.Enqueue(withYield)

Of course it does not work :(

(By the way: there is some extensive documentation of all rules the compiler apply to rewrite the computation in plain F#?)

Your version of Put is almost correct. Two issues though:

  • The bindK function is being used backwards, the parameters need to be swaped.
  • task should be passed a Cont<_,_> -> Cont<_,_> , not a unit -> Cont<_,_> -> Cont<_,_> .

Fixing those issues it could look like this:

    member this.Put(task) =
        let withYield =
            bindK
                (fun () ->
                    if tasks.Count <> 0 
                    then tasks.Dequeue()
                    else returnK ())
                (callcK (fun exit ->
                    task (
                        callcK (fun c ->
                            tasks.Enqueue(c())
                            exit ()))))
        tasks.Enqueue(withYield)

Of course it is not too elegant. When using bind it is better to declare an operator >>= :

let (>>=) c f = bindK f c

that way

  • do! translates to putting >>= fun () -> after
  • let! a = let! a = translates to putting >>= fun a -> after

and then your code will look a little bit better:

    member this.Put2(task) =
        let withYield =
            callcK( fun exit ->
                    task( callcK (fun c ->  
                        tasks.Enqueue(c())
                        exit())
                    )
                ) >>= fun () -> 
            if tasks.Count <> 0 then
                tasks.Dequeue() 
            else returnK ()
        tasks.Enqueue withYield

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM