Search code examples
f#agentsmailboxprocessor

MailboxProcessor.PostAndReply design choice


Looking at:

member this.PostAndReply : (AsyncReplyChannel<'Reply> -> 'Msg) * ?int -> 'Reply

I can't figure out why the signature looks so counter-intuitive to me. What we want to do is posting a message to an agent, and wait for a reply. Why do we have to give him a weird function as a 'message'?

See again this MSDN snippet:

let rec loop() =
    printf "> "
    let input = Console.ReadLine()
    printThreadId("Console loop")
    let reply = agent.PostAndReply(fun replyChannel -> input, replyChannel)
    if (reply <> "Stopping.") then
        printfn "Reply: %s" reply
       loop()
    else
        ()
loop()

I'd rather prefer something like this:

member this.PostAndReply : 'Msg * ?int -> 'Reply

Thanks


Solution

  • This type signature looks pretty confusing when you see it for the first time, but it does make sense.

    The F# library design
    The idea behind the is that when you call PostAndReply you need to give it a function that:

    • constructs a message of type 'Msg (to be sent to the agent)
    • after the F# runtime builds a channel for sending messages back to the caller (channels are represented as values of type AsyncReplyChannel<'Reply>).

    The message that you construct needs to contain the reply channel, but the F# library does not know how you want to represent your messages (and so it does not know how you want to store the reply channel in the message). As a result, the library asks you to write a function that will construct the message for the agent after the system constructs the channel.

    Your alternative suggestion
    The problem with your suggestion is that if PostAndReply had a type 'Msg -> 'Reply, the message that the agent receives after it calls Receive would be of the following type:

    'Msg * AsyncReplyChannel<'Reply>
    

    ... so every message received to the agent would also have to carry a channel for sending replies back. However, you probably don't want to send a reply back for every received message, so this wouldn't really work. Maybe you could use something like:

    'Msg * option<AsyncReplyChannel<'Reply>>
    

    ... but that's just getting more complicated (and it still isn't quite right, because you can only reply to some messages from 'Msg, but not to all of them).