Syntax
Declaration
let f x y = x + y + 1
Binding made with
let
keywordBinds both name (
f
) and parameters (x
andy
)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
return
keyword 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:
fun
keyword 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
delegate
type 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
match
expression 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.
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
rec
otherwise errorFS0039: ... is not defined
Very common in Fβ― to replace
for
loopsBecause it's often easier to design
And still performant thanks to tail recursion (details just after)
Tail recursion
Detected by the compiler and rewritten into a simple loop β β Better performance β β Prevents stack overflow
π‘ Accumulator argument
β Example: previous steps function rewritten as tail-recursive
Notes
Line 2:
TailCall
attribute was added in Fβ― 8 to indicate (to the compiler and to the reader) that the function should be tail recursive.Line 3:
loop
is a name idiomatic for this type of recursive inner-function. It's "accumulator" parameter is usually namedacc
, unless there is an obvious better name likecount
here.Line 8: we start the "loop" with
count = 0
.Lines 5 and 6: the recursive calls to
loop
are really the last call, hence the tail recursion.
π 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
loop
inner function is recursively reading thelist1
, accessing the current item as thehead
and passing thetail
to 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
head
is captured/accumulated in the continuation function built with a lambda that takes a list calledrest
, put thehead
on 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] :: list2
The real implementation of
List.append
is different and is optimized using other techniques including mutation.
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
Select
method has an overload taking the current index as additional parameter
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
Performance gain π
Longer compilation β οΈ
inline
keyword
inline
keywordTells the compiler to "inline" the function
Typical usage: small "syntactic sugar" function/operator
The other uses of inline
functions relate to SRTP π
Last updated
Was this helpful?