I'm trying to get started working with agents in F# via the MailboxProcessor<'Msg> class, and I quickly realized I had no proper handling for exceptions. In a Haskellian world, there wouldn't be any exceptions, and so the proper way to handle issues would be to simply provide them as a response case; so an agent could reply with something like:
type AgentResponse =
| HandledData of string
| InvalidData of string
One could then call the agent's .PostAndReply method and get an InvalidData containing a message indicating why the data is invalid. However, this isn't Haskell, and sometimes exceptions happen. So if I do this:
let agent =
new MailboxProcessor<string * AsyncReplyChannel<AgentResponse>>(fun inbox ->
async {
while true do
let! (msg, replyChannel) = inbox.Receive()
if msg = "die" then failwith "Unknown exception encountered!"
else replyChannel.Reply(HandledData(msg))
})
agent.Start()
agent.PostAndReply (fun rc -> "die", rc)
agent.PostAndReply (fun rc -> "Test", rc)
The second call to agent.PostAndReply blocks indefinitely. When not using AsyncReplyChannel and therefore just calling agent.Post, the calls don't block, but once the agent encounters an exception, new messages just stay in the queue. Restarting the agent in either case appears impossible, as the agent.Start function returns an InvalidOperationException when called again, and the natural way to handle this seems to be to create a new agent with a clean state, but then all queued messages are lost.
Beyond just wrapping the entire body of an agent in try..with, is there any good way to get an agent to continue running after an exception? Alternatively, has a "standard" way of dealing with this been established that someone can point me to?
You do have exceptions in Haskell : try Data.List.head [] in ghci....
Unfortunately, lack of dependent types means, in Haskell or in F#, that we can write type correct code which has no computational meaning.
Practically, wrapping the with a try ... with block is not a bad idea for dealing with exception. You dont need to wrap the entire body, just the non-pure part of your code though.
Then classically, you yield back a value made with the appropriate constructor.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With