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