Recently Don Syme made a post to the F# mailing list about some proposed changes to the F# libraries. In it talked about the virtues of using the new |> operator, sighting the following example as something it would be difficult without it:
let methods = System.AppDomain.CurrentDomain.GetAssemblies()
|> List.of_array |> List.map ( fun assm -> assm.GetTypes() ) |> Array.concat
|> List.of_array |> List.map ( fun t -> t.GetMethods() ) |> Array.concat
After squinting at it for a while I realised that I didn’t really understand what the sample was doing, let alone see the advantage of what the |> operator was doing. So I decided to try and rewrite it a number of different ways to see what was going on. First I tried the following:
let methods = Array.concat ( List.map ( fun (t : System.Type) -> t.GetMethods() ) (List.of_array (
Array.concat( List.map ( fun (assm : System.Reflection.Assembly) -> assm.GetTypes() )
This made what was going clearer to me, but it was more the process of rewriting it that cleared things up. I came to the conclusion that I naturally start reading F# code at the bottom right and work backwards and this is a habit I need to get out of when looking at the |> operator. The reworked function doesn’t look that great because the lambdas need type annotations because F#’s type inference works from left to right so the type of lambda can not be inferred as they need to be inferred from something that appears on the right. And, probably worse, the function needs lots and lots of parenthesis to sort out the precedence of all the different functions being applied.
I made another rewrite using lots of intermediates:
let methods = let assmsArray = System.AppDomain.CurrentDomain.GetAssemblies() in
let assemsList = List.of_array assmsArray in
let typesArrayList = List.map (fun (assm : System.Reflection.Assembly) -> assm.GetTypes() ) assemsList in
let typesArray = Array.concat ( typesArrayList ) in
let typesList = List.of_array( typesArray ) in
let methodsArrayList = List.map ( fun (t : System.Type) -> t.GetMethods() ) typesList in
This method is perhaps a little more clear but is almost twice as log as the original and still requires all the type annotations.
The |> operator also has implications for Linq. I made an earlier post complaining that when using Linq either type annotations are necessary on the lambda or the lambdas get moved away from the function they work with. The |> operator can be used to sort this out. I rewrote my sample from that post using the |> operator and it looks a lot better:
let namesByFunction = (type string).GetMethods()
|> where (fun m -> not m.IsStatic) |> groupBy (fun m -> m.Name )
|> select (fun m -> m.Key, count m.Group) |> orderBy (fun (_, m) -> m)
The |> operator offers some nice advantages including reducing the need for type annotations when working with .NET types and help sort out function precedence. Perhaps more importantly it means functions can be written in the order that things will happen with out having to use lots of intermediates to hold results. After starting off as a bit of a cynic about the |> this little exercise has convinced me of its usefulness and I shall certainly be looking for places that the |> operator will be useful in my future F# programming.