Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?

Assert On Steroids

I find it useful to have assert statements in my code. It has several benefits:

  • Self documents code expectations
  • Establishes boundary checks between component
  • Helps to pinpoint problems by failing fast, especially when you just started dealing with a new library
  • Here is a good summary on using assertions.

In a dream world all this can be addressed by sophisticated type system but we all know it’s not going to happen any time soon. F# has built-in assert, which mapped to Debug.Assert. Annoying thing is that it doesn’t display boolean expression that failed. Small test program

open System.Diagnostics

let x = "hello"
Debug.Assert(x.Length > 5)

Will fail depending on your configuration/framework with either meaningless message box


or exception logged to console


This is a sub-optimal experience usually improved by explicitly invoking Debug.Assert overload that receives a text message as a second argument

Debug.Assert(x.Length > 5, "x.Length > 5")

This is acceptable solution but at the cost of mechanical repetition. We could do better in F# !

module FSharp.Diagnostics 

open System.Diagnostics
open FSharp.Quotations
open System.Runtime.CompilerServices
open Patterns
open DerivedPatterns
open System
open FSharp.Linq.RuntimeHelpers

type Debug =

    static member Assert([<ReflectedDefinition>] condition: Expr<bool>, 
                            [<CallerFilePath>]?filePath: string, [<CallerLineNumber>]?line: int) : unit =        
        let rec prettyPrinty e = 
            match e with 
            | SpecificCall <@ (=) @> (None, _, [ lhs;  rhs ]) ->
                sprintf "%s = %s" (prettyPrinty lhs) (prettyPrinty rhs)
            | SpecificCall <@ (<) @> (None, _, [ lhs;  rhs ]) ->
                sprintf "%s < %s" (prettyPrinty lhs) (prettyPrinty rhs)
            | SpecificCall <@ (>) @> (None, _, [ lhs;  rhs ]) ->
                sprintf "%s > %s" (prettyPrinty lhs) (prettyPrinty rhs)
            | SpecificCall <@ (<>) @> (None, _, [ lhs;  rhs ]) ->
                sprintf "%s <> %s" (prettyPrinty lhs) (prettyPrinty rhs)
            | SpecificCall <@ (>=) @> (None, _, [ lhs;  rhs ]) ->
                sprintf "%s >= %s" (prettyPrinty lhs) (prettyPrinty rhs)
            | SpecificCall <@ (<=) @> (None, _, [ lhs;  rhs ]) ->
                sprintf "%s <= %s" (prettyPrinty lhs) (prettyPrinty rhs)
            | SpecificCall <@ not @> (None, _, [ x ]) ->
                sprintf "not %s" (prettyPrinty x) 
            | PropertyGet( Some( self), prop, args) -> 
                sprintf "%s.%s" (prettyPrinty self) prop.Name
            | Call(None, method, xs) ->
                let args = xs |> prettyPrinty |> String.concat","
                let invocation = 
                    if Char.IsLower(method.Name.[0]) && xs.Length = 1
                    then sprintf "%s %s" method.Name (prettyPrinty xs.Head)
                    else sprintf "%s(%s)" method.Name args
                sprintf "%s.%s" method.DeclaringType.Name invocation
            | OrElse (lhs, rhs) ->
                sprintf "%s || %s" (prettyPrinty lhs) (prettyPrinty rhs)
            | AndAlso (lhs, rhs) ->
                sprintf "%s && %s" (prettyPrinty lhs) (prettyPrinty rhs)
            | ValueWithName(_, _, name) -> 
            | Value(x, _) -> 
                sprintf "%A" x 
            | _ -> "" 

        let evalCondition = LeafExpressionConverter.EvaluateQuotation(condition) :?> _
        if not evalCondition
            let expr = prettyPrinty condition
            let location = sprintf "\nat %s:line %i" filePath.Value line.Value
            let message = sprintf "Assertion (%s) failed%s" expr location

Module FSharp.Diagnostics can be used as a “drop-in” to magically “lighten up” Debug.Assert. Once you include the module at the top of any file that uses partially qualified Debug.Assert you’ll see full boolean condition that failed:

module Program 

open System.Diagnostics
open FSharp.Diagnostics //shadow default Debug.Assert

let main _ =
    let x = "hello"
    Debug.Assert(x.Length > 5)


Maestro Don Syme would not recommend this “drop-in” approach. But what does he know, right? :)

You can add [<AutoOpen>] at the top of FSharp.Diagnostics module if you feel adventurous.

Even if you’re skeptical about usefulness of assertions in your code the module is a nice demo of F# language features:

Both prettyPrint and LeafExpressionConverter.EvaluateQuotation are not equipped to deal with arbitrarily complex F# code. This is good because if you have complex expression as boolean condition for assert there is something wrong with your code. It’s possible that I missed some cases for prettyPrint. Feel free to extend it.

One can go as far as using same code with Trace.Assert in production release build. Nothing wrong with that but make sure you understand how to configure Trace.Listeners to fit production environment expectations. Something like

        Trace.Listeners.Add {
            new DefaultTraceListener(AssertUiEnabled = false) with
                member __.Fail( message) = failwith message
        } |> ignore

might do the job.

Happy holidays F# community !

Nice !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment