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
Propulsé par GitBook
Sur cette page
  • Computation expression
  • Builder
  • Builder desugaring
  • Exemples
  • logger
  • maybe
  • Limites
  • Imbrication de CE
  • Combinaison de CE

Cet article vous a-t-il été utile ?

Modifier sur GitHub
  1. Types monadiques

🚀 Computation expression (CE)

Computation expression

Sucre syntaxique cachant une « machinerie »

  • Applique la Separation of Concerns

  • Code + lisible Ă  l'intĂ©rieur de la computation expression (CE)

Syntaxe : builder { expr }

  • builder instance d'un Builder📍

  • expr peut contenir let, let!, do!, yield, yield!, return, return!

đź’ˇ Note : seq, async et task sont des CE

Builder

Une computation expression s'appuie sur un objet appelé Builder. → Cet objet permet éventuellement de stocker un état en background.

Pour chaque mot-clé supporté (let!, return...), le Builder implémente une ou plusieurs méthodes associées. Exemples :

  • builder { return expr } → builder.Return(expr)

  • builder { let! x = expr; cexpr } → builder.Bind(expr, (fun x -> {| cexpr |}))

Le builder peut également wrappé le résultat dans un type qui lui est propre :

  • async { return x } renvoie un type Async<'X>

  • seq { yield x } renvoie un type Seq<'X>

Builder desugaring

Le compilateur opère la traduction vers les méthodes du builder.

→ La CE masque la complexité de ces appels, souvent imbriqués :

seq {
    for n in list do
        yield n
        yield n * 10 }

// Traduit en :
seq.For(list, fun () ->
    seq.Combine(seq.Yield(n),
                seq.Delay(fun () -> seq.Yield(n * 10)) ) )

Exemples

logger

Besoin : logguer les valeurs intermédiaires d'un calcul

let log value = printfn $"{value}"

let loggedCalc =
    let x = 42
    log x  // âť¶
    let y = 43
    log y  // âť¶
    let z = x + y
    log z  // âť¶
    z

Problèmes ⚠️

  1. Verbeux : les log x gĂŞnent lecture

  2. Error prone : oublier un log, logguer mauvaise valeur...

đź’ˇ Rendre les logs implicites dans une CE lors du let! / Bind :

type LoggingBuilder() =
    let log value = printfn $"{value}"; value
    member _.Bind(x, f) = x |> log |> f
    member _.Return(x) = x

let logger = LoggingBuilder()

//---

let loggedCalc = logger {
    let! x = 42
    let! y = 43
    let! z = x + y
    return z
}

maybe

Besoin : simplifier enchaînement de "trySomething" renvoyant une Option

let tryDivideBy bottom top = // (bottom: int) -> (top: int) -> int option
    if (bottom = 0) or (top % bottom <> 0)
    then None
    else Some (top / bottom)

// Sans CE
let division =
    36
    |> tryDivideBy 2                // Some 18
    |> Option.bind (tryDivideBy 3)  // Some 6
    |> Option.bind (tryDivideBy 2)  // Some 3

// Avec CE
type MaybeBuilder() =
    member _.Bind(x, f) = x |> Option.bind f
    member _.Return(x) = Some x

let maybe = MaybeBuilder()

let division' = maybe {
    let! v1 = 36 |> tryDivideBy 2
    let! v2 = v1 |> tryDivideBy 3
    let! v3 = v2 |> tryDivideBy 2
    return v3
}

Bilan : ✅ Symétrie, ❌ Valeurs intermédiaires

Limites

Imbrication de CE

✅ On peut imbriquer des CE différentes ❌ Mais code devient difficile à comprendre

Exemple : combiner logger et maybe âť“

Solution alternative :

let inline (>>=) x f = x |> Option.bind f

let logM value = printfn $"{value}"; Some value  // 'a -> 'a option

let division' =
    36 |> tryDivideBy 2 >>= logM
      >>= tryDivideBy 3 >>= logM
      >>= tryDivideBy 2 >>= logM

Combinaison de CE

type LoginError =
    | InvalidUser | InvalidPassword
    | Unauthorized of AuthError | TokenErr of TokenError

let login username password =
    asyncResult {
        // tryGetUser: string -> Async<User option>
        let! user = username |> tryGetUser |> AsyncResult.requireSome InvalidUser
        // isPasswordValid: string -> User -> bool
        do! user |> isPasswordValid password |> Result.requireTrue InvalidPassword
        // authorize: User -> Async<Result<unit, AuthError>>
        do! user |> authorize |> AsyncResult.mapError Unauthorized
        // createAuthToken: User -> Result<AuthToken, TokenError>
        return! user |> createAuthToken |> Result.mapError TokenErr
    } // Async<Result<AuthToken, LoginError>>

PrécédentSmart constructorSuivant🚀 CE - Fondements théoriques

Dernière mise à jour il y a 2 ans

Cet article vous a-t-il été utile ?

Combiner Async + Option/Result ? → Solution : CE asyncResult + helpers dans

FsToolkit