TL;DR: how to raise a previously caught exception later on, while preserving the original exception's stacktrace.
Since I think this is useful with the Result monad or computation expression, esp. since that pattern is often used for wrapping an exception without throwing it, here's a worked out example of that:
type Result<'TResult, 'TError> =
| Success of 'TResult
| Fail of 'TError
module Result =
let bind f =
function
| Success v -> f v
| Fail e -> Fail e
let create v = Success v
let retnFrom v = v
type ResultBuilder () =
member __.Bind (m , f) = bind f m
member __.Return (v) = create v
member __.ReturnFrom (v) = retnFrom v
member __.Delay (f) = f
member __.Run (f) = f()
member __.TryWith (body, handler) =
try __.Run body
with e -> handler e
[<AutoOpen>]
module ResultBuilder =
let result = Result.ResultBuilder()
And now let's use it:
module Extern =
let calc x y = x / y
module TestRes =
let testme() =
result {
let (x, y) = 10, 0
try
return Extern.calc x y
with e ->
return! Fail e
}
|> function
| Success v -> v
| Fail ex -> raise ex // want to preserve original exn's stacktrace here
The problem is that the stacktrace will not include the source of the exception (here namely the calc function). If I run the code as written, it will throw as follows, which gives no information to the origin of the error:
System.DivideByZeroException : Attempted to divide by zero.
at Microsoft.FSharp.Core.Operators.Raise[T](Exception exn)
at PlayFul.TestRes.testme() in D:\Experiments\Play.fs:line 197
at PlayFul.Tests.TryItOut() in D:\Experiments\Play.fs:line 203
Using reraise() won't work, it wants a catch-context. Obviously, the following kind-a works, but makes debugging harder because of the nested exceptions and could get pretty ugly if this wrap-reraise-wrap-reraise pattern gets called multiple times in a deep stack.
System.Exception("Oops", ex)
|> raise
Update: TeaDrivenDev suggested in the comments to use ExceptionDispatchInfo.Capture(ex).Throw(), which works, but requires to wrap the exception in something else, complicating the model. However, it does preserve the stacktrace and it can be made into a fairly workable solution.