To avoid infinite recursion, we need an "exit" case:
β Empty list named Empty and noted []
π Generic and recursive union type:
type List<'T> =
| ( [] )
| ( :: ) of head: 'T * tail: List<'T>
βοΈ Note: this syntax with cases as operator is only allowed in FSharp.Core.
Type alias
List(big L) refers to the Fβ― type or its companion module.
list(small l) is an alias of Fβ―'s List type, often used with OCaml notation:
β let l : string list = ...
β οΈ Warnings:
β After open System.Collections.Generic:
List is the Cβ― mutable list, hiding the Fβ― type!
The List Fβ― companion module remains available β confusion!
π‘ Use the ResizeArray alias to reference the Cβ― list directly
no need for open System.Collections.Generic π
Immutability
A List is immutable:
β It is not possible to modify an existing list.
Adding an element in the list:
= Cheap operation with the Cons operator (::)
β Creates a new list with:
β’ Head = given element
β’ Tail = existing list
// Range: Start..End (Step=1)
let numFromOneToFive = [1..5] // [1; 2; 3; 4; 5]
// Range: Start..Step..End
let oddFromOneToNine = [1..2..9] // [1; 3; 5; 7; 9]
// Comprehension
let pairsWithDistinctItems =
[ for i in [1..3] do
for j in [1..3] do
if i <> j then
i, j ]
// [(1; 2); (1; 3); (2, 1); (2, 3); (3, 1); (3, 2)]
Exercices πΉοΈ
1. Implement the rev function
β Inverts a list: rev [1; 2; 3] β‘ [3; 2; 1]
2. Implement the map function
β Transforms each element: [1; 2; 3] |> map ((+) 1) β‘ [2; 3; 4]
π‘ Hints
β Use empty list [] or Conshead :: tail patterns
β Write a recursive function
Solution
let rev list =
let rec loop acc rest =
match rest with
| [] -> acc
| x :: xs -> loop (x :: acc) xs
loop [] list
let map f list =
let rec loop acc rest =
match rest with
| [] -> acc
| x :: xs -> loop (f x :: acc) xs
list |> loop [] |> rev
We can verify that it's working in FSI console:
let (=!) actual expected =
if actual = expected
then printfn $"β {actual}"
else printfn $"β {actual} != {expected}"
[1..3] |> rev =! [3; 2; 1];;
// β [3; 2; 1]
[1..3] |> map ((+) 1) =! [2; 3; 4];;
// β [2; 3; 4]
Type Array
Signature: 'T array(recommended) or 'T[] or 'T []
Main differences compared to the List:
Fixed-size
Fat square brackets [| |] for literals
Mutable β
Access by index in O(1) π
// Literal
[| 1; 2; 3; 4; 5 |] // val it : int [] = [|1; 2; 3; 4; 5|]
// Range
[| 1 .. 5 |] = [| 1; 2; 3; 4; 5 |] // true
[| 1 .. 3 .. 10 |] = [| 1; 4; 7; 10 |] // true
// Comprehension
[| for i in 1 .. 5 -> i, i * 2 |]
// [|(1, 2); (2, 4); (3, 6); (4, 8); (5, 10)|]
// Slicing: sub-array between the given `(start)..(end)` indices
let names = [|"0: Alice"; "1: Jim"; "2: Rachel"; "3: Sophia"; "4: Tony"|]
names[1..3] // [| "1: Jim"; "2: Rachel"; "3: Sophia" |]
names[2..] // [| "2: Rachel"; "3: Sophia"; "4: Tony"|]
names[..3] // [|"0: Alice"; "1: Jim"; "2: Rachel"; "3: Sophia" |]
// Mutation
let names = [| "Juliet"; "Tony" |]
names[1] <- "Bob"
names;; // [| "Juliet"; "Bob" |]
π‘ Slicing works also with string: "012345"[1..3] β‘ "123"
Alias ResizeArray
Alias for System.Collections.Generic.List<T> BCL type
let rev items = items |> Seq.rev |> ResizeArray
let initial = ResizeArray [ 1..5 ]
let reversed = rev initial // ResizeArray [ 5..-1..0 ]
Advantages π
No need for open System.Collections.Generic
No name conflicts on List
Notes βοΈ
Do not confuse the alias ResizeArray with the Array Fβ― type.
ResizeArray is in Fβ― a better name for the BCL generic List<T>
Closer semantically and in usage to an array than a list
Type Seq
Definition: Series of elements of the same type
't seq β‘ Seq<'T> β‘ IEnumerable<'T>
Lazy: sequence built gradually as it is iterated
β All other collections built entirely from their declaration
Parameter: initializer is used to create the specified index element (>= 0)
let seqOfSquares = Seq.initInfinite (fun i -> i * i)
seqOfSquares |> Seq.take 5 |> List.ofSeq;;
// val it: int list = [0; 1; 4; 9; 16]
Option 2
Recursive function to generate the sequence
[<TailCall>]
let rec private squaresStartingAt n =
seq {
yield n * n
yield! squaresStartingAt (n + 1) // ποΈ
}
let squares = squaresStartingAt 0
squares |> Seq.take 10 |> List.ofSeq;;
// val it: int list = [0; 1; 4; 9; 16; 25; 36; 49; 64; 81]
Type Set
Self-ordering collection of unique elements (without duplicates)
Implemented as a binary tree
// Construct
set [ 2; 9; 4; 2 ] // set [2; 4; 9] // β Only one '2' in the set
Set.ofArray [| 1; 3 |] // set [1; 3]
Set.ofList [ 1; 3 ] // set [1; 3]
seq { 1; 3 } |> Set.ofSeq // set [1; 3]
// Add/remove element
Set.empty // set []
|> Set.add 2 // set [2]
|> Set.remove 9 // set [2] // β No exception
|> Set.add 9 // set [2; 9]
|> Set.remove 9 // set [2]
Informations
β count, minElement, maxElement
let oneToFive = set [1..5] // set [1; 2; 3; 4; 5]
// Number of elements: `Count` property or `Set.count` function - β οΈ O(N)
// β Do not confuse with `Xxx.length` for Array, List, Seq
let nb = Set.count oneToFive // 5
// Element min, max
let min = oneToFive |> Set.minElement // 1
let max = oneToFive |> Set.maxElement // 5
// Construct: from collection of (key, val) pairs
// β `Map.ofXxx` function β’ Xxx = Array, List, Seq
let map1 = seq { (2, "A"); (1, "B") } |> Map.ofSeq
// β `Map(tuples)` constructor
let map2 = Map [ (2, "A"); (1, "B"); (3, "C"); (3, "D") ]
// map [(1, "B"); (2, "A"); (3, "D")]
// π Ordered by key (1, 2, 3) and deduplicated in last win - see '(3, "D")'
// Add/remove entry
Map.empty // map []
|> Map.add 2 "A" // map [(2, "A")]
|> Map.remove 5 // map [(2, "A")] // β No exception if key not found
|> Map.add 9 "B" // map [(2, "A"); (9, "B")]
|> Map.remove 2 // map [(9, "B")]
Access/Lookup by key
let table = Map [ ("A", "Abc"); ("G", "Ghi"); ("Z", "Zzz") ]
// Indexer by key
table["A"];; // val it: string = "Abc"
table["-"];; // π£ KeyNotFoundException
// `Map.find`: return the matching value or π£ if the key is not found
table |> Map.find "G";; // val it: string = "Ghi"
// `Map.tryFind`: return the matching value in an option
table |> Map.tryFind "Z";; // val it: string option = Some "Zzz"
table |> Map.tryFind "-";; // val it: string option = None
Dictionaries
dict function
Builds an IDictionary<'k, 'v> from a sequence of key/value pairs
The interface is error prone: the dictionary is immutable β
let table = dict [ (1, 100); (2, 200) ]
// val table: System.Collections.Generic.IDictionary<int,int>
table[1];; // val it: int = 100
table[99];; // π£ KeyNotFoundException
table[1] <- 111;; // π£ NotSupportedException: This value cannot be mutated
table.Add(3, 300);; // π£ NotSupportedException: This value cannot be mutated
readOnlyDict function
Builds an IReadOnlyDictionary<'k, 'v> from a sequence of key/value pairs
The interface is honest: the dictionary is immutable
let table = readOnlyDict [ (1, 100); (2, 200) ]
// val table: System.Collections.Generic.IReadOnlyDictionary<int,int>
table[1];; // val it: int = 100
table[99];; // π£ KeyNotFoundException
do table[1] <- 111;;
// ~~~~~~~~ π₯ Error FS0810: Property 'Item' cannot be set
do table.Add(3, 300);;
// ~~~ π₯ Error FS0039:
// The type 'IReadOnlyDictionary<_,_>' does not define the field, constructor or member 'Add'.
Recommendation
dict returns an object that does not implement fully IDictionary<'k, 'v>
β Violate the Liskov's substitution principleβ
readOnlyDict returns an object that respects IReadOnlyDictionary<'k, 'v>
π Prefer readOnlyDict to dict when possible
KeyValue active pattern
Used to deconstruct a KeyValuePair dictionary entry to a (key, value) pair
// FSharp.Core / prim-types.fs#4983
let (|KeyValue|) (kvp: KeyValuePair<'k, 'v>) : 'k * 'v =
kvp.Key, kvp.Value
let table =
readOnlyDict
[ (1, 100)
(2, 200)
(3, 300) ]
// Iterate through the dictionary
for kv in table do // kv: KeyValuePair<int,int>
printfn $"{kv.Key}, {kv.Value}"
// Same with the active pattern
for KeyValue (key, value) in table do
printfn $"{key}, {value}"
Lookup performance
Dictionary vs Map
readOnlyDict creates high-performance dictionaries
β 10x faster than Map for lookups
DictionaryvsArray
~ Rough heuristics
β The Array type is OK for few lookups (< 100) and few elements (< 100)
β Use a Dictionary otherwise
Map and SetvsIComparable
Only works if elements (of a Set) or keys (of a Map) are comparable!
Examples:
// Classes are not comparable by default, so you cannot use them in a set or a map
type NameClass(name: string) =
member val Value = name
let namesClass = set [NameClass("Alice"); NameClass("Bob")]
// ~~~~~~~~~~~~~~~~~~
// π₯ Error FS0193: The type 'NameClass' does not support the 'comparison' constraint.
// For example, it does not support the 'System.IComparable' interface
F# functional type: tuple, record, union
// Example: single-case union
type Name = Name of string
let names = set [Name "Alice"; Name "Bob"]
Structs:
[<Struct>]
type NameStruct(name: string) =
member this.Name = name
let namesStruct = set [NameStruct("Alice"); NameStruct("Bob")]
Classes implementing IComparable... but not IComparable<'T> π€·