Can an F# agent with multiple recursive async bodies use multiple inbox.Receive() in each?

116 Views Asked by At

I have a multi-state F# MailboxProcessor example here, just wondering why it compiles but the behavior is unexpected - can F# agents only have one inbox.Receive() statement in the passed in lambda function? I am trying to follow the general example pattern provided in "Expert F# 3.0" page 284, where the use of multiple async {} bodies allows multiple states, but it isn't specific as to whether the inbox.Receive() can be used in each async?

open System

let mb1<'T> = MailboxProcessor<string>.Start(fun inbox ->
                        let rec loop1 (n:int) = async {
                                    printfn "loop1 entry "
                                    let! msg = inbox.Receive()
                                    do! Async.Sleep(1000)
                                    printfn "loop1 calling loop2"  //msg received %A" msg
                                    return! loop2 (n+1) }

                        and loop2 (x:int) =     async {
                                    printfn "loop2 entry"
                                    let! msg2 = inbox.Receive()
                                    printfn "loop2 msg received %A" msg2
                                    printfn "loop2 calling loop1"
                                    return! loop1 (x+1) }
        loop2 0                    
                                        )

mb1.Post("data message 1")
mb1.Post("data message 2")

yields

loop2 entry
loop2 msg received "data message 1"
loop2 calling loop1
loop1 entry 
val it : unit = ()
> 
loop2 entry
loop2 msg received "data message 2"
loop2 calling loop1
loop1 entry 
val it : unit = ()
> 

so the let! msg = inbox.Receive() in loop 1 is skipped? I would have thought that loop2 is completed by the return! loop1 and that the let! assignment of the inbox.Receive() is specific to the async block in which it used.

1

There are 1 best solutions below

3
On BEST ANSWER

This has to do with so called "value restriction".

Because you gave mb1 an explicit generic parameter, it gets compiled as a function, not a value. Without going into too much detail, the compiler has to do this in order to facilitate the possibility of accessing the value with different generic arguments.

As a result, every time you "reference" mb1, what actually happens is a function call that creates a brand new agent, so the two .Post calls happen on different objects.

To rectify the situation, either remove generic argument from the value, thus making it a real computed-once value:

let mb1 = MailboxProcessor<string>( ...

Or make sure you're calling it only once:

let mb = mb1
mb.Post("data message 1")
mb.Post("data message 2")