Mavnn's blog

Stuff from my brain

Corrected Error Handling Computational Expression

I’ve been wanting to write code like this in F#, and know that any exceptions within a bound expression in an audit { } block will not only get caught, but that an external auditing service will get notified that the operation has failed.

let agent =
AutoCancelAgent.Start(fun inbox -> async {
while true do
audit {
use! client = audit { return getClient () }
Log "Checking email..."
do! audit { return CollectMessages client }
expunge client
} |> doAuditedProcess
do! Async.Sleep(match pollInterval with Interval s -> s)
})
view raw AuditedCode.fs hosted with ❤ by GitHub

Unfortunately, it turns out my code in my post on error handling ( https//blog.mavnn.co.uk/playing-with-error-handling-strategies ) was flawed in its ability to handle errors. The irony has not escaped me.

The issue is with the eager evaluation of arguments to the TryFinally method of the builder. If it takes you a while to work out what that means, don’t worry: it took me about 2 days to wrap my head round it and work out how to correct the code to make it behave as I would have expected.

To make things work correctly, the type returned by the computational expression pretty much has to be a deferred function of some kind.

So, the Interface, now renamed IAuditBuilder, gains a couple of helper functions and becomes:

open System
type AuditedProcess<'T> = unit -> Option<'T>
let runAuditedProcess (auditedProcess : AuditedProcess<_>) =
auditedProcess ()
let doAuditedProcess (auditedProcess : AuditedProcess<_>) =
runAuditedProcess auditedProcess |> ignore
type IAuditBuilder =
abstract Bind : AuditedProcess<'T> * ('T -> AuditedProcess<'U>) -> AuditedProcess<'U>
abstract Delay : (unit -> AuditedProcess<'T>) -> AuditedProcess<'T>
abstract Return : 'T -> AuditedProcess<'T>
abstract ReturnFrom : AuditedProcess<'T> -> AuditedProcess<'T>
abstract TryFinally : AuditedProcess<'T> * (unit -> unit) -> AuditedProcess<'T>
abstract Using : 'T * ('T -> AuditedProcess<'U>) -> AuditedProcess<'U> when 'T :> IDisposable
abstract Zero : unit -> AuditedProcess<'T>

The implementation of the TestAuditBuilder (only logs to console on error) becomes:

type TestErrorBuilder () =
member this.Bind (expr, func) =
try
match expr () with
| None -> fun () -> None
| Some r ->
func r
with
| _ as e ->
printfn "%A" e
fun () -> None
member this.Delay (f: unit -> AuditedProcess<'U>) : AuditedProcess<'U> =
this.Bind (this.Return (), f)
member this.Return<'T> (value : 'T) : AuditedProcess<'T> =
fun () -> Some value
member this.ReturnFrom (value) =
value
member this.TryFinally (expr, comp) =
try expr
finally comp ()
member this.Using (res : #System.IDisposable, expr) =
this.TryFinally(expr res, fun () -> res.Dispose())
member this.Zero () =
fun () -> None
interface IAuditBuilder with
member this.Bind (expr, func) = this.Bind (expr, func)
member this.Delay (f) = this.Delay (f)
member this.Return (value) = this.Return (value)
member this.ReturnFrom (value) = this.ReturnFrom(value)
member this.TryFinally (expr, comp) = this.TryFinally (expr, comp)
member this.Using (res : #System.IDisposable, expr) = this.Using (res, expr)
member this.Zero () = this.Zero ()

So: many thanks to Johann Deneux for patiently pointing out to me what the flaw in the original code was. I hope this example of a lazy computational expression is useful to other starting out down this rabbit hole of monadic weirdness. At least the resulting code looks pretty nice and readable now that the builder is fixed.

Comments