MailboxProcessor.PostAndReply design choice
Asked Answered
G

1

12

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

Gery answered 29/2, 2012 at 14:57 Comment(0)
S
9

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).

Shig answered 29/2, 2012 at 15:10 Comment(2)
Ok thanks, I see. If it was me, I would have required in the constructor an additional parameter: a function that constructs a message from an AsyncReplyChannel (AsyncReplyChannel<'Reply> -> 'Msg) additionnally to the body of the agent. Indeed, from a user-centric point of view, I fail to see why would people want to introduce different ways to construct a msg from a reply channel, when calling PostAndReply, though it would be less generic.Gery
@Gery - That would not work either. It is quite common to have different ways of constructing 'Msg values that contain AsyncReplyChannel. For example, the blocking queue agent (see MSDN msdn.microsoft.com/en-us/library/hh297096.aspx) has two different messages that both carry a reply channel and so you have two ways of constructing the message. That said, I agree that the signature is pretty confusing. It would be nice to have more readable alternative, but I just cannot think what that might be...Shig

© 2022 - 2024 — McMap. All rights reserved.