Asynchronous – f# asynchronous workflow / task combined with free Monad

I'm trying to build a pipeline for message processing using the free monad pattern. My code looks like this:

module PipeMonad =
type PipeInstruction<'msgIn,'msgOut,'a> =
    | HandleAsync of 'msgIn * (Async<'msgOut> -> 'a)
    | SendOutAsync of 'msgOut * (Async -> 'a)

let private mapInstruction f = function
    | HandleAsync (x,next) -> HandleAsync (x,next >> f)
    | SendOutAsync (x,next) -> SendOutAsync (x,next >> f)

type PipeProgram<'msgIn,'a> =
    | Act of PipeInstruction<'msgIn,PipeProgram<'msgIn,'a>>
    | Stop of 'a

let rec bind f = function
    | Act x -> x |> mapInstruction (bind f) |> Act
    | Stop x -> f x

type PipeBuilder() =
    member __.Bind (x,f) = bind f x
    member __.Return x = Stop x
    member __.Zero () = Stop ()
    member __.ReturnFrom x = x

let pipe = PipeBuilder()
let handleAsync msgIn = Act (HandleAsync (msgIn,Stop))
let sendOutAsync msgOut = Act (SendOutAsync (msgOut,Stop))

I wrote it according to this article

However, it is important to make these methods asynchronous (preferably task, but async is acceptable), but when I create a builder for my pipeline, I can't figure out how to use it - how can I wait for the task <'msgout & gt; Or async <'msgout > so I can send it out and wait for this "send" task?

Now I have this Code:

let pipeline log msgIn =
    pipe {
        let! msgOut = handleAsync msgIn
        let result = async {
            let! msgOut = msgOut
            log msgOut
            return sendOutAsync msgOut
        }
        return result
    }

Return to pipeprogram <'b, 'a, async < < pipeprogram <'c, async > > >

Solution

In my understanding, the point of free monad is that you don't expose effects like async, so I don't think they should be used in pipeinstruction type The interpreter is where you add effects

In addition, free monad really only makes sense in Haskell. All you need to do is define an imitation function, and then you automatically complete the rest of the implementation In F #, you have to write the rest of the code, so using free is not much better than the traditional interpreter mode The Turtle program code you linked to is just an experiment – I don't recommend using free instead of actual code

Finally, it makes no sense to use this method if you already know the effect to be used and you won't have multiple explanations It makes sense only when the benefits outweigh complexity

Anyway, if you really want to write an interpreter Version (not free), that's what I do:

First, define instructions without any impact

/// The abstract instruction set
module PipeProgram =

    type PipeInstruction<'msgIn,'state> =
        | Handle of 'msgIn * ('msgOut -> PipeInstruction<'msgIn,'state>)
        | SendOut of 'msgOut * (unit -> PipeInstruction<'msgIn,'state>)
        | Stop of 'state

Then you can write a calculation expression for it:

/// A computation expression for a PipeProgram
module PipeProgramCE =
    open PipeProgram

    let rec bind f instruction =
        match instruction with
        | Handle (x,next) ->  Handle (x,(next >> bind f))
        | SendOut (x,next) -> SendOut (x,(next >> bind f))
        | Stop x -> f x

    type PipeBuilder() =
        member __.Bind (x,f) = bind f x
        member __.Return x = Stop x
        member __.Zero () = Stop ()
        member __.ReturnFrom x = x

let pipe = PipeProgramCE.PipeBuilder()

Then you can start writing your calculation expression This will help clear the design before you start using the interpreter

// helper functions for CE
let stop x = PipeProgram.Stop x
let handle x = PipeProgram.Handle (x,stop)
let sendOut x  = PipeProgram.SendOut (x,stop)

let exampleProgram : PipeProgram.PipeInstruction<string,string,string> = pipe {
    let! msgOut1 = handle "In1"
    do! sendOut msgOut1
    let! msgOut2 = handle "In2"
    do! sendOut msgOut2
    return msgOut2
    }

Once the description is described, the interpreter can be written As I said, if you're not writing multiple interpreters, maybe you don't need to do so at all

This is a non asynchronous version of the interpreter ("ID monad", just like it):

module PipeInterpreterSync =
    open PipeProgram

    let handle msgIn =
        printfn "In: %A"  msgIn
        let msgOut = System.Console.ReadLine()
        msgOut

    let sendOut msgOut =
        printfn "Out: %A"  msgOut
        ()

    let rec interpret instruction =
        match instruction with
        | Handle (x,next) ->
            let result = handle x
            result |> next |> interpret
        | SendOut (x,next) ->
            let result = sendOut x
            result |> next |> interpret
        | Stop x ->
            x

This is an asynchronous version:

module PipeInterpreterAsync =
    open PipeProgram

    /// Implementation of "handle" uses async/IO
    let handleAsync msgIn = async {
        printfn "In: %A"  msgIn
        let msgOut = System.Console.ReadLine()
        return msgOut
        }

    /// Implementation of "sendOut" uses async/IO
    let sendOutAsync msgOut = async {
        printfn "Out: %A"  msgOut
        return ()
        }

    let rec interpret instruction =
        match instruction with
        | Handle (x,next) -> async {
            let! result = handleAsync x
            return! result |> next |> interpret
            }
        | SendOut (x,next) -> async {
            do! sendOutAsync x
            return! () |> next |> interpret
            }
        | Stop x -> x
The content of this article comes from the network collection of netizens. It is used as a learning reference. The copyright belongs to the original author.
THE END
分享
二维码
< <上一篇
下一篇>>