F# Le espressioni di calcolo forniscono una sintassi alternativa per collegare le Monade


Esempio

Relativi ai Monadi sono le espressioni di calcolo F# ( CE ). Un programmatore di solito implementa un CE per fornire un approccio alternativo al concatenamento delle Monade, ovvero invece di questo:

let v = m >>= fun x -> n >>= fun y -> return_ (x, y)

Puoi scrivere questo:

let v = ce {
    let! x = m
    let! y = n
    return x, y
  }

Entrambi gli stili sono equivalenti e spetta alle preferenze degli sviluppatori quali scegliere.

Per dimostrare come implementare una CE immagina che ti piacciano tutte le tracce per includere un id di correlazione. Questo ID di correlazione aiuterà a correlare le tracce che appartengono alla stessa chiamata. Ciò è molto utile quando i file di registro contengono tracce provenienti da chiamate simultanee.

Il problema è che è complicato includere l'id di correlazione come argomento per tutte le funzioni. Poiché Monads consente di trasportare lo stato implicito , definiremo una Log Monad per nascondere il contesto del log (cioè l'id di correlazione).

Iniziamo definendo un contesto di log e il tipo di una funzione che traccia con il contesto del log:

type Context =
  {
    CorrelationId : Guid
  }
  static member New () : Context = { CorrelationId = Guid.NewGuid () }

type Function<'T> = Context -> 'T

// Runs a Function<'T> with a new log context
let run t = t (Context.New ())

Definiamo anche due funzioni di traccia che registreranno con l'ID di correlazione dal contesto del registro:

let trace v   : Function<_> = fun ctx -> printfn "CorrelationId: %A - %A" ctx.CorrelationId v
let tracef fmt              = kprintf trace fmt

trace è una Function<unit> che significa che verrà passato un contesto di log quando invocato. Dal contesto del registro, prendiamo l'id di correlazione e lo tracciamo insieme a v

Inoltre definiamo bind e return_ e mentre seguono il leggi Monade questa forma il nostro Log Monade.

let bind t uf : Function<_> = fun ctx ->
  let tv = t ctx  // Invoke t with the log context
  let u  = uf tv  // Create u function using result of t
  u ctx           // Invoke u with the log context

// >>= is the common infix operator for bind
let inline (>>=) (t, uf) = bind t uf

let return_ v : Function<_> = fun ctx -> v

Definiamo infine LogBuilder che ci consentirà di utilizzare la sintassi CE per concatenare le Monade Log .

type LogBuilder() =
  member x.Bind   (t, uf) = bind t uf
  member x.Return v       = return_ v

// This enables us to write function like: let f = log { ... }
let log = Log.LogBuilder ()

Ora possiamo definire le nostre funzioni che dovrebbero avere il contesto implicito del registro:

let f x y =
  log {
    do! Log.tracef "f: called with: x = %d, y = %d" x y
    return x + y
  }

let g =
  log {
    do! Log.trace "g: starting..."
    let! v = f 1 2
    do! Log.tracef "g: f produced %d" v
    return v
  }

Eseguiamo g con:

printfn "g produced %A" (Log.run g)

Quale stampa:

CorrelationId: 33342765-2f96-42da-8b57-6fa9cdaf060f - "g: starting..."
CorrelationId: 33342765-2f96-42da-8b57-6fa9cdaf060f - "f: called with: x = 1, y = 2"
CorrelationId: 33342765-2f96-42da-8b57-6fa9cdaf060f - "g: f produced 3"
g produced 3

Si noti che CorrelationId viene trasportato implicitamente da run a g to f che ci consente di correlare le voci del log durante la risoluzione dei problemi.

CE ha molte più funzioni ma questo dovrebbe aiutarti a iniziare a definire il tuo CE : s.

Codice completo:

module Log =
  open System
  open FSharp.Core.Printf

  type Context =
    {
      CorrelationId : Guid
    }
    static member New () : Context = { CorrelationId = Guid.NewGuid () }

  type Function<'T> = Context -> 'T

  // Runs a Function<'T> with a new log context
  let run t = t (Context.New ())

  let trace v   : Function<_> = fun ctx -> printfn "CorrelationId: %A - %A" ctx.CorrelationId v
  let tracef fmt              = kprintf trace fmt

  let bind t uf : Function<_> = fun ctx ->
    let tv = t ctx  // Invoke t with the log context
    let u  = uf tv  // Create u function using result of t
    u ctx           // Invoke u with the log context

  // >>= is the common infix operator for bind
  let inline (>>=) (t, uf) = bind t uf

  let return_ v : Function<_> = fun ctx -> v

  type LogBuilder() =
    member x.Bind   (t, uf) = bind t uf
    member x.Return v       = return_ v

// This enables us to write function like: let f = log { ... }
let log = Log.LogBuilder ()

let f x y =
  log {
    do! Log.tracef "f: called with: x = %d, y = %d" x y
    return x + y
  }

let g =
  log {
    do! Log.trace "g: starting..."
    let! v = f 1 2
    do! Log.tracef "g: f produced %d" v
    return v
  }

[<EntryPoint>]
let main argv =
  printfn "g produced %A" (Log.run g)
  0