如何在 F# 中使用 sprintf 构建格式字符串?
how can I build a format string with sprintf, in F#?
我正在尝试从:
sprintf "%3.1f" myNumber
至:
sprintf myFormatter myNumber
这是不可能的
我遇到数字精度取决于某些设置的情况,因此我希望能够创建自己的格式化程序字符串。
我知道可以用 String.Format 来完成,但我很好奇是否有使用 sprintf 或 ksprinf 的 F# 方式;可以吗?
简单回答
编辑:F# Slack 上的 Diego Esmerio 向我展示了一种更简单的方法,老实说,我在得出下面的答案时从未想过。诀窍是直接使用PrintfFormat
,如下所示。
// Credit: Diego. This
let formatPrec precision =
PrintfFormat<float -> string,unit,string,string>(sprintf "%%1.%if" precision)
let x = 15.234
let a = sprintf (formatPrec 0) x
let b = sprintf (formatPrec 1) x
let c = sprintf (formatPrec 3) x
输出:
val formatPrec : precision:int -> PrintfFormat<(float -> string),unit,string,string>
val x : float = 15.234
val a : string = "15"
val b : string = "15.2"
val c : string = "15.234"
这种方法可以说比下面基于 Expr
的方法简单得多。对于这两种方法,请注意格式化字符串,因为它可以正常编译,但如果无效则在运行时中断。
原始答案(复杂)
这不是一件容易的事,因为像 sprintf
和 printfn
这样的函数是编译时的特殊情况函数,可以将您的字符串参数转换为函数(在这种情况下是类型float -> string
).
您可以使用 kprintf
做一些事情,但它不允许格式化参数成为动态值,因为编译器仍然需要进行类型检查。
然而,使用引号我们可以自己构建这样的函数。简单的方法是从您的表达式创建引用并更改我们需要更改的部分。
起点是这样的:
> <@ sprintf "%3.1f" @>
val it : Expr<(float -> string)> =
Let (clo1,
Call (None, PrintFormatToString,
[Coerce (NewObject (PrintfFormat`5, Value ("%3.1f")), PrintfFormat`4)]),
Lambda (arg10, Application (clo1, arg10)))
...
这看起来可能一团糟,但由于我们只需要改变一点点,我们可以相当简单地做到这一点:
open Microsoft.FSharp.Quotations // part of F#
open Microsoft.FSharp.Quotations.Patterns // part of F#
open FSharp.Quotations.Evaluator // NuGet package (with same name)
// this is the function that in turn will create a function dynamically
let withFormat format =
let expr =
match <@ sprintf "%3.1f" @> with
| Let(var, expr1, expr2) ->
match expr1 with
| Call(None, methodInfo, [Coerce(NewObject(ctor, [Value _]), mprintFormat)]) ->
Expr.Let(var, Expr.Call(methodInfo, [Expr.Coerce(Expr.NewObject(ctor, [Expr.Value format]), mprintFormat)]), expr2)
| _ -> failwith "oops" // won't happen
| _ -> failwith "oops" // won't happen
expr.CompileUntyped() :?> (float -> string)
要使用它,我们现在可以简单地这样做:
> withFormat "%1.2f" 123.4567899112233445566;;
val it : string = "123.46"
> withFormat "%1.5f" 123.4567899112233445566;;
val it : string = "123.45679"
> withFormat "%1.12f" 123.4567899112233445566;;
val it : string = "123.456789911223"
或者像这样:
> let format = "%0.4ef";;
val format : string = "%0.4ef"
> withFormat format 123.4567899112233445566;;
val it : string = "1.2346e+002f"
格式字符串现在在编译期间是否为固定字符串并不重要。但是,如果这用于性能敏感区域,您可能希望缓存结果函数,因为重新编译表达式树的成本适中。
我正在尝试从:
sprintf "%3.1f" myNumber
至:
sprintf myFormatter myNumber
这是不可能的
我遇到数字精度取决于某些设置的情况,因此我希望能够创建自己的格式化程序字符串。
我知道可以用 String.Format 来完成,但我很好奇是否有使用 sprintf 或 ksprinf 的 F# 方式;可以吗?
简单回答
编辑:F# Slack 上的 Diego Esmerio 向我展示了一种更简单的方法,老实说,我在得出下面的答案时从未想过。诀窍是直接使用PrintfFormat
,如下所示。
// Credit: Diego. This
let formatPrec precision =
PrintfFormat<float -> string,unit,string,string>(sprintf "%%1.%if" precision)
let x = 15.234
let a = sprintf (formatPrec 0) x
let b = sprintf (formatPrec 1) x
let c = sprintf (formatPrec 3) x
输出:
val formatPrec : precision:int -> PrintfFormat<(float -> string),unit,string,string>
val x : float = 15.234
val a : string = "15"
val b : string = "15.2"
val c : string = "15.234"
这种方法可以说比下面基于 Expr
的方法简单得多。对于这两种方法,请注意格式化字符串,因为它可以正常编译,但如果无效则在运行时中断。
原始答案(复杂)
这不是一件容易的事,因为像 sprintf
和 printfn
这样的函数是编译时的特殊情况函数,可以将您的字符串参数转换为函数(在这种情况下是类型float -> string
).
您可以使用 kprintf
做一些事情,但它不允许格式化参数成为动态值,因为编译器仍然需要进行类型检查。
然而,使用引号我们可以自己构建这样的函数。简单的方法是从您的表达式创建引用并更改我们需要更改的部分。
起点是这样的:
> <@ sprintf "%3.1f" @>
val it : Expr<(float -> string)> =
Let (clo1,
Call (None, PrintFormatToString,
[Coerce (NewObject (PrintfFormat`5, Value ("%3.1f")), PrintfFormat`4)]),
Lambda (arg10, Application (clo1, arg10)))
...
这看起来可能一团糟,但由于我们只需要改变一点点,我们可以相当简单地做到这一点:
open Microsoft.FSharp.Quotations // part of F#
open Microsoft.FSharp.Quotations.Patterns // part of F#
open FSharp.Quotations.Evaluator // NuGet package (with same name)
// this is the function that in turn will create a function dynamically
let withFormat format =
let expr =
match <@ sprintf "%3.1f" @> with
| Let(var, expr1, expr2) ->
match expr1 with
| Call(None, methodInfo, [Coerce(NewObject(ctor, [Value _]), mprintFormat)]) ->
Expr.Let(var, Expr.Call(methodInfo, [Expr.Coerce(Expr.NewObject(ctor, [Expr.Value format]), mprintFormat)]), expr2)
| _ -> failwith "oops" // won't happen
| _ -> failwith "oops" // won't happen
expr.CompileUntyped() :?> (float -> string)
要使用它,我们现在可以简单地这样做:
> withFormat "%1.2f" 123.4567899112233445566;;
val it : string = "123.46"
> withFormat "%1.5f" 123.4567899112233445566;;
val it : string = "123.45679"
> withFormat "%1.12f" 123.4567899112233445566;;
val it : string = "123.456789911223"
或者像这样:
> let format = "%0.4ef";;
val format : string = "%0.4ef"
> withFormat format 123.4567899112233445566;;
val it : string = "1.2346e+002f"
格式字符串现在在编译期间是否为固定字符串并不重要。但是,如果这用于性能敏感区域,您可能希望缓存结果函数,因为重新编译表达式树的成本适中。