gpt4 book ai didi

f# - 如何在 F# 中有条件地包装 sprintf?

转载 作者:行者123 更新时间:2023-12-04 15:10:05 25 4
gpt4 key购买 nike

我读过一个类似的问题:Magic sprintf function - how to wrap it? ,但我的要求有点不同,所以我想知道它是否可行。

首先,我想稍微解释一下场景,我目前有一个跟踪功能,如

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

因此,只有当traceLevel 小于或等于Config.TraceLevel 指定的跟踪级别时,才会调用函数“fs”来生成字符串。 因此,当 traceLevel 大于 Config.TraceLevel 时,它是无操作的。根本不评估“fs” .

虽然不限于,但在实践中,几乎所有用例看起来都像
Trace 4 (fun _ -> sprintf "%s : %i"  "abc" 1)

总是写“fun _ -> sprintf”部分是相当乏味的。理想情况下,最好提供一种用户可以编写的风格
Trace 4 "%s : %i" "abc" 1

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

  • 即使在阅读了原始 SO question 的答案后,我也无法弄清楚如何实现这一点.

    似乎 kprintf 允许针对格式化字符串调用延续函数。包装器仍然返回由 printf 函数之一返回的函数(然后可以是带有一个或多个参数的函数)。所以柯里化(Currying)可以发挥作用。但是,在上述情况下,需要在格式化字符串之前评估条件,然后将格式化的字符串应用到 Trace.WriteLine。似乎现有的 Printf 模块有一个 API 允许注入(inject)前置条件评估。因此,通过包装现有的 API 似乎并不容易。

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

    更新

    感谢托马斯和林肯的回答。我认为这两种方法都会对性能造成一些影响。我用 fsi 在我的机器上做了一些简单的测量。

    选项 1:我原来的方法,在“假”路径上,根本不评估“fs()”。用法不是很好,因为需要编写“fun _ -> sprintf”部分。
    let trace1 lvl (fs : unit -> string) =
    if lvl <= 3 then Console.WriteLine(fs())

    选项 2:格式化字符串,但将其丢弃在“假”路径上
    let trace2 lvl fmt = 
    Printf.kprintf (fun s -> if lvl <= 3 then Console.WriteLine(s)) fmt

    选项3:通过递归、反射和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> ())

    现在我用类似的代码对这三个时间都进行了计时
    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 的性能都受到了显着影响。如果字符串格式更复杂,这个差距会扩大。例如,如果我将格式和参数更改为
    "%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

    到目前为止,似乎仍然没有令人满意的解决方案来同时获得可用性和性能。

    最佳答案

    诀窍是使用 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的格式字符串所需的所有参数将成为您正在定义的函数的参数。

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

    关于f# - 如何在 F# 中有条件地包装 sprintf?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31442608/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com