Computation Expressions 為鏈 Monad 提供了另一種語法
與 Monads 相關的是 F#
計算表示式 (CE
)。程式設計師通常實現 CE
來提供連結 Monads 的替代方法,即代替:
let v = m >>= fun x -> n >>= fun y -> return_ (x, y)
你可以這樣寫:
let v = ce {
let! x = m
let! y = n
return x, y
}
這兩種風格都是等價的,這取決於開發者的偏好。
為了演示如何實現 CE
,想象你希望所有跟蹤都包含相關 ID。此關聯 ID 將幫助關聯屬於同一呼叫的跟蹤。當包含來自併發呼叫的跟蹤的日誌檔案時,這非常有用。
問題是將相關 id 作為引數包含在所有函式中是很麻煩的。由於 Monads 允許攜帶隱式狀態, 我們將定義一個 Log Monad 來隱藏日誌上下文(即相關 id)。
我們首先定義日誌上下文和跟蹤日誌上下文的函式型別:
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 ())
我們還定義了兩個跟蹤函式,這些函式將使用日誌上下文中的相關 ID 進行記錄:
let trace v : Function<_> = fun ctx -> printfn "CorrelationId: %A - %A" ctx.CorrelationId v
let tracef fmt = kprintf trace fmt
trace
是一個 Function<unit>
,這意味著它將在呼叫時傳遞日誌上下文。從日誌上下文中我們獲取相關 ID 並將其與 v
一起跟蹤
此外,我們定義了 bind
和 return_
,當他們遵循 Monad Laws 時, 這形成了我們的 Log Monad。
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
最後我們定義了 LogBuilder
,這將使我們能夠使用 CE
語法連結 Log
Monads。
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
}
我們執行 g:
printfn "g produced %A" (Log.run g)
哪個列印品:
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
請注意,CorrelationId 隱含地從 run
傳送到 g
到 f
,這允許我們在故障排除期間關聯日誌條目。
CE
有很多功能, 但這可以幫助你開始定義自己的 CE
:s。
完整程式碼:
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