与Monad相关的是F#计算表达式(CE)。程序员通常实现,CE以提供一种替代方法来链接Monad,即代替此方法:
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用作所有函数的参数很麻烦。由于Monad允许携带隐式状态,我们将定义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.CorrelationIdv let tracef fmt = kprintf trace fmt
trace是aFunction<unit>表示在调用时将传递日志上下文。从日志上下文中,我们选择相关性ID并将其与v
此外,我们定义bind并return_遵循Monad法则,这构成了Log Monad。
let bind t uf : Function<_> = fun ctx -> let tv = t ctx // 使用日志上下文调用t let u = uf tv // 使用t的结果创建u函数 u ctx // 使用日志上下文调用u // >>= is the common infix operator for bind let inline (>>=) (t, uf) = bind t uf let return_ v : Function<_> = fun ctx -> v
最后,我们定义LogBuilder了将使我们能够使用CE语法来链接LogMonad的方法。
type LogBuilder() = memberx.Bind (t, uf) = bind t uf memberx.Returnv = return_ v // 这使我们能够编写如下函数: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.CorrelationIdv let tracef fmt = kprintf trace fmt let bind t uf : Function<_> = fun ctx -> let tv = t ctx // 使用日志上下文调用t let u = uf tv // 使用t的结果创建u函数 u ctx // 使用日志上下文调用u // >>= is the common infix operator for bind let inline (>>=) (t, uf) = bind t uf let return_ v : Function<_> = fun ctx -> v type LogBuilder() = memberx.Bind (t, uf) = bind t uf memberx.Returnv = return_ v // 这使我们能够编写如下函数: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