ComposableExtensions


Why Composable: From C# to F#

In C# we define static functions:

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:

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.

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:

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

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

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

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

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 ComposableExtensions 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

val add : y:int -> x:int -> int

Full name: why.add
val y : int
val x : int
val addOne : (int -> int)

Full name: why.addOne
val addTwo : (int -> int)

Full name: why.addTwo
Multiple items
val int : value:'T -> int (requires member op_Explicit)

Full name: Microsoft.FSharp.Core.Operators.int

--------------------
type int = int32

Full name: Microsoft.FSharp.Core.int

--------------------
type int<'Measure> = int

Full name: Microsoft.FSharp.Core.int<_>
Multiple items
val seq : sequence:seq<'T> -> seq<'T>

Full name: Microsoft.FSharp.Core.Operators.seq

--------------------
type seq<'T> = System.Collections.Generic.IEnumerable<'T>

Full name: Microsoft.FSharp.Collections.seq<_>
module Seq

from Microsoft.FSharp.Collections
val filter : predicate:('T -> bool) -> source:seq<'T> -> seq<'T>

Full name: Microsoft.FSharp.Collections.Seq.filter
namespace System
Multiple items
val string : value:'T -> string

Full name: Microsoft.FSharp.Core.Operators.string

--------------------
type string = System.String

Full name: Microsoft.FSharp.Core.string
module String

from Microsoft.FSharp.Core
Multiple items
module List

from Microsoft.FSharp.Collections

--------------------
type List<'T> =
  | ( [] )
  | ( :: ) of Head: 'T * Tail: 'T list
  interface IEnumerable
  interface IEnumerable<'T>
  member Head : 'T
  member IsEmpty : bool
  member Item : index:int -> 'T with get
  member Length : int
  member Tail : 'T list
  static member Cons : head:'T * tail:'T list -> 'T list
  static member Empty : 'T list

Full name: Microsoft.FSharp.Collections.List<_>
val sortWith : comparer:('T -> 'T -> int) -> list:'T list -> 'T list

Full name: Microsoft.FSharp.Collections.List.sortWith
type 'T list = List<'T>

Full name: Microsoft.FSharp.Collections.list<_>
namespace System.Linq
type Enumerable =
  static member Aggregate<'TSource> : source:IEnumerable<'TSource> * func:Func<'TSource, 'TSource, 'TSource> -> 'TSource + 2 overloads
  static member All<'TSource> : source:IEnumerable<'TSource> * predicate:Func<'TSource, bool> -> bool
  static member Any<'TSource> : source:IEnumerable<'TSource> -> bool + 1 overload
  static member AsEnumerable<'TSource> : source:IEnumerable<'TSource> -> IEnumerable<'TSource>
  static member Average : source:IEnumerable<int> -> float + 19 overloads
  static member Cast<'TResult> : source:IEnumerable -> IEnumerable<'TResult>
  static member Concat<'TSource> : first:IEnumerable<'TSource> * second:IEnumerable<'TSource> -> IEnumerable<'TSource>
  static member Contains<'TSource> : source:IEnumerable<'TSource> * value:'TSource -> bool + 1 overload
  static member Count<'TSource> : source:IEnumerable<'TSource> -> int + 1 overload
  static member DefaultIfEmpty<'TSource> : source:IEnumerable<'TSource> -> IEnumerable<'TSource> + 1 overload
  ...

Full name: System.Linq.Enumerable
System.Linq.Enumerable.OrderByDescending<'TSource,'TKey>(source: System.Collections.Generic.IEnumerable<'TSource>, keySelector: System.Func<'TSource,'TKey>) : System.Linq.IOrderedEnumerable<'TSource>
System.Linq.Enumerable.OrderByDescending<'TSource,'TKey>(source: System.Collections.Generic.IEnumerable<'TSource>, keySelector: System.Func<'TSource,'TKey>, comparer: System.Collections.Generic.IComparer<'TKey>) : System.Linq.IOrderedEnumerable<'TSource>
Multiple items
type Func<'T1,'T2,'T3,'T4,'T5,'T6,'T7,'T8,'T9,'T10,'T11,'T12,'T13,'T14,'T15,'T16,'TResult> =
  delegate of 'T1 * 'T2 * 'T3 * 'T4 * 'T5 * 'T6 * 'T7 * 'T8 * 'T9 * 'T10 * 'T11 * 'T12 * 'T13 * 'T14 * 'T15 * 'T16 -> 'TResult

Full name: System.Func<_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_>

--------------------
type Func<'T1,'T2,'T3,'T4,'T5,'T6,'T7,'T8,'T9,'T10,'T11,'T12,'T13,'T14,'T15,'TResult> =
  delegate of 'T1 * 'T2 * 'T3 * 'T4 * 'T5 * 'T6 * 'T7 * 'T8 * 'T9 * 'T10 * 'T11 * 'T12 * 'T13 * 'T14 * 'T15 -> 'TResult

Full name: System.Func<_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_>

--------------------
type Func<'T1,'T2,'T3,'T4,'T5,'T6,'T7,'T8,'T9,'T10,'T11,'T12,'T13,'T14,'TResult> =
  delegate of 'T1 * 'T2 * 'T3 * 'T4 * 'T5 * 'T6 * 'T7 * 'T8 * 'T9 * 'T10 * 'T11 * 'T12 * 'T13 * 'T14 -> 'TResult

Full name: System.Func<_,_,_,_,_,_,_,_,_,_,_,_,_,_,_>

--------------------
type Func<'T1,'T2,'T3,'T4,'T5,'T6,'T7,'T8,'T9,'T10,'T11,'T12,'T13,'TResult> =
  delegate of 'T1 * 'T2 * 'T3 * 'T4 * 'T5 * 'T6 * 'T7 * 'T8 * 'T9 * 'T10 * 'T11 * 'T12 * 'T13 -> 'TResult

Full name: System.Func<_,_,_,_,_,_,_,_,_,_,_,_,_,_>

--------------------
type Func<'T1,'T2,'T3,'T4,'T5,'T6,'T7,'T8,'T9,'T10,'T11,'T12,'TResult> =
  delegate of 'T1 * 'T2 * 'T3 * 'T4 * 'T5 * 'T6 * 'T7 * 'T8 * 'T9 * 'T10 * 'T11 * 'T12 -> 'TResult

Full name: System.Func<_,_,_,_,_,_,_,_,_,_,_,_,_>

--------------------
type Func<'T1,'T2,'T3,'T4,'T5,'T6,'T7,'T8,'T9,'T10,'T11,'TResult> =
  delegate of 'T1 * 'T2 * 'T3 * 'T4 * 'T5 * 'T6 * 'T7 * 'T8 * 'T9 * 'T10 * 'T11 -> 'TResult

Full name: System.Func<_,_,_,_,_,_,_,_,_,_,_,_>

--------------------
type Func<'T1,'T2,'T3,'T4,'T5,'T6,'T7,'T8,'T9,'T10,'TResult> =
  delegate of 'T1 * 'T2 * 'T3 * 'T4 * 'T5 * 'T6 * 'T7 * 'T8 * 'T9 * 'T10 -> 'TResult

Full name: System.Func<_,_,_,_,_,_,_,_,_,_,_>

--------------------
type Func<'T1,'T2,'T3,'T4,'T5,'T6,'T7,'T8,'T9,'TResult> =
  delegate of 'T1 * 'T2 * 'T3 * 'T4 * 'T5 * 'T6 * 'T7 * 'T8 * 'T9 -> 'TResult

Full name: System.Func<_,_,_,_,_,_,_,_,_,_>

--------------------
type Func<'T1,'T2,'T3,'T4,'T5,'T6,'T7,'T8,'TResult> =
  delegate of 'T1 * 'T2 * 'T3 * 'T4 * 'T5 * 'T6 * 'T7 * 'T8 -> 'TResult

Full name: System.Func<_,_,_,_,_,_,_,_,_>

--------------------
type Func<'T1,'T2,'T3,'T4,'T5,'T6,'T7,'TResult> =
  delegate of 'T1 * 'T2 * 'T3 * 'T4 * 'T5 * 'T6 * 'T7 -> 'TResult

Full name: System.Func<_,_,_,_,_,_,_,_>

--------------------
type Func<'T1,'T2,'T3,'T4,'T5,'T6,'TResult> =
  delegate of 'T1 * 'T2 * 'T3 * 'T4 * 'T5 * 'T6 -> 'TResult

Full name: System.Func<_,_,_,_,_,_,_>

--------------------
type Func<'T1,'T2,'T3,'T4,'T5,'TResult> =
  delegate of 'T1 * 'T2 * 'T3 * 'T4 * 'T5 -> 'TResult

Full name: System.Func<_,_,_,_,_,_>

--------------------
type Func<'T1,'T2,'T3,'T4,'TResult> =
  delegate of 'T1 * 'T2 * 'T3 * 'T4 -> 'TResult

Full name: System.Func<_,_,_,_,_>

--------------------
type Func<'T1,'T2,'T3,'TResult> =
  delegate of 'T1 * 'T2 * 'T3 -> 'TResult

Full name: System.Func<_,_,_,_>

--------------------
type Func<'T1,'T2,'TResult> =
  delegate of 'T1 * 'T2 -> 'TResult

Full name: System.Func<_,_,_>

--------------------
type Func<'T,'TResult> =
  delegate of 'T -> 'TResult

Full name: System.Func<_,_>

--------------------
type Func<'TResult> =
  delegate of unit -> 'TResult

Full name: System.Func<_>
System.Linq.Enumerable.ThenBy<'TSource,'TKey>(source: System.Linq.IOrderedEnumerable<'TSource>, keySelector: System.Func<'TSource,'TKey>) : System.Linq.IOrderedEnumerable<'TSource>
System.Linq.Enumerable.ThenBy<'TSource,'TKey>(source: System.Linq.IOrderedEnumerable<'TSource>, keySelector: System.Func<'TSource,'TKey>, comparer: System.Collections.Generic.IComparer<'TKey>) : System.Linq.IOrderedEnumerable<'TSource>
namespace Composable
namespace Composable.Linq
Fork me on GitHub