Concepts

Currying

Definition

Consists in transforming :

  • a function taking N parameters Func<T1, T2, ...Tn, TReturn> in C♯

  • into a chain of N functions taking 1 parameter Func<T1, Func<Tn, ...Func<Tn, TReturn>>

Partial application

Calling a function with fewer arguments than its number of parameters.

  • Possible thanks to currying

  • Returns a function taking the remaining number of arguments as parameters.

// Template function with 2 parameters
let insideTag (tagName: string) (content: string) =
    $"<{tagName}>{content}</{tagName}>"

// Helpers with a single parameter `content`
// `tagName` is fixed by partial application
let emphasize = insideTag "em"     // `tagName` fixed to "em"
let strong    = insideTag "strong" // `tagName` fixed to "strong"

// Equivalent less elegant but more explicit
let em content = insideTag "em" content

⚠️ Caution : partial application impacts the signature:

  1. Loss of parameter names (content: string becomes just string)

  2. Signature of a function value, hence the parentheses ((string -> string))

Syntax of F♯ functions

Parameters separated by spaces:

  • Indicates that functions are curried

  • Hence the -> in the signature between parameters

IntelliSense with Ionide

💡 In VsCode with Ionide, IntelliSense provides a more readable description of functions, putting each argument in a new line:

.NET compilation of a curried function

☝ A curried function is compiled differently depending on how it's called!

  • Basically, it is compiled as a method with tuple-parameters → Viewed as a regular method when consumed in C♯

Example: F♯ then C♯ equivalent, based on a simplified version from SharpLab:

  • When partially applied, the function is compiled as a pseudo Delegate class extending FSharpFunc<int, int> with an Invoke method that encapsulates the 1st supplied arguments.

Example: F♯ then C♯ equivalent, based on a simplified version from SharpLab:

Unified function design

unit type and currying make it possible to design functions simply as:

  • Takes a single parameter of any type

    • including unit for a “parameterless” function

    • including another (callback) function

  • Returns a single value of any type

    • including unit for a “return nothing” function

    • including another function

👉 Universal signature of a function in F♯: 'T -> 'U.

Parameter order

Between C♯ and F♯, the parameter concerning the main object (the this in case of a method) is not placed in the same place:

  • In a method extension C♯, the this object is the first parameter.

    • E.g. items.Select(x => x)

  • In F♯, the main object is rather the last parameter: (it's called the data-last style)

    • E.g. List.map (fun x -> x) items

The data-last style favors :

  • Pipeline: items |> List.map square |> List.sum

  • Partial application: let sortDesc = List.sortBy (fun i -> -i)

  • Composition of partially applied functions up to param “data”.

    • (List.map square) >> List.sum

⚠️ There can be some friction with .NET BCL methods because the BCL is data-first driven. The solution is to wrap the method in a new curried function having parameters sorted in an order more F♯ piping friendly.

💡 Tips

Prefer to put 1st the most static parameters = those likely to be predefined by partial applications.

E.g.: “dependencies” that would be injected into an object in C♯.

👉 Partial application is a way to implement the dependency injection in F♯.

Last updated

Was this helpful?