FSharp.Interop.Compose


Why Composable: From C# to F#

In C# we define static functions:

1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
9: 
public static int Add(int y, int x){
  return x + y;
}
public static int AddOne(int x){
  return Add(x, 1);
}
public static int AddTwo(int x){
  return Add(x, 2);
}

When combining these functions it looks like this:

1: 
AddTwo(AddOne(4)); // 7

Not very readable.

We cram functions into objects so that code reads from left to right. Method chaining in F# and Clojure accomplishes this more flexibly. –Jessica Kerr (@jessitron) March 3, 2014

So when we do this in F#

1: 
2: 
3: 
4: 
let add y x = x + y
let addOne = add 1
let addTwo = add 2
4 |> addOne |> addTwo

output:

1: 
val it : int = 7

it works very cleanly, because the functions are in curried form. Add doesn't take two arguments, it takes one argument and returns a function that takes takes 1 argument and returns a result.

You can see manually do curried form in C# but the final invocation is still clunky.

1: 
2: 
3: 
4: 
5: 
6: 
public static Func<int,int> Add(int y){
  return x => x + y;
}
var addOne = Add(1);
var addTwo = Add(2);
addTwo(addOne(4)); // 7

However there is a way to do left to right in C# without resorting to objects...Extension Methods:

1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
9: 
public static int Add(this int x, int y){
  return x + y;
}
public static int AddOne(this int x){
    return x.Add(1);
}
public static int AddTwo(this int x){
    return x.Add(2);
}

Giving us left to right

1: 
4.AddOne().AddTwo(); // 7

And thusly is the basis for the method chaining api of LINQ.

1: 
Enumerable.Range(1,5).Where(x => x < 3); // 1,2

In F# we already can do the equivalent

1: 
2: 
seq { 1..5 }
  |> Seq.filter (fun x -> x < 3)

output:

1: 
val it : mkSeq@543<int> = seq [1; 2]

However, if we add a little bit more complexity to the C# LINQ

1: 
2: 
3: 
4: 
5: 
6: 
new [] {
  new {FirstName = "Stella", LastName = "Gibson"},
  new {FirstName = "Paul", LastName = "Spector"},
  new {FirstName = "Danielle", LastName = "Ferrington"},
  new {FirstName = "Olivia", LastName = "Spector"},
}.OrderByDescending(x=>x.LastName).ThenBy(x=>x.FirstName);

The F# idomatic built-in list comprehension is not as robust

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
open System

type Person = { FirstName : string; LastName: string; }

let lastThenFirst (x:Person) (y:Person) =
  let cmp = -String.Compare(x.LastName, y.LastName)
  if cmp = 0 then
    String.Compare(x.FirstName, y.FirstName)
  else
    cmp

[
  {FirstName="Stella"; LastName="Gibson"}
  {FirstName = "Paul"; LastName = "Spector"}
  {FirstName = "Danielle"; LastName = "Ferrington"}
  {FirstName = "Olivia"; LastName = "Spector"}
]
  |> List.sortWith lastThenFirst

output:

1: 
2: 
3: 
4: 
5: 
val it : list<Person> =
     [{FirstName = "Olivia";LastName = "Spector";};
      {FirstName = "Paul"; LastName = "Spector";};
      {FirstName = "Stella";LastName = "Gibson";};
      {FirstName = "Danielle"; LastName = "Ferrington";}]

You can use LINQ directly, Extension Methods are supported in F# 3.0 along with Type Directed Conversions at Member Invocations.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
open System.Linq

type Person = { FirstName : string; LastName: string; }

[
  {FirstName="Stella"; LastName="Gibson"}
  {FirstName = "Paul"; LastName = "Spector"}
  {FirstName = "Danielle"; LastName = "Ferrington"}
  {FirstName = "Olivia"; LastName = "Spector"}
].OrderByDescending(fun x -> x.LastName).ThenBy(fun x -> x.FirstName)

output:

1: 
2: 
3: 
4: 
5: 
val it : OrderedEnumerable<Person, string> = seq
  [{FirstName = "Olivia"; LastName = "Spector";};
   {FirstName = "Paul"; LastName = "Spector";};
   {FirstName = "Stella"; LastName = "Gibson";};
   {FirstName = "Danielle"; LastName = "Ferrington";}]

It's not idiomatic F# syntax. It looks like C# (just with more "fun")

But it is static, immutable and lazy.

So we have a pattern with left to right with both C# extension methods and curried form F#.

So given an extension method (this, arg) -> value it could be reformed into arg -> this -> value and it would syntactically look like an F# API.

Examples

1: 
2: 
let inline orderByDescending (keySelector:'TSource->'TKey) source = System.Linq.Enumerable.OrderByDescending(source, System.Func<'TSource, 'TKey>(keySelector))
let inline thenBy (keySelector:'TSource->'TKey) source = System.Linq.Enumerable.ThenBy(source, System.Func<'TSource, 'TKey>(keySelector))

And this is the basis for FSharp.Interop.Compose we can make .NET BCL api's that have idiomatic F# behavior look, read, write like idiomatic F# api's.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
open Composable.Linq

type Person = { FirstName : string; LastName: string; }

[
  {FirstName="Stella"; LastName="Gibson"}
  {FirstName = "Paul"; LastName = "Spector"}
  {FirstName = "Danielle"; LastName = "Ferrington"}
  {FirstName = "Olivia"; LastName = "Spector"}
]
  |> Enumerable.orderByDescending (fun x -> x.LastName)
  |> Enumerable.thenBy (fun x -> x.FirstName)

output:

1: 
2: 
3: 
4: 
5: 
val it : OrderedEnumerable<Person, string> =
    seq [{FirstName = "Olivia"; LastName = "Spector";};
         {FirstName = "Paul"; LastName = "Spector";};
         {FirstName = "Stella"; LastName = "Gibson";};
         {FirstName = "Danielle"; LastName = "Ferrington";}]

See also these StackOverflow questions

Fork me on GitHub