如何在F#中有条件地包装sprintf? [英] How to wrap sprintf conditionally in F#?

查看:100
本文介绍了如何在F#中有条件地包装sprintf?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我读过一个类似的问题:魔术sprintf函数-如何包装它?,但是我的要求有些不同,所以我想知道它是否可行.

I have read a similar question: Magic sprintf function - how to wrap it?, but my requirement is a little bit different, so I am wondering whether it's doable or not.

首先,我想稍微说明一下这种情况,我目前有一个类似的跟踪功能

First, I want to explain the scenario a little bit, I currently have a trace function like

let Trace traceLevel ( fs : unit -> string) =
    if traceLevel <= Config.TraceLevel then
        Trace.WriteLine <| fs()

因此,仅当traceLevel小于或等于Config.TraceLevel指定的跟踪级别时,才调用函数"fs"以生成字符串. 因此,当traceLevel大于Config.TraceLevel时,则为空.完全不评估"fs" .

So the function "fs" is called to generate a string only if traceLevel is less than or equal to the trace level specified by the Config.TraceLevel. So when traceLevel is greater than the Config.TraceLevel, it's a no op. "fs" is not evaluated at all.

尽管不限于,但实际上,几乎所有用例都看起来像

Although not limited to, but in practice, almost all use cases look like

Trace 4 (fun _ -> sprintf "%s : %i"  "abc" 1)

始终编写"fun _-> sprintf"部分非常繁琐.理想情况下,提供一种用户可以编写的味道会很好

It's pretty tedious to always write the "fun _ -> sprintf" part. Ideally, it would be nice to provide a flavor that user can just write

Trace 4 "%s : %i" "abc" 1

而且可以

  • 获取sprintf提供的格式/参数检查.
  • 具有与采用lambda"fs"的原始跟踪函数相同的性能行为.这意味着,如果跟踪级别的检查返回false,则本质上是无操作的.无需支付额外费用(例如字符串格式等)

即使阅读原始这样的问题.

似乎kprintf允许对格式化的字符串调用延续函数.包装程序仍然返回由printf函数之一返回的函数(然后可以是带有一个或多个参数的函数).因此,可能会出现欺骗.但是,在上述情况下,需要的是在格式化字符串之前先评估条件,然后将格式化后的字符串应用于Trace.WriteLine.似乎现有的Printf模块具有API,以允许注入前提条件评估.因此,通过包装现有的API似乎不容易实现.

It seems that kprintf allows a continuation function to be invoked against the formatted string. The wrapper still returns a function returned by one of the printf functions (which can then be a function taking one or more arguments). So currying can be in play. However, in the case above, what needed is to evaluate a condition before formatting the string, then apply the formatted string to Trace.WriteLine. It seems that the existing Printf module has an API to allow the injection of a pre-condition evaluation. So it seems not easily doable by wrapping the existing APIs.

关于如何实现这一目标的任何想法? (我读了 FSharp.Core/printf.fs 非常简短,似乎可以通过提供一个新的派生PrintfEnv来实现.但是,这些是内部类型).

Any idea on how to achieve this? (I read FSharp.Core/printf.fs very briefly, it seems possible to do it by providing a new derived PrintfEnv. However, these are internal types).

感谢托马斯(Tomas)和林肯(Lincoln)的回答.我认为这两种方法都会影响性能.我使用fsi在机器上进行了一些简单的测量.

Thanks for the answers from Tomas and Lincoln. I think both approaches take some performance hit. I did some simple measurement on my machine with fsi.

选项1:我的原始方法在假"路径上根本没有评估"fs()".用法不是很好,因为需要编写"fun _-> sprintf"部分.

Option 1: my original approach, on the "false" path, "fs()" is not evaluated at all. The usage is not so nice, since one needs to write the "fun _ -> sprintf" part.

let trace1 lvl (fs : unit -> string) =
    if lvl <= 3 then Console.WriteLine(fs())

选项2:设置字符串格式,但将其丢弃在假"路径上

Option 2: format the string but throw it away on the "false" path

let trace2 lvl fmt = 
    Printf.kprintf (fun s -> if lvl <= 3 then Console.WriteLine(s)) fmt

选项3:通过递归,反射和框

Option 3: through recursion, reflection and box

let rec dummyFunc (funcTy : Type) retVal =
    if FSharpType.IsFunction(funcTy) then
        let retTy = funcTy.GenericTypeArguments.[1]
        FSharpValue.MakeFunction(funcTy, (fun _ -> dummyFunc retTy retVal))
    else box retVal

let trace3 lvl (fmt : Printf.StringFormat<'t, unit>) =
    if lvl <= 3 then Printf.kprintf (fun s -> Console.WriteLine(s)) fmt
    else downcast (dummyFunc typeof<'t> ())

现在我给这三个代码计时,就像

Now I timed all three with code like

for i in 1..1000000 do
    trace1 4 (fun _ -> sprintf "%s:%i" (i.ToString()) i)

for i in 1..1000000 do
    trace2 4 "%s:%i" (i.ToString()) i

for i in 1..1000000 do
    trace3 4 "%s:%i" (i.ToString()) i

这就是我得到的:

trace1: 
  Real: 00:00:00.009, CPU: 00:00:00.015, GC gen0: 2, gen1: 1, gen2: 0
trace2:
  Real: 00:00:00.709, CPU: 00:00:00.703, GC gen0: 54, gen1: 1, gen2: 0
trace3:
  Real: 00:00:50.918, CPU: 00:00:50.906, GC gen0: 431, gen1: 5, gen2: 0

因此,与选项1(尤其是选项3)相比,选项2和3的性能均显着下降.如果字符串格式更复杂,则此差距将增大.例如,如果我将格式和参数更改为

So both option 2 and 3 takes a significant perf hit compared to option 1 (especially option 3). This gap would grow if the string format is more complicated. For example, if I change the format and parameters to

"%s: %i %i %i %i %i" (i.ToString()) i (i * 2) (i * 3) (i * 4) (i * 5)

我知道

trace1: 
  Real: 00:00:00.007, CPU: 00:00:00.015, GC gen0: 3, gen1: 1, gen2: 0
trace2:
  Real: 00:00:01.912, CPU: 00:00:01.921, GC gen0: 136, gen1: 0, gen2: 0
trace3:
  Real: 00:02:10.683, CPU: 00:02:10.671, GC gen0: 1074, gen1: 14, gen2: 1

到目前为止,似乎仍然没有令人满意的解决方案来兼顾可用性和性能.

So far, there seems still no satisfying solution to get both usability and perf.

推荐答案

诀窍是使用kprintf函数:

let trace level fmt = 
  Printf.kprintf (fun s -> if level > 3 then printfn "%s" s) fmt

trace 3 "Number %d" 10
trace 4 "Better number %d" 42

您可以通过部分应用程序使用它,以便kprintf格式字符串所需的所有参数都将成为您正在定义的函数的参数.

You can use it via partial application, so that all parameters required by the format string of kprintf will become parameters of the function you are defining.

该函数然后使用最后一个字符串调用延续,因此您可以决定如何处理它.

The function then calls a continuation with the final string, and so you can decide what to do with it.

这篇关于如何在F#中有条件地包装sprintf?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆