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:
Loss of parameter names (
content: stringbecomes juststring)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
Delegateclass extendingFSharpFunc<int, int>with anInvokemethod 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
unitfor a “parameterless” functionincluding another (callback) function
Returns a single value of any type
including
unitfor a “return nothing” functionincluding 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
thisobject 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.sumPartial 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?