Reputation: 105
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.
Upvotes: 3
Views: 109
Reputation: 80765
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")
Upvotes: 5