Reconciling Stacktraces with Computation Expressions, Revisited.

About a year ago, I wrote an article arguing for adding stacktrace support to F# computation expressions. Thanks to work by Lincoln Atkinson and Avi Avni, the upcoming F# 4.1 will be providing this functionality.

On the occasion of the release of VS2017 RC, I decided to spend some time adapting my continuation monad implementation to the new feature. The Bind implementation now looks as follows:

member __.Bind(f : Cont<'T>, g : 'T -> Cont<'S>,
                [<CallerMemberName>]?callerName : string,
                [<CallerFilePath>]?callerFilePath : string,
                [<CallerLineNumber>]?callerLineNumber : int) : Cont<'S> =

    fun sc ec ->
        let sc' (t : 'T) =
            match (try Ok(g t) with e -> Error e) with
            | Ok g -> g sc ec
            | Error e -> ec (SymbolicException.capture e)

        let ec' (se : SymbolicException) =
            let stackMsg =
                sprintf "   at %s in %s:line %d" 
                    callerName.Value 
                    callerFilePath.Value
                    callerLineNumber.Value

            ec (SymbolicException.append stackMsg se)

        f sc' ec'

and ReturnFrom:

member __.ReturnFrom(f : Cont<'T>,
                        [<CallerMemberName>]?callerName : string,
                        [<CallerFilePath>]?callerFilePath : string,
                        [<CallerLineNumber>]?callerLineNumber : int) : Cont<'T> =
    fun sc ec ->
        let ec' (se : SymbolicException) =
            let stackMsg =
                sprintf "   at %s in %s:line %d" 
                    callerName.Value 
                    callerFilePath.Value
                    callerLineNumber.Value

            ec (SymbolicException.append stackMsg se)

        f sc ec'

Running the odd/even example

let rec odd (n : int) = 
    cont {
        if n = 0 then return false
        else
            return! even (n - 1)
    }
 
and even (n : int) =
    cont {
        if n = 0 then return failwith "bug!"
        else
            return! odd (n - 1)
    }

odd 5 |> Cont.run

Now generates the stacktrace:

System.Exception: bug!
   at Program.even@119.Invoke(Unit unitVar) in C:\Users\eirik\devel\public\cont\Program.fs:line 119
   at Program.sc'@54-1.Invoke(a t) in C:\Users\eirik\devel\public\cont\Program.fs:line 54
   at odd in C:\Users\eirik\devel\public\cont\Program.fs:line 114
   at even in C:\Users\eirik\devel\public\cont\Program.fs:line 121
   at odd in C:\Users\eirik\devel\public\cont\Program.fs:line 114
   at even in C:\Users\eirik\devel\public\cont\Program.fs:line 121
   at odd in C:\Users\eirik\devel\public\cont\Program.fs:line 114
   at Program.ContModule.ec@102-1.Invoke(SymbolicException se) in C:\Users\eirik\devel\public\cont\Program.fs:line 102
   at Program.ContModule.run[T](FSharpFunc`2 cont) in C:\Users\eirik\devel\public\cont\Program.fs:line 103
   at <StartupCode$ConsoleApplication3>.$Program.main@() in C:\Users\eirik\devel\public\cont\Program.fs:line 106

The results look pretty promising. Looking forward to apply this technique to async and cloud builders!

You can find the full implementation here.

Advertisements