Syntax
Declaration
let f x y = x + y + 1
Binding made with
letkeywordBinds both name (
f) and parameters (xandy)Optional type annotations for parameters and/or returned value
let f (x: int) (y: int) : int = ...
Otherwise, they will be inferred, eventually with an automatic generalization
The function body is an expression:
β no need for a
returnkeyword here.β possibility to define intermediary variables and functions inside the body
Generic function
In many cases, inference works with automatic generalization
let listOf x = [x]β(x: 'a) -> 'a list
Explicit inline annotation of generic parameters
let f (x: 'a) = ...
Explicit inline annotation with generic type inference
let f (list: list<_>) = ...
Full explicit annotation of generic parameters
let f<'a> (x: 'a) = ...Pros: callers can specify the type parameter
Anonymous / Lambda function
Expression defining a function
Syntax: fun parameter1 parameter2 etc. -> expression
β Note:
funkeyword is mandatoryThin arrow
->β Bold arrow=>(Cβ―, JS)
Lambda: usage examples
1. As an argument to a high-order function
To avoid having to define a named function
Recommended for a short function, to keep it readable
Beware of useless lambdas:
List.map (fun x -> f x) β
List.map f β
2. In a let binding with inference
let binding with inferenceTo make it explicit when the function returns a function
A kind of manual currying
Use sparingly β
3. In an annotated let binding
The function signature is pre-defined using a type alias
Force implementation to follow signature
π Domain modelling made functional by Scott Wlaschin
Parameter naming
We can't provide the names of the parameters in the type alias. β
But the parameters are named in the function signature. π
To get named parameters in the type itself, we can use a
delegatetype or aninterface. π
Conversion to a Func
We can convert a lambda into a Func<> but explicitly by passing through its constructor:
Conversion to a LINQ Expression π
function keyword
function keywordDefine an anonymous function with an implicit
matchexpression surrounding the body.Short syntax equivalent to
fun x -> match x with.Takes 1 parameter which is implicit
A matter of taste
For fans of point-free π
More generally, in a succession of pipes
arg |> f1 |> function ... |> f2...β The idea is to pattern match a large expression without storing it in an intermediate variable.
Parameters deconstruction
As in JavaScript, you can deconstruct inline a parameter.
This is also a way of indicating the type of parameter.
The parameter appears without a name in the signature.
Example with a Record type π
This is also called pattern matching.
β But I prefer to reserve this expression to match x with ...
Tuple parameter
As in Cβ―, you may want to group function parameters together.
For the sake of cohesion, when these parameters form a whole.
To avoid the long parameter list code smell.
They can be grouped together in a tuple and even deconstructed.
f (x, y, z)looks a lot like a Cβ― method!The signature indicates the change:
(int * int * int) -> TResult.The function now only has only 1 parameter instead of 3.
Loss of partial application of each element of the tuple.
Conclusion
Resist the temptation to use a tuple all the time (because it's familiar, a habit from Cβ―)
Use it only when it makes sense to group parameters together.
If relevant, even use a dedicated (anonymous) record type. π
Recursive function
Function that calls itself
Special syntax with keyword
recotherwise errorFS0039: ... is not definedVery common in Fβ― to replace
forloopsBecause it's often easier to design
And still performant thanks to tail recursion (details just after)
Example: find the number of steps to reach 1 in the Collatz conjecture
Tail recursion
Type of recursion where the recursive call is the last instruction π Tail recursion | Wikipedia
Detected by the compiler and rewritten into a simple loop β β Better performance β β Prevents stack overflow
π‘ Accumulator argument
In general, we can transform a non-tail-recursive function into a tail-recursive one by adding an additional parameter, playing the role of "accumulator" like with fold/reduce functions - see Versatile aggregate functions π
β Example: previous steps function rewritten as tail-recursive
Notes
Line 2:
TailCallattribute was added in Fβ― 8 to indicate (to the compiler and to the reader) that the function should be tail recursive.Line 3:
loopis a name idiomatic for this type of recursive inner-function. It's "accumulator" parameter is usually namedacc, unless there is an obvious better name likecounthere.Line 8: we start the "loop" with
count = 0.Lines 5 and 6: the recursive calls to
loopare really the last call, hence the tail recursion.We can verify that the function is compiled as a
whileloop in SharpLab.
π Continuation-passing style
Another common trick to write a tail-recursive function, although more advanced, is to accumulate values inside a series of continuation function calls.
β Example: we can write List.append using this style:
Notes
The
loopinner function is recursively reading thelist1, accessing the current item as theheadand passing thetailto the next iteration of the loop.Line 4: when we reach the end of
list1, we call the continuation function, called herecont. Another usual name for the continuation function parameter isnext.Line 5: the
headis captured/accumulated in the continuation function built with a lambda that takes a list calledrest, put theheadon top of it, and pass the resulting list to the previous version of the continuation function.Let's unwrap the continuation functions:
0 β¬ fun rest0 -> id (list1[0] :: rest0) β fun rest0 -> list1[0] :: rest0 1 β¬ fun rest1 -> (fun rest0 -> list1[0] :: rest0)(list1[1] :: rest1) β fun rest1 -> list1[0] :: list1[1] :: rest1 ... N β¬ fun restN -> list1[0] :: list1[1] :: ... :: list1[N] :: restN β list1[0] :: list1[1] :: ... :: list1[N] :: list2The real implementation of
List.appendis different and is optimized using other techniques including mutation.
π Continuation-passing style | Wikipedia π Understanding continuations | fsharp for fun and profit
Mutually recursive functions
Functions that call each other
Must be declared together:
1st function indicated as recursive with
rec.other functions added to the declaration with
and.
Function overload
Warning
A function cannot be overloaded!
Each version of the functions should have a dedicated and unique name.
Example:
LINQ
Selectmethod has an overload taking the current index as additional parameterThe equivalent in the
Listmodule are the 2 functionsmapandmapi:List.map (mapping: 'T -> 'U) (items: 'T list) : 'U list List.mapi (mapping: (index: int) -> 'T -> 'U) (items: 'T list) : 'U listβ See map vs mapi π
Template function
Create specialized "overloads"
β Example: helper wrapping String.Compare:
Note that compareTwoStrings is declared as private: to hide implementation details.
Parameter order
The additional parameter is placed at a different location in Cβ― and Fβ―:
Comes last in Cβ―:
Comes first in Fβ―, to enable its partial application:
inline function
inline functionPrinciple
Inline extansion ou inlining : compiler optimization to replace a function call with the its body. π Inline expansion | Wikipedia
Performance gain π
Longer compilation β οΈ
π‘ Same as the refactoring Inline Method
inline keyword
inline keywordTells the compiler to "inline" the function
Typical usage: small "syntactic sugar" function/operator
Examples from FSharp.Core:
The other uses of inline functions relate to SRTP π
Last updated
Was this helpful?