Debugging piped operations in F#

A little on the pipe operator

In F# you can create piped operations using the |> operator. This takes the output of the previous statement and funnels it as the input to the next statement. Using the pipe operator, a statement like this:

x |> f |> g |> h

Means having functions nested like this:

h(g(f(x))

So a piece of code like this:

let print item = Console.WriteLine(item.ToString)

let seqDebug =
        [0..1000]
                |> List.map (fun i -> i + 1)
                |> List.filter (fun i -> i < 5)
                |> List.head
                |> print

Decompiles into this (formatting added):

[DebuggerBrowsable(DebuggerBrowsableState.Never)]
internal static Unit seqDebug\u00407;

public static void main\u0040()
{
    Program.print(
        ListModule.Head(
            ListModule.Filter((FSharpFunc<int, bool>) new Program.seqDebug\u004010(),
                ListModule.Map<int, int>((FSharpFunc<int, int>) new Program.seqDebug\u00409\u002D1(),
                    SeqModule.ToList(Operators.CreateSequence(
                        Operators.OperatorIntrinsics.RangeInt32(0, 1, 1000)))))));

    \u0024Program.seqDebug\u00407 = (Unit) null;
}

Which really boils down to:

seqDebug = Print(Head(Filter(Map(sequence))))

The F# syntax is nice because it lets us write code from the outside in, instead of inside out.

Debugging it

Now that we know what F# is doing, lets say we want to debug the print statement. You can’t use your normal “Step Over” F10 key to go through your piped statement here because it compiles down to a one line group of nested functions. We could use the “Step Into” key (F11) to step into the entire sequence but then we have to execute the anonymous map lambda 1001 times just to get to the next statement. Then another 1001 for the filter. Then the head statement, and finally, our print. No thanks.

Thankfully, Visual Studio has thought of this and you can use the Step Into Specific functionality. This lets you see the list of nested functions at that line and you can jump into whatever you need to here. Step Into Specific isn’t an F# only feature, but I never realized it existed until I ran into this scenario.

Step into Specific

The example is a little trivial, since you would’ve just put a breakpoint in the print statement, right? But what if you are piping through F# operators like List.map and List.filter? In these cases it can be hard to know what is the direct input to these functions since the input argument is automatically applied. For these scenarios, a simple identity function can be really helpful:

let identity item = item

let seqDebug =
        [0..1000]
                |> List.map (fun i -> i + 1)
                |> identity
                |> List.filter (fun i -> i < 5)
                |> List.head

So you can sprinkle in your identity function and put breakpoints there. This way you can inject yourself into the middle of this sequence.

Piping with functions that return unit

Taking this one step further, sometimes I want to print out a value in the middle of the sequence, or call a function that has a return type of unit but continue piping. Because let’s be honest here, when all else fails nothing beats a well placed printf in your code. But, we’re left with a small dilemma: since pipes take the output of the last function and use it as the input to the next function we can’t really use print statements. Both printf and Console.WriteLine effectively return a void. Putting them in the middle of a chain won’t work since their output won’t map to the next functions input (unless that next function takes unit).

However, F# lets you define your own operators, so I created one that I like to call the “argument identity” that executes a function which returns void and then returns the original argument (acting as an argument identity function):

let (~~) (func:'a-> unit) (arg:'a) = (func arg) |> fun () -> arg

The ~~ symbol is a prefix operator that takes a function of one argument that returns unit, then closes the argument into a function with type unit -> 'a. Then I pipe the return value (unit) to the anonymous function (that takes unit) which will return the closed value of the original argument. Now I can do things like this:

let (~~) (func:'a-> unit) (arg:'a) = (func arg) |> fun () -> arg

let seqDebug =
        [0..1000]
                |> List.map (fun i -> i + 1)
                |> ~~ Console.WriteLine
                |> List.filter (fun i -> i < 3)
                |> ~~ Console.WriteLine
                |> List.head
                |> ~~ Console.WriteLine

Which prints out

[1; 2; 3; ... ]
[1; 2]
1

You obviously don’t need your own operator, you can make it a named helper function if you want. Either way, some sort of argument identity function is useful in these scenarios.

Disassemble the pipe

And of course, when all else fails, you can break up the sequence into a series of let statements to debug it the old fashioned way.

let seqDebugDecomposed =
        let source = [0..1000]
        let sourcePlusOne = List.map (fun i -> i + 1) source
        let filteredSource = List.filter (fun i -> i < 3) sourcePlusOne
        let listHead = List.head filteredSource
        print listHead

Recent Post by Anton Kropp

2 Pingbacks/Trackbacks