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