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.
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))
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
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]
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With