F# Training
Formation F#
Formation F#
  • Intro
  • Bases
    • Le F♯, c'est quoi ?
    • Syntaxe
    • Premiers concepts
    • 🍔 Quiz
  • Fonctions
    • Signature
    • Fonctions
    • Fonctions standard
    • Opérateurs
    • Fonctions : compléments
    • 🍔 Quiz
    • 📜 Récap’
  • Types composites
    • Généralités
    • Tuples
    • Records
    • Unions
    • Enums
    • Records anonymes
    • Types valeur
    • 🍔 Quiz
  • Types : Compléments
    • Type unit
    • Génériques
    • Types flexibles
    • Unités de mesure
    • Conversion
    • Exceptions F#
  • Pattern matching
    • Patterns
    • Match expression
    • 🚀 Active Patterns
    • 📜 Récap’
  • Collections
    • Vue d'ensemble
    • Types
    • Fonctions génériques
    • Fonctions spécifiques
    • 🍔 Quiz
    • 📜 Récap’
  • Programmation asynchrone
    • Workflow asynchrone
    • Interop avec la TPL .NET
    • 📜 Récap’
  • Types monadiques
    • Type Option
    • Type Result
    • Smart constructor
    • 🚀 Computation expression (CE)
    • 🚀 CE - Fondements théoriques
    • 📜 Récap’
  • Module & namespace
    • Vue d'ensemble
    • Namespace
    • Module
    • 🍔 Quiz
    • 📜 Récap’
  • Orienté-objet
    • Introduction
    • Membres
    • Extensions de type
    • Classe, structure
    • Interface
    • Expression objet
    • Recommandations
  • 🦚 Aller plus loin
Powered by GitBook
On this page
  • Présentation
  • Cas d'utilisation
  • Modéliser un champ optionnel
  • Opération partielle
  • Exemples
  • Exemple 1 - Inverse d'un nombre
  • Exemple 2 - Recherche d'un élément dans une collection
  • Avantages 👍
  • Flux de contrôle
  • Manuel avec pattern matching
  • Intégré au module Option
  • Exercice
  • Solution
  • Réponses 🎁
  • Exemple
  • Bénéfices
  • Option vs List
  • Option vs null
  • Option vs Nullable

Was this helpful?

Edit on GitHub
  1. Types monadiques

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 ou Some 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 et Value (🤞💥)

    • 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

Opération de Mapping de la valeur (de type 'T) si ∃ :

  • option |> Option.map f avec f opération totale 'T -> 'U

  • option |> Option.bind f avec f opération partielle 'T -> 'U option

Conserver la valeur si ∃ et si respecte condition :

  • option |> Option.filter predicate avec predicate: '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 ≃ 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

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

Type System.Nullable<'T> ≃ Option<'T> en + limité

  • ❗ Ne marche pas pour les types références

  • ❗ Manque comportement monadique i.e. fonctions map et bind

  • ❗ 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
Previous📜 Récap’NextType Result

Last updated 2 months ago

Was this helpful?