Syntax

Declaration

let f x y = x + y + 1

  • Binding made with let keyword

  • Binds both name (f) and parameters (x and y)

  • 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 is mandatory

  • Thin 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

2. In a let binding with inference

  • To 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 an interface. πŸ“

Conversion to a Func

We can convert a lambda into a Func<> but explicitly by passing through its constructor:

Conversion to a LINQ Expression πŸš€

Conversion to a LINQ Expression πŸš€

Contrary to C#, it's more complex in Fβ™― to write an Expression<Func<> from a lambda. You have to go through a quotation and then convert it into an expression using either the FSharp.Linq.RuntimeHelpers.LeafExpressionConverter for simple cases (see example below), or the NuGet package FSharp.Quotations.Evaluator for more complex cases.

function keyword

  • Define 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.

    • 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

Recursive function

  • Function that calls itself

  • Special syntax with keyword rec otherwise error FS0039: ... is not defined

  • Very common in Fβ™― to replace for loops

    • Because 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

  1. 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.

  2. Line 3: loop is a name idiomatic for this type of recursive inner-function. It's "accumulator" parameter is usually named acc, unless there is an obvious better name like count here.

  3. Line 8: we start the "loop" with count = 0.

  4. Lines 5 and 6: the recursive calls to loop are really the last call, hence the tail recursion.

  5. We can verify that the function is compiled as a while loop 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 loop inner function is recursively reading the list1, accessing the current item as the head and passing the tail to the next iteration of the loop.

  • Line 4: when we reach the end of list1, we call the continuation function, called here cont. Another usual name for the continuation function parameter is next.

  • Line 5: the head is captured/accumulated in the continuation function built with a lambda that takes a list called rest, put the head 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.

πŸ”— 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

Example:

  • LINQ Select method has an overload taking the current index as additional parameter

  • The equivalent in the List module are the 2 functions map and mapi: 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:

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

Principle

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

  • Tells 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?