Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can you chain F# functions inline properly for a set of operations performed on a list?

Tags:

.net

f#

So basically if I have a simple list in F#

let ls = [3; 9; 2; 15; 3]

And I want to sortDescending and then take 3. I can do this chaining:

let orderList ls = List.sortDescending ls
let take3 ls = List.take(3) ls
let main ls = (orderList >> take3) ls

And this works just fine and will work for what I want when using the F# Interactive debugger. But I am trying to do it inline in all one expression and not getting the syntax right. Is it possible and I am just not getting the syntax.

let main2 ls = List.sortDescending >> List.take(3) ls

I am moving from more of an object orient mindset where in C# I can do this:

var ls = new List<int>{ 3, 9, 2, 15, 3 };
var subset = ls.OrderByDescending(x => x).Take(3).ToList();

And I was just expecting you could chain continue like fluent syntax INLINE, but it appears I am missing syntax or not getting something simple. I have only been learning F# recently, but so far I dig it. I just am having a hard time getting my head around some of the way things are chained together.

like image 837
djangojazz Avatar asked Oct 13 '16 18:10

djangojazz


3 Answers

The >> operator takes two functions and produces a function that is the composition of those two functions.

The |> operator takes a value and a function and passes the value to the function, producing another value.

Suppose we have

let addone x = x + 1
let double x = x * 2

Then (addone >> double) 100 and 100 |> addone |> double are both 202. Do you see why? Make sure this is clear.

Think of them like this. f >> g is composition:

let compose f g =
  fun x -> g (f x)

So

(addone >> double) 100

is the same as

(fun x -> double ( addone x ) ) 100

Which is the same as

double (addone 100)

Whereas x |> f is pipe:

let pipe x f = f x

So

100 |> addone |> double

is the same as

(addone 100) |> double

is the same as

double (addone 100)

In C# the business of chaining together extension methods is basically the pipe operator:

customers.Take(100).ToList()

is the same as

Enumerable.ToList(Enumerable.Take(customers, 100))
like image 77
Eric Lippert Avatar answered Oct 09 '22 16:10

Eric Lippert


I think you get confused because you miss some details about parenthesis in F#. You have written this:

let main2 ls = List.sortDescending >> List.take(3) ls

I think you probably assumed that List.take is called with a single argument, but it isn't. Notice that in F# parenthesis only group things. They are not needed to call a function. In your case your parenthesis has literally no effect. Look at those two function calls. They have the same meaning.

List.take (3) (ls)
List.take 3 ls

If this confuses you, just think of simple math. Also here parenthesis just group stuff. That means, anything inside of parenthesis is computed first.

(3 * 6) + 10
3 * 6 + 10

Also in the above case you get the same result, with or without parenthesis. You only need parenthesis if you need another precedence. Like first adding 6 + 10 and then multiply the result by 3.

3 * (6 + 10)

In F# it is the. Whatever is inside of parenthesis is calculated first. In your above example you just put 3 in it. This has no meaning at all. It would be the same as if you write.

(3) * (6) + (10)

As another example look at:

sqrt(2.0) + 2.0
sqrt 2.0 + 2.0

This both example mean the same. And probably that is your confusion. But the right way with parenthesis how it is interpreted is written like that:

(sqrt 2.0) + 2.0

The first parenthesis around (2.0) has no meaning. But if you first want to add the numbers and you want to square-root 4.0 you need to write:

sqrt (2.0 + 2.0)

Back to your example. If we remove the parenthesis around 3 and adding the necessary parenthesis back to your code how the code is interpreted you actually have written something like this:

let main2 ls = List.sortDescending >> List.take(3) ls
let main2 ls = List.sortDescending >> (List.take 3 ls)

This means. Your code first executes List.take 3 ls. The result of that is a List. Then you try to do compose a function List.sortDescending with a List. This sure fails with a type-error. What you prabably wanted to do is just partial apply List.take with a single argument and compose the resulting function with List.sortDescending. In this case you need to write:

(List.take 3)

Or as a full example:

let main2 ls = (List.sortDescending >> (List.take 3)) ls
let main2 ls = (List.sortDescending >> List.take 3) ls

Both of the two lines above once again have the same meaning. What you do nowin your case is create a partial applied function List.take 3 and you compose it with List.sortDescending. This then returns a new function that you then pass ls as an argument. Notice that this code is a little bit verbose. You also "theoretically" could write it just as:

let main2 ls = (List.sortDescending >> List.take 3) ls
let main2 = List.sortDescending >> List.take 3

Notice that this potentially can create a "Value Restriction" error if you don't call the function with a concrete value. By the way there are still other ways how you can write your function. One way that also eliminates a value restriction error is:

let main ls = ls |> List.sortDescending |> List.take 3

This is the same as:

let main ls = List.take 3 (List.sortDescending ls)

Btw. as another notice not directly related to your problem. You also should look at List.truncate instead of List.take. List.take throws an error if it cannot return the exact amount of elements you specified. List.truncate never throws an error.

List.take     4 [1;2;3] // throws an exception
List.truncate 4 [1;2;3] // returns -> [1;2;3]

If you want further details about piping, nesting or composition. I have a blog article covering this details: http://sidburn.github.io/blog/2016/09/25/function-application-and-composition

like image 2
David Raab Avatar answered Oct 09 '22 16:10

David Raab


You can of course use the linq extension methods in F# as well.

open System
open System.Linq


let ls = [3; 9; 2; 15; 3]
ls.OrderByDescending(fun x -> x).Take(3).ToList()

Moreover, if you like this type of syntax, Fsharp.Core.Fluent can do it:

#r @"..\packages\FSharp.Core.Fluent-4.0.1.0.0.5\lib\portable-net45+netcore45+wpa81+wp8+MonoAndroid1+MonoTouch1\FSharp.Core.Fluent-4.0.dll"
#r "System.Runtime"
open FSharp.Core.Fluent
open System.Runtime

ls.sortDescending().Take(3).toList()

Please note the difference between the .ToList(), which is .NET's generic list: Collections.Generic.List<int> = seq [15; 9; 3] and .toList(), which is F#'s linked list: val it : int list = [15; 9; 3].

like image 1
s952163 Avatar answered Oct 09 '22 14:10

s952163