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