F#: Forward Operator
Continuing on my F# journey I came across a post by Ben Hall describing the approach he takes when learning a new programming language.
One of the approaches he describes is that of writing unit tests to help keep your learning on track. I’ve only been using the F# interactive console so far so I thought I’d give it a try.
After reading about the somewhat convoluted approach required to use NUnit or MBUnit to write F# unit tests I came across XUnit.NET via Matthew Podwysocki’s blog post about FsTest - a testing DSL he’s written to use on top of XUnit.NET.
One of the cool F# features I came across while reading Matthew’s post is the Forward Operator ( |
>). |
This is particularly useful for writing test assertions which read like English. For example:
[<Fact>] let list_should_contain_3() = [1..5] |> should contain 3
Typically we would have the function followed by the data we want to apply it against but using this operator allows us to do it the other way around.
From my understanding the forward operator (also known as the push operator) pushes the value from the left hand side of the function to the first parameter of the function.
To use a simpler example of adding 5 to a number.
Normally we would do this:
> (fun x -> x+5) 5;;
val it : int = 10
Using the forward operator we can do this instead:
> 5 |> (fun x -> x+5)
val it : int = 10
If we look at the definition of " |
>" this makes a bit more sense: |
> (|>);;
val it : ('a -> ('a -> 'b) -> 'b) = <fun:clo@84>
It takes in 2 arguments "'a" and "('a -> 'b)" and returns "'b".
The first argument in this case is the value '5' and the second is a function which takes in an "'a" and returns a "'b", in this case the (x -> x +5) function.
Armed with that knowledge the DSL example hopefully now makes more sense. To recall with the full code:
#light
open Xunit
let should f actual = f actual
let contain (expected: int) (actual: int list) = Assert.Contains(expected, actual)
[<Fact>] let list_should_contain_3() = [1..5] |> should contain 3
This is the same as writing the following:
[<Fact>] let list_should_contain_3() = should contain 3 [1..5]
Working from the 'should' outwards…
> should;;
val it : (('a -> 'b) -> 'a -> 'b) = <fun:clo@0_1>
It expects to take in a function ('a -> 'b), a value ('a) and will return a value ('b).
In this case that function is 'contain':
> contain;;
val it : (int -> int list -> unit) = <fun:clo@0_2>
It expects to take in two values (an int and a list of ints) and doesn’t return any value (unit is the equivalent of void in C#.)
Evaluating both together:
> should contain;;
val it : (int -> int list -> unit) = <fun:clo@88_1>
Here we have a partial application of the 'should' function i.e. we have only passed in one of the arguments (the 'contain' function). We have now created another function which requires an int and a list of ints and returns nothing.
If we now take the whole statement together:
[1..5] |> should contain 1;;
It seems like the [1..5] should be applied as the first argument to the 'contain' function but in actual fact the precedence rules dictate that the right hand side of the " |
>" gets evaluated first meaning that the 1 is passed as the first argument to 'contain'. |
The [1..5] is passed in as the second argument to the 'contain' function completing all the inputs needed by the expression and therefore executing the Assert.Contains(…) assertion.
Matthew Cochran has an article which helps explain the operator with some diagrams and Matthew Podwysocki talks about it more in his post about language oriented programming.
I’m new to F# so if I’ve got anything wrong about the way this works please let me know.
About the author
I'm currently working on short form content at ClickHouse. I publish short 5 minute videos showing how to solve data problems on YouTube @LearnDataWithMark. I previously worked on graph analytics at Neo4j, where I also co-authored the O'Reilly Graph Algorithms Book with Amy Hodler.