I'm trying to go from:
sprintf "%3.1f" myNumber
to:
sprintf myFormatter myNumber
这是不可能的。
我的情况是数字精度取决于一些设置,因此我想能够创建自己的格式化字符串。
我知道可以使用String.Format来实现,但我想知道是否有F#的方式来使用sprintf或ksprinf来完成,是否可行?
I'm trying to go from:
sprintf "%3.1f" myNumber
to:
sprintf myFormatter myNumber
这是不可能的。
我的情况是数字精度取决于一些设置,因此我想能够创建自己的格式化字符串。
我知道可以使用String.Format来实现,但我想知道是否有F#的方式来使用sprintf或ksprinf来完成,是否可行?
编辑:在 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"