F # переписать выражение вычисления - PullRequest
0 голосов
/ 17 декабря 2018

Я изучаю продолжения, потому что хочу использовать интересные сопрограммы ... в любом случае, я хочу лучше понять одну найденную реализацию.

Для этого я хочу переписать реализацию без использованиявычислительное выражение (продолжение Monad), но я не совсем могу это сделать.

У меня есть это:

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``()

Итак, я хочу переписать Put членКласс сопрограмм без использования вычислительного выражения K.

Я, конечно, прочитал это и это и несколько других статей о катаморфизмах , нопереписать этот монанд продолжения не так просто, как, например, переписать монаду записи ...

Я пробую несколько способов, это один из них:

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)

Конечно, это не работает: (

(Кстати: есть обширная документация по всем правилам, которые применяются компилятором для перезаписи вычислений на обычном F #?)

1 Ответ

0 голосов
/ 17 декабря 2018

Ваша версия Put почти верна.Однако есть две проблемы:

  • Функция bindK используется в обратном направлении, параметры необходимо поменять местами.
  • task следует передавать Cont<_,_> -> Cont<_,_>, а не unit -> Cont<_,_> -> Cont<_,_>.

Исправление этих проблем может выглядеть так:

    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)

Конечно, это не слишком элегантно.При использовании bind лучше объявить оператор >>=:

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

таким образом

  • do! означает перевод >>= fun () -> после
  • let! a = означает >>= fun a -> после

, и тогда ваш код будет выглядеть немного лучше:

    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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...