简体   繁体   English

F#重写计算表达式

[英]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. 为此我想重写实现而不使用计算表达式(continuation Monad),但我不能完成它。

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 . 所以,我想在不使用计算表达式K情况下重写Coroutine类的Put成员。

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... 我当然读过 以及其他几篇关于catamorphisms的文章,但是重写这个延续monand并不是很容易,因为它是重写Write Monad的例子......

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#?) (顺便说一句:有一些关于编译器应用于在普通F#中重写计算的所有规则的大量文档?)

Your version of Put is almost correct. 您的Put版本几乎是正确的。 Two issues though: 但有两个问题:

  • The bindK function is being used backwards, the parameters need to be swaped. bindK函数正在向后使用,需要交换参数。
  • task should be passed a Cont<_,_> -> Cont<_,_> , not a unit -> Cont<_,_> -> Cont<_,_> . task应该传递给Cont<_,_> -> Cont<_,_> ,而不是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 >>= : 使用bind ,最好声明一个operator >>=

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

that way 那样

  • do! translates to putting >>= fun () -> after 转换为放置>>= fun () ->之后
  • let! a = let! a = translates to putting >>= fun a -> after let! a =转换为放置>>= 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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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