Type Option
Présentation
A.k.a Maybe
(Haskell), Optional
(Java 8)
Modélise l'absence de valeur
→ Dans le sens d'une possibilité pour une valeur d'être absente
→ Différent de unit
qui est utilisé dans le cas où il n'y a jamais de valeur
Défini sous la forme d'une union avec 2 cases :
type Option<'Value> =
| None // Case sans donnée → quand valeur absente
| Some of 'Value // Case avec donnée → quand valeur présente
Cas d'utilisation
Modéliser un champ optionnel
type Civility = Mr | Mrs
type User = { Name: string; Civility: Civility option }
let joey = { Name = "Joey"; Civility = Some Mr }
let guest = { Name = "Guest"; Civility = None }
→ Rend explicite le fait que Name
est obligatoire et Civility
facultatif
☝ Attention : ce design n'empêche pas ici d'avoir Name = null
(limite BCL)
Opération partielle
Opération où aucune valeur de sortie n'est possible pour certaines entrées.
Exemples
Exemple 1 - Inverse d'un nombre
let inverse n = 1.0 / n
let tryInverse n =
match n with
| 0.0 -> None
| n -> Some (1.0 / n)
Fonction
Opération
Signature
n = 0.5
n = 0.0
inverse
Partielle
float -> float
2.0
infinity
❓
tryInverse
Totale
float -> float option
Some 2.0
None
👌
Exemple 2 - Recherche d'un élément dans une collection
Opération partielle :
find predicate
→ 💥 quand élément non trouvéOpération totale :
tryFind predicate
→None
ouSome item
Avantages 👍
Explicite (honnête) concernant la partialité de l'opération
Pas de valeur spéciale (et cachée) :
null
,infinity
Pas d'exception
Force le code appelant à gérer la totalité des cas :
Présence d'une valeur en sortie :
Some value
Absence d'une valeur en sortie :
None
Flux de contrôle
Pour tester la présence de la valeur (de type 'T
) dans l'option
❌ Ne pas utiliser
IsSome
,IsNone
etValue
(🤞💥)if option.IsSome then option.Value...
👌 A la main avec pattern matching
✅ Fonctions du module
Option
Manuel avec pattern matching
Exemple :
let print option =
match option with
| Some x -> printfn "%A" x
| None -> printfn "None"
print (Some 1.0) // 1.0
print None // None
Intégré au module Option
Option
Opération de Mapping de la valeur (de type 'T
) si ∃ :
option |> Option.map f
avecf
opération totale'T -> 'U
option |> Option.bind f
avecf
opération partielle'T -> 'U option
Conserver la valeur si ∃ et si respecte condition :
option |> Option.filter predicate
avecpredicate: 'T -> bool
appelé que si valeur ∃
Exercice
Implémenter map
, bind
et filter
avec pattern matching
Solution
let map f option = // (f: 'T -> 'U) -> 'T option -> 'U option
match option with
| Some x -> Some (f x)
| None -> None // 🎁 1. Pourquoi on ne peut pas écrire `None -> option` ?
let bind f option = // (f: 'T -> 'U option) -> 'T option -> 'U option
match option with
| Some x -> f x
| None -> None
let filter predicate option = // (predicate: 'T -> bool) -> 'T option -> 'T option
match option with
| Some x when predicate x -> option
| _ -> None // 🎁 2. Implémenter `filter` avec `bind` ?
Réponses 🎁
// 🎁 1. Pourquoi on ne peut pas écrire `None -> option` :
let map (f: 'T -> 'U) (option: 'T option) : 'U option =
match option with
| Some x -> Some (f x)
| None -> (*None*) option // 💥 Erreur de typage : `'U option` attendu != `'T option`
// 🎁 2. Implémenter `filter` avec `bind` :
let filter predicate option = // (predicate: 'T -> bool) -> 'T option -> 'T option
let f x = if predicate x then option else None
bind f option
Exemple
// Application console de questions/réponses
type Answer = A | B | C | D
let tryParseAnswer text =
match text with
| "A" -> Some A
| "B" -> Some B
| "C" -> Some C
| "D" -> Some D
| _ -> None
// Fonction appelée quand l'utilisateur saisit la réponse au clavier à la question posée
let checkAnswer (expectedAnswer: Answer) (givenAnswer: string) =
tryParseAnswer givenAnswer
|> Option.filter ((=) expectedAnswer)
|> Option.map (fun _ -> "✅")
|> Option.defaultValue "❌"
["X"; "A"; "B"] |> List.map (checkAnswer B) // ["❌"; "❌"; "✅"]
Bénéfices
Rend logique métier + lisible
Pas de
if hasValue then / else
Met en valeur le happy path
Centralise à la fin la gestion de l'absence de valeur
💡 Les 🚀 Computation expression (CE)📍 fournissent une syntaxe alternative + légère
Option
vs List
Option
vs List
Option ≃ Liste de 0 ou 1 élément → cf. fonction Option.toList
let noneIsEmptyList = Option.toList(None) = [] // true
let someIsListWithOneItem = Option.toList(Some 1) = [1] // true
☝ Une List
peut avoir + de 1 élément
→ Type Option
modélise mieux l'absence de valeur que type List
💡 Module Option
: beaucoup de même fonctions que module List
→ contains
, count
, exist
, filter
, fold
, forall
, map
Option
vs null
Option
vs null
De part ses interactions avec la BCL, F♯ autorise parfois la valeur null
👉 Bonne pratique
→ Isoler ces cas de figure et wrapper dans un type Option
→ Par exemple en utilisant la fonction Option.ofObj
let readLine (reader: System.IO.TextReader) =
reader.ReadLine() |> Option.ofObj
// Équivalent à faire :
match reader.ReadLine() with
| null -> None
| line -> Some line
Option
vs Nullable
Option
vs Nullable
Type System.Nullable<'T>
≃ Option<'T>
en + limité
❗ Ne marche pas pour les types références
❗ Manque comportement monadique i.e. fonctions
map
etbind
❗ En F♯, pas de magie comme en C♯ / mot clé
null
👉 Option
est le type idiomatique en F♯
💡 On utilise le type Nullable
en cas d'interop. Pour l'instancier :
→ ❌ Contrairement au C♯, le mot clé null
ne marche pas !
→ 👎 L'emploie de Unchecked.defaultof
marche mais n'est pas élégant.
→ 👍 Utiliser l'un des constructeurs : Nullable()
ou Nullable(value)
open System
let x: Nullable<int> = null
// ⚠️ The type 'Nullable<int>' does not have 'null' as a proper value. To create a null value for a Nullable type use 'System.Nullable()' (FS43)
let x = Unchecked.defaultof<Nullable<int>>
// val x: Nullable<int> = <null>
let x' = Nullable<int>()
// val x': Nullable<int> = <null>
let y = Nullable(1)
// val y: Nullable<int> = 1
Last updated
Was this helpful?