F# Training
F# Training
F# Training
  • Presentation
  • Fundamentals
    • Introduction
    • Syntax
      • Bases
      • Functions
      • Rules
      • Exceptions
    • First concepts
    • πŸ”Quiz
  • Functions
    • Signature
    • Concepts
    • Syntax
    • Standard functions
    • Operators
    • Addendum
    • πŸ”Quiz
    • πŸ“œSummary
  • Types
    • Overview
    • Tuples
    • Records
    • Unions
    • Enums
    • Anonymous records
    • Value types
    • πŸ“œRecap
    • Addendum
  • Monadic types
    • Intro
    • Option type
    • Result type
    • Smart constructor
    • πŸš€Computation expression (CE)
    • πŸš€CE theoretical basements
    • πŸ“œRecap
  • Pattern matching
    • Patterns
    • Match expression
    • Active patterns
    • πŸš€Fold function
    • πŸ“œRecap
    • πŸ•ΉοΈExercises
  • Collections
    • Overview
    • Types
    • Common functions
    • Dedicated functions
    • πŸ”Quiz
    • πŸ“œRecap
  • Asynchronous programming
    • Asynchronous workflow
    • Interop with .NET TPL
    • πŸ“œRecap
  • Module and Namespace
    • Overview
    • Namespace
    • Module
    • πŸ”Quiz
    • πŸ“œRecap
  • Object-oriented
    • Introduction
    • Members
    • Type extensions
    • Class, Struct
    • Interface
    • Object expression
    • Recommendations
Powered by GitBook
On this page
  • Declaration
  • Generic function
  • Anonymous / Lambda function
  • Lambda: usage examples
  • Conversion to a Func
  • Conversion to a LINQ Expression πŸš€
  • function keyword
  • Parameters deconstruction
  • Tuple parameter
  • Recursive function
  • Tail recursion
  • πŸ’‘ Accumulator argument
  • πŸš€ Continuation-passing style
  • Mutually recursive functions
  • Function overload
  • Template function
  • Parameter order
  • inline function
  • Principle
  • inline keyword

Was this helpful?

Edit on GitHub
  1. Functions

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

[1..10] |> List.map (fun i -> i + 1) // πŸ‘ˆ () around the lambda

// Alternative with a named function
let add1 i = i + 1
[1..10] |> List.map add1

Beware of useless lambdas: List.map (fun x -> f x) ❌ List.map f βœ…

2. In a let binding with inference

  • To make it explicit when the function returns a function

  • A kind of manual currying

  • Use sparingly ❗

let add x y = x + y                     // Regular version: auto curried
let add' x = fun y -> x + y             // 1 nested lambda
let add'' = fun x -> (fun y -> x + y)   // 2 nested lambdas

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

type Add = int -> int -> int

let add: Add =
    fun x y ->
        x + y

// val add: x: int -> y: int -> int

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:

let isNull = System.Func<_, _>(fun x -> x = null)
// val isNull: System.Func<'a,bool> when 'a: equality and 'a: null

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.

open System
open System.Linq.Expressions
open Microsoft.FSharp.Linq.RuntimeHelpers

let isNullQuot = <@ Func<_, _> (fun (x: obj) -> x = null) @>
// val isNullQuot: Quotations.Expr<System.Func<obj,bool>> =
//   NewDelegate (Func`2, x, Call (None, op_Equality, [x, Value (<null>)]))

let isNullExpr =
    isNullQuot
    |> LeafExpressionConverter.QuotationToExpression 
    |> unbox<Expression<Func<obj, bool>>>
// val isNullExpr: System.Linq.Expressions.Expression<System.Func<obj,bool>> =
//   x => (x == null)

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

let ouiNon x =
  match x with
  | true  -> "Oui"
  | false -> "Non"
// val ouiNon: x: bool -> string

// πŸ‘‰ Same with `function`:
let ouiNon =
  function
  | true  -> "Oui"
  | false -> "Non"
// val ouiNon: _arg1: bool -> string

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 πŸ“

type Person = { Name: string; Age: int }

let name { Name = x } = x     // Person -> string
let age { Age = x } = x       // Person -> int
let age' person = person.Age  // Alternative w/o deconstruction

let bob = { Name = "Bob"; Age = 18 } // Person
let bobAge = age bob // int = 18

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.

// V1 : too many parameters
let f x y z = ...

// V2 : parameters grouped in a tuple
let f params =
    let (x, y, z) = params
    ...

// V3 : idem with tuple deconstructed on site
let f (x, y, z) = ...
  • 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 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)

let rec steps (n: int) : int =
    if n = 1       then 0
    elif n % 2 = 0 then 1 + steps (n / 2)
    else                1 + steps (3 * n + 1)

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

let steps (number: int) : int =
    [<TailCall>]
    let rec loop count n =
        if n = 1       then count
        elif n % 2 = 0 then loop (count + 1) (n / 2)
        else                loop (count + 1) (3 * n + 1)

    loop 0 number

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.

πŸš€ 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:

let append list1 list2 =
    let rec loop list cont =
        match list with
        | [] -> cont list2
        | head :: tail -> loop tail (fun rest -> cont (head :: rest))

    loop list1 id

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.

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.

// ⚠️ Convoluted algo, just for illustration purposes

let rec Even x =        // πŸ‘ˆ Keyword `rec`
    if x = 0 then true
    else Odd (x-1)      // πŸ‘ˆ Call to `Odd` defined below
and Odd x =             // πŸ‘ˆ Keyword `and`
    if x = 0 then false
    else Even (x-1)     // πŸ‘ˆ Call to `Even` defined above

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:

type ComparisonResult = Bigger | Smaller | Equal // Union type πŸ“

let private compareTwoStrings (comparison: StringComparison) string1 string2 =
    let result = System.String.Compare(string1, string2, comparison)
    if result > 0 then
        Bigger
    elif result < 0 then
        Smaller
    else
        Equal

// Partial application of the 'comparison' parameter
let compareCaseSensitiveΒ Β  = compareTwoStrings StringComparison.CurrentCulture
let compareCaseInsensitive = compareTwoStrings StringComparison.CurrentCultureIgnoreCase

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β™―:

String.Compare(String, String, StringComparison)
String.Compare(String, String)
  • Comes first in Fβ™―, to enable its partial application:

compareTwoStrings    : StringComparison -> String -> String -> ComparisonResult
compareCaseSensitive :                     String -> String -> ComparisonResult

inline function

Principle

  • Performance gain πŸ‘

  • Longer compilation ⚠️

inline keyword

  • Tells the compiler to "inline" the function

  • Typical usage: small "syntactic sugar" function/operator

let inline ignore _ = ()
let inline (|>) x f = f x

let t = true |> ignore
//   ~= ignore true // After the inlining of |>
//   ~= ()          // After the inlining of ignore

The other uses of inline functions relate to SRTP πŸ“

PreviousConceptsNextStandard functions

Last updated 18 days ago

Was this helpful?

To avoid the code smell.

Example: find the number of steps to reach 1 in the

Type of recursion where the recursive call is the last instruction πŸ”—

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 πŸ“

We can verify that the function is compiled as a while loop in .

πŸ”— πŸ”—

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 πŸ“

Inline extansion ou inlining : compiler optimization to replace a function call with the its body. πŸ”—

πŸ’‘ Same as the refactoring

Examples from :

long parameter list
Collatz conjecture
Tail recursion | Wikipedia
SharpLab
Continuation-passing style | Wikipedia
Understanding continuations | fsharp for fun and profit
Inline expansion | Wikipedia
Inline Method
FSharp.Core
Versatile aggregate functions
map vs mapi