Skip to content

Instantly share code, notes, and snippets.

@1eyewonder
Created September 2, 2025 01:38
Show Gist options
  • Save 1eyewonder/1c7a7c745c02295498c7a1d1ec9e3bf9 to your computer and use it in GitHub Desktop.
Save 1eyewonder/1c7a7c745c02295498c7a1d1ec9e3bf9 to your computer and use it in GitHub Desktop.
playground for "stack traces" in CEs
#r "nuget: FsToolkit.ErrorHandling"
open FsToolkit.ErrorHandling
open System.Runtime.CompilerServices
open System
open System.IO
#nowarn 3535
[<RequireQualifiedAccess>]
module Seq =
let inline cons x xs = seq {
yield x
yield! xs
}
type Yarn = {
CallerMemberName: string
ExecutingMemberName: string
CallerFilePath: string
CallerLineNumber: int
} with
static member Create(caller: _ option, executing: _ option, path: _ option, line: _ option) = {
CallerMemberName = defaultArg caller ""
ExecutingMemberName = defaultArg executing ""
CallerFilePath = defaultArg path ""
CallerLineNumber = defaultArg line 0
}
let inline formatCallerMember (t: Yarn) =
$"Executing %s{t.ExecutingMemberName} from %s{t.CallerMemberName} in %s{t.CallerFilePath}:line %i{t.CallerLineNumber}"
type YarnNode = {
Yarn: Yarn
Children: YarnNode list
Depth: int
}
module YarnTree =
let buildTree (yarns: Yarn seq) =
let yarnList = yarns |> Seq.toList
let rec buildNodeTree (remaining: Yarn list) (currentDepth: int) (acc: YarnNode list) =
match remaining with
| [] -> List.rev acc
| yarn :: rest ->
let isNewBranch =
acc
|> List.tryHead
|> Option.map (fun prev -> prev.Yarn.CallerMemberName <> yarn.CallerMemberName)
|> Option.defaultValue true
let depth = if isNewBranch then currentDepth else currentDepth + 1
let node = { Yarn = yarn; Children = []; Depth = depth }
buildNodeTree rest depth (node :: acc)
buildNodeTree yarnList 0 []
let pruneTree (yarnFilters: seq<Yarn -> Yarn option>) (nodes: YarnNode list) =
let rec pruneNode (node: YarnNode) =
let prunedChildren =
node.Children
|> List.choose pruneNode
let filteredYarn =
yarnFilters
|> Seq.fold (fun yarnOpt filter ->
match yarnOpt with
| Some y -> filter y
| None -> None
) (Some node.Yarn)
match filteredYarn with
| Some yarn -> Some { node with Yarn = yarn; Children = prunedChildren }
| None -> None
nodes |> List.choose pruneNode
let rec formatTree (nodes: YarnNode list) =
nodes
|> List.map (fun node ->
let indent = String.replicate (node.Depth * 2) " "
let formatted = formatCallerMember node.Yarn
$"%s{indent}├─ %s{formatted}")
|> String.concat Environment.NewLine
module Yarn =
module FilterOut =
let callerMember (value: string) (t: Yarn) =
if t.CallerMemberName.Equals(value, StringComparison.OrdinalIgnoreCase) then
None
else
Some t
let executingMember (value: string) (t: Yarn) =
if String.Equals(t.ExecutingMemberName, value, StringComparison.OrdinalIgnoreCase) then
None
else
Some t
let callerFilePath (value: string) (t: Yarn) =
if t.CallerFilePath.Equals(value, StringComparison.OrdinalIgnoreCase) then
None
else
Some t
let callerLineNumber value (t: Yarn) =
if t.CallerLineNumber = value then None else Some t
module WriteOutput =
let simple (ball: Yarn seq) =
ball |> Seq.map formatCallerMember |> String.concat Environment.NewLine
let debuggable blacklist (ball: Yarn seq) =
YarnTree.buildTree ball
|> YarnTree.pruneTree blacklist
|> YarnTree.formatTree
type IUnravel<'e> =
static abstract Unravel: Yarn * 'e -> 'e
// ========================
// TO SAVE YOURSELF FROM SCROLLING YOU LIKELY WANT
// TO FOLD THIS UNLESS YOU ARE INSPECTING THE CE CODE
// =======================
[<AutoOpen>]
module ComputationExpressions =
type ResultTraceBuilder() =
member inline _.Return<'ok, 'error when IUnravel<'error>>(value: 'ok) : Result<'ok, 'error> = Ok value
member inline this.ReturnFrom<'ok, 'error when IUnravel<'error>>
(
result: Result<'ok, 'error>,
[<CallerMemberName>] ?memberName: string,
[<CallerFilePath>] ?sourceFilePath: string,
[<CallerLineNumber>] ?sourceLineNumber: int
) : Result<'ok, 'error> =
match result with
| Ok v -> Ok v
| Error e ->
Error(
'error
.Unravel(
Yarn.Create(
memberName,
Some <| sprintf "%s.%s" (this.GetType().FullName) (nameof this.ReturnFrom),
sourceFilePath,
sourceLineNumber
),
e
)
)
member this.Zero<'ok, 'error when IUnravel<'error>>() : Result<unit, 'error> = this.Return()
member inline this.Bind<'okInput, 'okOutput, 'error when IUnravel<'error>>
(
input: Result<'okInput, 'error>,
[<InlineIfLambda>] binder: 'okInput -> Result<'okOutput, 'error>,
[<CallerMemberName>] ?memberName: string,
[<CallerFilePath>] ?sourceFilePath: string,
[<CallerLineNumber>] ?sourceLineNumber: int
) : Result<'okOutput, 'error> =
match input with
| Ok v ->
match binder v with
| Ok o -> Ok o
| Error e ->
Error(
'error
.Unravel(
Yarn.Create(
memberName,
Some <| sprintf "%s.%s" (this.GetType().FullName) (nameof this.Bind),
sourceFilePath,
sourceLineNumber
),
e
)
)
| Error e ->
Error(
'error
.Unravel(
Yarn.Create(
memberName,
Some <| sprintf "%s.%s" (this.GetType().FullName) (nameof this.Bind),
sourceFilePath,
sourceLineNumber
),
e
)
)
member inline this.Delay<'ok, 'error when IUnravel<'error>>
(
[<InlineIfLambda>] generator: unit -> Result<'ok, 'error>,
[<CallerMemberName>] ?memberName: string,
[<CallerFilePath>] ?sourceFilePath: string,
[<CallerLineNumber>] ?sourceLineNumber: int
) : unit -> Result<'ok, 'error> =
fun () ->
match generator () with
| Ok v -> Ok v
| Error e ->
Error(
'error
.Unravel(
Yarn.Create(
memberName,
Some <| sprintf "%s.%s" (this.GetType().FullName) (nameof this.Delay),
sourceFilePath,
sourceLineNumber
),
e
)
)
member inline this.Run<'ok, 'error when IUnravel<'error>>
(
[<InlineIfLambda>] generator: unit -> Result<'ok, 'error>,
[<CallerMemberName>] ?memberName: string,
[<CallerFilePath>] ?sourceFilePath: string,
[<CallerLineNumber>] ?sourceLineNumber: int
) : Result<'ok, 'error> =
match generator () with
| Ok v -> Ok v
| Error e ->
Error(
'error
.Unravel(
Yarn.Create(
memberName,
Some <| sprintf "%s.%s" (this.GetType().FullName) (nameof this.Run),
sourceFilePath,
sourceLineNumber
),
e
)
)
member inline this.Combine<'ok, 'error when IUnravel<'error>>
(result: Result<unit, 'error>, [<InlineIfLambda>] binder: unit -> Result<'ok, 'error>)
: Result<'ok, 'error> =
this.Bind(result, binder)
member inline this.TryWith<'T, 'TError when IUnravel<'TError>>
(
[<InlineIfLambda>] generator: unit -> Result<'T, 'TError>,
[<InlineIfLambda>] handler: exn -> Result<'T, 'TError>,
[<CallerMemberName>] ?memberName: string,
[<CallerFilePath>] ?sourceFilePath: string,
[<CallerLineNumber>] ?sourceLineNumber: int
) : Result<'T, 'TError> =
try
this.Run generator
with e ->
match handler e with
| Ok v -> Ok v
| Error err ->
Error(
'TError
.Unravel(
Yarn.Create(
memberName,
Some <| sprintf "%s.%s" (this.GetType().FullName) (nameof this.TryWith),
sourceFilePath,
sourceLineNumber
),
err
)
)
member inline this.TryFinally<'ok, 'error when IUnravel<'error>>
(
[<InlineIfLambda>] generator: unit -> Result<'ok, 'error>,
[<InlineIfLambda>] compensation: unit -> unit,
[<CallerMemberName>] ?memberName: string,
[<CallerFilePath>] ?sourceFilePath: string,
[<CallerLineNumber>] ?sourceLineNumber: int
) : Result<'ok, 'error> =
try
this.Run generator
finally
compensation ()
member inline this.Using<'error when IUnravel<'error>>
(
resource: 'disposable :> IDisposableNull,
binder: 'disposable -> Result<'ok, 'error>,
[<CallerMemberName>] ?memberName: string,
[<CallerFilePath>] ?sourceFilePath: string,
[<CallerLineNumber>] ?sourceLineNumber: int
) : Result<'ok, 'error> =
this.TryFinally(
(fun () ->
match binder resource with
| Ok v -> Ok v
| Error e ->
Error(
'error
.Unravel(
Yarn.Create(
memberName,
Some <| sprintf "%s.%s" (this.GetType().FullName) (nameof this.Using),
sourceFilePath,
sourceLineNumber
),
e
)
)),
(fun () ->
if not (obj.ReferenceEquals(resource, null)) then
resource.Dispose())
)
member inline this.While<'ok, 'error when IUnravel<'error>>
(
[<InlineIfLambda>] guard: unit -> bool,
[<InlineIfLambda>] generator: unit -> Result<unit, 'error>,
[<CallerMemberName>] ?memberName: string,
[<CallerFilePath>] ?sourceFilePath: string,
[<CallerLineNumber>] ?sourceLineNumber: int
) : Result<unit, 'error> =
let mutable doContinue = true
let mutable result = Ok()
while doContinue && guard () do
match generator () with
| Ok() -> ()
| Error e ->
doContinue <- false
result <-
Error(
'error
.Unravel(
Yarn.Create(
memberName,
Some <| sprintf "%s.%s" (this.GetType().FullName) (nameof this.While),
sourceFilePath,
sourceLineNumber
),
e
)
)
result
member inline this.For<'TError when IUnravel<'TError>>
(
sequence: #seq<'T>,
[<InlineIfLambda>] binder: 'T -> Result<unit, 'TError>,
[<CallerMemberName>] ?memberName: string,
[<CallerFilePath>] ?sourceFilePath: string,
[<CallerLineNumber>] ?sourceLineNumber: int
) : Result<unit, 'TError> =
this.Using(
sequence.GetEnumerator(),
fun enum ->
this.While(
(fun () -> enum.MoveNext()),
this.Delay(fun () ->
match binder enum.Current with
| Ok() -> Ok()
| Error e ->
Error(
'TError
.Unravel(
Yarn.Create(
memberName,
Some <| sprintf "%s.%s" (this.GetType().FullName) (nameof this.For),
sourceFilePath,
sourceLineNumber
),
e
)
))
)
)
member inline this.BindReturn<'okInput, 'okOutput, 'error when IUnravel<'error>>
(
x: Result<'okInput, 'error>,
[<InlineIfLambda>] f: 'okInput -> 'okOutput,
[<CallerMemberName>] ?memberName: string,
[<CallerFilePath>] ?sourceFilePath: string,
[<CallerLineNumber>] ?sourceLineNumber: int
) : Result<'okOutput, 'error> =
match x with
| Ok v -> Ok(f v)
| Error e ->
Error(
'error
.Unravel(
Yarn.Create(
memberName,
Some <| sprintf "%s.%s" (this.GetType().FullName) (nameof this.BindReturn),
sourceFilePath,
sourceLineNumber
),
e
)
)
/// <summary>
/// Method lets us transform data types into our internal representation. This is the identity method to recognize the self type.
///
/// See https://stackoverflow.com/questions/35286541/why-would-you-use-builder-source-in-a-custom-computation-expression-builder
/// </summary>
/// <param name="result"></param>
/// <returns></returns>
member inline this.Source<'ok, 'error when IUnravel<'error>>
(
result: Result<'ok, 'error>,
[<CallerMemberName>] ?memberName: string,
[<CallerFilePath>] ?sourceFilePath: string,
[<CallerLineNumber>] ?sourceLineNumber: int
) : Result<'ok, 'error> =
match result with
| Ok v -> Ok v
| Error e ->
Error(
'error
.Unravel(
Yarn.Create(
memberName,
Some <| sprintf "%s.%s" (this.GetType().FullName) (nameof this.Source),
sourceFilePath,
sourceLineNumber
),
e
)
)
/// <summary>
/// The <c>Result</c> computation expression.
/// </summary>
let resultTrace = ResultTraceBuilder()
type ValidationTraceBuilder() =
member inline _.Return(value: 'ok) : Validation<'ok, 'error> = Validation.ok value
member inline this.ReturnFrom<'ok, 'error when IUnravel<'error>>
(
result: Validation<'ok, 'error>,
[<CallerMemberName>] ?memberName: string,
[<CallerFilePath>] ?sourceFilePath: string,
[<CallerLineNumber>] ?sourceLineNumber: int
) : Validation<'ok, 'error> =
match result with
| Ok v -> Ok v
| Error errors ->
errors
|> List.map (fun e ->
'error
.Unravel(
Yarn.Create(
memberName,
Some <| sprintf "%s.%s" (this.GetType().FullName) (nameof this.ReturnFrom),
sourceFilePath,
sourceLineNumber
),
e
))
|> Error
member inline this.Bind<'okInput, 'okOutput, 'error when IUnravel<'error>>
(
result: Validation<'okInput, 'error>,
[<InlineIfLambda>] binder: 'okInput -> Validation<'okOutput, 'error>,
[<CallerMemberName>] ?memberName: string,
[<CallerFilePath>] ?sourceFilePath: string,
[<CallerLineNumber>] ?sourceLineNumber: int
) : Validation<'okOutput, 'error> =
match result with
| Ok v ->
match binder v with
| Ok o -> Ok o
| Error errors ->
errors
|> List.map (fun e ->
'error
.Unravel(
Yarn.Create(
memberName,
Some <| sprintf "%s.%s" (this.GetType().FullName) (nameof this.Bind),
sourceFilePath,
sourceLineNumber
),
e
))
|> Error
| Error errors ->
errors
|> List.map (fun e ->
'error
.Unravel(
Yarn.Create(
memberName,
Some <| sprintf "%s.%s" (this.GetType().FullName) (nameof this.Bind),
sourceFilePath,
sourceLineNumber
),
e
))
|> Error
member inline this.Zero() : Validation<unit, 'error> = this.Return()
member inline this.Delay<'ok, 'error when IUnravel<'error>>
(
[<InlineIfLambda>] generator: unit -> Validation<'ok, 'error>,
[<CallerMemberName>] ?memberName: string,
[<CallerFilePath>] ?sourceFilePath: string,
[<CallerLineNumber>] ?sourceLineNumber: int
) : unit -> Validation<'ok, 'error> =
fun () ->
match generator () with
| Ok v -> Ok v
| Error errors ->
errors
|> List.map (fun e ->
'error
.Unravel(
Yarn.Create(
memberName,
Some <| sprintf "%s.%s" (this.GetType().FullName) (nameof this.Delay),
sourceFilePath,
sourceLineNumber
),
e
))
|> Error
member inline this.Run<'ok, 'error when IUnravel<'error>>
(
[<InlineIfLambda>] generator: unit -> Validation<'ok, 'error>,
[<CallerMemberName>] ?memberName: string,
[<CallerFilePath>] ?sourceFilePath: string,
[<CallerLineNumber>] ?sourceLineNumber: int
) : Validation<'ok, 'error> =
match generator () with
| Ok v -> Ok v
| Error errors ->
errors
|> List.map (fun e ->
'error
.Unravel(
Yarn.Create(
memberName,
Some <| sprintf "%s.%s" (this.GetType().FullName) (nameof this.Run),
sourceFilePath,
sourceLineNumber
),
e
))
|> Error
member inline this.Combine<'ok, 'error when IUnravel<'error>>
(result: Validation<unit, 'error>, [<InlineIfLambda>] binder: unit -> Validation<'ok, 'error>)
: Validation<'ok, 'error> =
this.Bind(result, binder)
member inline this.TryWith
(
[<InlineIfLambda>] generator: unit -> Validation<'ok, 'error>,
[<InlineIfLambda>] handler: exn -> Validation<'ok, 'error>,
[<CallerMemberName>] ?memberName: string,
[<CallerFilePath>] ?sourceFilePath: string,
[<CallerLineNumber>] ?sourceLineNumber: int
) : Validation<'ok, 'error> =
try
this.Run generator
with e ->
match handler e with
| Ok v -> Ok v
| Error errors ->
errors
|> List.map (fun err ->
'error
.Unravel(
Yarn.Create(
memberName,
Some <| sprintf "%s.%s" (this.GetType().FullName) (nameof this.TryWith),
sourceFilePath,
sourceLineNumber
),
err
))
|> Error
member inline this.TryFinally<'ok, 'error when IUnravel<'error>>
([<InlineIfLambda>] generator: unit -> Validation<'ok, 'error>, [<InlineIfLambda>] compensation: unit -> unit)
: Validation<'ok, 'error> =
try
this.Run generator
finally
compensation ()
member inline this.Using
(
resource: 'disposable :> IDisposableNull,
[<InlineIfLambda>] binder: 'disposable -> Validation<'okOutput, 'error>,
[<CallerMemberName>] ?memberName: string,
[<CallerFilePath>] ?sourceFilePath: string,
[<CallerLineNumber>] ?sourceLineNumber: int
) : Validation<'okOutput, 'error> =
this.TryFinally(
(fun () ->
match binder resource with
| Ok v -> Ok v
| Error errors ->
errors
|> List.map (fun e ->
'error
.Unravel(
Yarn.Create(
memberName,
Some <| sprintf "%s.%s" (this.GetType().FullName) (nameof this.Using),
sourceFilePath,
sourceLineNumber
),
e
))
|> Error),
(fun () ->
if not (obj.ReferenceEquals(resource, null)) then
resource.Dispose())
)
member inline this.While<'ok, 'error when IUnravel<'error>>
(
[<InlineIfLambda>] guard: unit -> bool,
[<InlineIfLambda>] generator: unit -> Validation<unit, 'error>,
[<CallerMemberName>] ?memberName: string,
[<CallerFilePath>] ?sourceFilePath: string,
[<CallerLineNumber>] ?sourceLineNumber: int
) : Validation<unit, 'error> =
let mutable doContinue = true
let mutable result = Ok()
while doContinue && guard () do
match generator () with
| Ok() -> ()
| Error errors ->
doContinue <- false
result <-
errors
|> List.map (fun e ->
'error
.Unravel(
Yarn.Create(
memberName,
Some <| sprintf "%s.%s" (this.GetType().FullName) (nameof this.While),
sourceFilePath,
sourceLineNumber
),
e
))
|> Error
result
member inline this.For<'ok, 'error when IUnravel<'error>>
(
sequence: #seq<'ok>,
[<InlineIfLambda>] binder: 'ok -> Validation<unit, 'error>,
[<CallerMemberName>] ?memberName: string,
[<CallerFilePath>] ?sourceFilePath: string,
[<CallerLineNumber>] ?sourceLineNumber: int
) : Validation<unit, 'error> =
this.Using(
sequence.GetEnumerator(),
fun enum ->
this.While(
enum.MoveNext,
this.Delay(fun () ->
match binder enum.Current with
| Ok() -> Ok()
| Error errors ->
errors
|> List.map (fun e ->
'error
.Unravel(
Yarn.Create(
memberName,
Some <| sprintf "%s.%s" (this.GetType().FullName) (nameof this.For),
sourceFilePath,
sourceLineNumber
),
e
))
|> Error)
)
)
member inline this.BindReturn<'okInput, 'okOutput, 'error when IUnravel<'error>>
(
input: Validation<'okInput, 'error>,
[<InlineIfLambda>] mapper: 'okInput -> 'okOutput,
[<CallerMemberName>] ?memberName: string,
[<CallerFilePath>] ?sourceFilePath: string,
[<CallerLineNumber>] ?sourceLineNumber: int
) : Validation<'okOutput, 'error> =
match input with
| Ok v -> Ok(mapper v)
| Error errors ->
errors
|> List.map (fun e ->
'error
.Unravel(
Yarn.Create(
memberName,
Some <| sprintf "%s.%s" (this.GetType().FullName) (nameof this.BindReturn),
sourceFilePath,
sourceLineNumber
),
e
))
|> Error
member inline this.MergeSources<'left, 'right, 'error when IUnravel<'error>>
(
left: Validation<'left, 'error>,
right: Validation<'right, 'error>,
[<CallerMemberName>] ?memberName: string,
[<CallerFilePath>] ?sourceFilePath: string,
[<CallerLineNumber>] ?sourceLineNumber: int
) : Validation<'left * 'right, 'error> =
match Validation.zip left right with
| Ok(l, r) -> Ok(l, r)
| Error errors ->
errors
|> List.map (fun e ->
'error
.Unravel(
Yarn.Create(
memberName,
Some <| sprintf "%s.%s" (this.GetType().FullName) (nameof this.MergeSources),
sourceFilePath,
sourceLineNumber
),
e
))
|> Error
/// <summary>
/// Method lets us transform data types into our internal representation. This is the identity method to recognize the self type.
///
/// See https://stackoverflow.com/questions/35286541/why-would-you-use-builder-source-in-a-custom-computation-expression-builder
/// </summary>
/// <param name="result"></param>
/// <returns></returns>
member inline this.Source<'ok, 'error when IUnravel<'error>>
(
result: Validation<'ok, 'error>,
[<CallerMemberName>] ?memberName: string,
[<CallerFilePath>] ?sourceFilePath: string,
[<CallerLineNumber>] ?sourceLineNumber: int
) : Validation<'ok, 'error> =
match result with
| Ok v -> Ok v
| Error errors ->
errors
|> List.map (fun e ->
'error
.Unravel(
Yarn.Create(
memberName,
Some <| sprintf "%s.%s" (nameof ValidationTraceBuilder) (nameof this.Source),
sourceFilePath,
sourceLineNumber
),
e
))
|> Error
let validationTrace = ValidationTraceBuilder()
[<AutoOpen>]
module ValidationTraceCEExtensions =
// Having members as extensions gives them lower priority in
// overload resolution and allows skipping more type annotations.
type ValidationTraceBuilder with
/// <summary>
/// Needed to allow `for..in` and `for..do` functionality
/// </summary>
member inline _.Source(s: #seq<_>) : #seq<_> = s
/// <summary>
/// Method lets us transform data types into our internal representation.
/// </summary>
member inline this.Source<'ok, 'error when IUnravel<'error>>
(
s: Result<'ok, 'error>,
[<CallerMemberName>] ?memberName: string,
[<CallerFilePath>] ?sourceFilePath: string,
[<CallerLineNumber>] ?sourceLineNumber: int
) : Validation<'ok, 'error> =
match Validation.ofResult s with
| Ok v -> Ok v
| Error errors ->
errors
|> List.map (fun e ->
'error
.Unravel(
Yarn.Create(
memberName,
Some <| sprintf "%s.%s" (this.GetType().FullName) (nameof this.Source),
sourceFilePath,
sourceLineNumber
),
e
))
|> Error
/// <summary>
/// Method lets us transform data types into our internal representation.
/// </summary>
/// <returns></returns>
member inline this.Source<'ok, 'error when IUnravel<'error>>
(
choice: Choice<'ok, 'error>,
[<CallerMemberName>] ?memberName: string,
[<CallerFilePath>] ?sourceFilePath: string,
[<CallerLineNumber>] ?sourceLineNumber: int
) : Validation<'ok, 'error> =
match Validation.ofChoice choice with
| Ok v -> Ok v
| Error errors ->
errors
|> List.map (fun e ->
'error
.Unravel(
Yarn.Create(
memberName,
Some <| sprintf "%s.%s" (this.GetType().FullName) (nameof this.Source),
sourceFilePath,
sourceLineNumber
),
e
))
|> Error
// ========================
// Example stuff starts here
// ========================
type IBedError<'e> =
static abstract iTooCold: int -> 'e
static abstract iAlarmFailed: 'e
let getOutOfBed<'e when IBedError<'e>> (d: DateTime) =
if d.Hour < 6 then Error('e.iTooCold 5)
elif d.Hour > 9 then Error 'e.iAlarmFailed
else Ok()
let doFoo () = resultTrace {
let! x = getOutOfBed <| DateTime.Parse("2021-01-01 10:00:00")
return ()
}
let doFoo2 () = resultTrace {
let! x = getOutOfBed <| DateTime.Parse("2021-01-01 5:00:00")
return ()
}
let doBar () = resultTrace {
let! x = doFoo ()
return ()
}
let doBaz () = resultTrace {
let! x = doBar ()
return ()
}
let doQux () = resultTrace {
let! x = doBaz ()
return ()
}
let doQux2 () = resultTrace { return! doFoo2 () }
let ultraQux () = validationTrace {
let! x = doQux ()
and! y = doQux2 ()
return 2
}
type FooErrors =
| TooCold of int * Yarn seq
| AlarmFailed of Yarn seq
interface IBedError<FooErrors> with
static member iTooCold e = TooCold(e, Seq.empty)
static member iAlarmFailed = AlarmFailed Seq.empty
interface IUnravel<FooErrors> with
static member Unravel(msg, e) =
match e with
| TooCold(e, ball) -> TooCold(e, Seq.cons msg ball)
| AlarmFailed ball -> Seq.cons msg ball |> AlarmFailed
// simple output of the stack trace
member x.FormattedTrace blacklist =
match x with
| TooCold(_, ball)
| AlarmFailed ball ->
ball
|> Seq.filter (fun yarn ->
blacklist
|> Seq.forall (fun f -> f yarn |> Option.isSome)
)
|> Yarn.WriteOutput.simple
// nicely formatted tree output of the stack trace
member x.DebuggingTrace blacklist =
match x with
| TooCold(_, ball)
| AlarmFailed ball ->
YarnTree.buildTree ball
|> YarnTree.pruneTree blacklist
|> YarnTree.formatTree
// can configure how detailed the stacks traces are
let yarnIgnores =
seq {
// ignore these CE member calls. They are likely overkill and not needed,
// but it is nice to see the inner workings of the CE for example purposes
Yarn.FilterOut.executingMember "FSI_0004+ComputationExpressions+ResultTraceBuilder.Run"
Yarn.FilterOut.executingMember "FSI_0004+ComputationExpressions+ResultTraceBuilder.Delay"
Yarn.FilterOut.executingMember "FSI_0004+ComputationExpressions+ResultTraceBuilder.Source"
Yarn.FilterOut.executingMember "FSI_0004+ComputationExpressions+ValidationTraceBuilder.Run"
Yarn.FilterOut.executingMember "FSI_0004+ComputationExpressions+ValidationTraceBuilder.Delay"
Yarn.FilterOut.executingMember "FSI_0004+ComputationExpressions+ValidationTraceBuilder.Source"
Yarn.FilterOut.executingMember "FSI_0004+ComputationExpressions+ValidationTraceBuilder.MergeSources"
// Yarn.FilterOut.callerMember "doBaz"
// ignore specific lines in specific files
// yield!
// seq {
// // @"c/Users/brons/source/repos/Unravel/test.fsx", 821
// }
// |> Seq.map (fun (path, line) ->
// Yarn.FilterOut.callerFilePath path
// >> Option.bind (Yarn.FilterOut.callerLineNumber line)
// )
}
|> Seq.cache
match ultraQux () with
| Ok _ -> printfn "Success"
| Error(errors: FooErrors list) ->
errors
|> Seq.iter (fun e -> printfn $"%s{e.FormattedTrace yarnIgnores}")
printfn "-----"
errors
|> Seq.iter (fun e -> printfn $"%s{e.DebuggingTrace yarnIgnores}")
printfn "-----"
printfn "%A" errors
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment