F# 支持使用 printf
、 printfn
、 sprintf
和相关函数的纯文本类型检查格式。
例如,
dotnet fsi
> printfn "Hello %s, %d + %d is %d" "world" 2 2 (2+2);;
提供输出
Hello world, 2 + 2 is 4
F# 还允许结构化值格式化为纯文本。 例如,请考虑以下示例,将输出的格式设置为类似于矩阵的元组显示。
dotnet fsi
> printfn "%A" [ for i in 1 .. 5 -> [ for j in 1 .. 5 -> (i, j) ] ];;
[[(1, 1); (1, 2); (1, 3); (1, 4); (1, 5)];
[(2, 1); (2, 2); (2, 3); (2, 4); (2, 5)];
[(3, 1); (3, 2); (3, 3); (3, 4); (3, 5)];
[(4, 1); (4, 2); (4, 3); (4, 4); (4, 5)];
[(5, 1); (5, 2); (5, 3); (5, 4); (5, 5)]]
在格式字符串中使用%A
printf
格式时,将激活结构化纯文本格式。
当在 F# 交互中设置值输出的格式时,也会激活它,其中输出包含额外信息,并且还可以自定义。
通过对 F# 联合和记录值的任何调用 x.ToString()
(包括调试、日志记录和其他工具中隐式发生的调用)也可以看到纯文本格式。
检查 printf
-format 字符串
如果 printf
格式化函数与格式字符串中的 printf 格式说明符不匹配的参数一起使用,将报告编译时错误。 例如,
sprintf "Hello %s" (2+2)
提供输出
sprintf "Hello %s" (2+2)
----------------------^
stdin(3,25): error FS0001: The type 'string' does not match the type 'int'
从技术上讲,在使用 printf
和其他相关函数时,F# 编译器中的特殊规则会检查作为格式字符串传递的字符串文本,确保应用的后续参数的类型正确,以匹配所使用的格式说明符。
格式说明符 printf
格式规范 printf
是带有 %
指示格式的标记的字符串。 格式占位符由 %[flags][width][.precision][type]
类型解释为以下位置组成:
格式说明符 | 类型 | 注解 |
---|---|---|
%b |
bool (System.Boolean ) |
格式为 true 或 false |
%s |
string (System.String ) |
格式化为其未转义的内容 |
%c |
char (System.Char ) |
格式化为字符文本 |
%d 、%i |
基本整数类型 | 格式化为十进制整数,如果基本整数类型是有符号的,则格式化为带符号的整数。 |
%u |
基本整数类型 | 格式化为无符号十进制整数 |
%x 、%X |
基本整数类型 | 格式化为无符号十六进制数(分别用于十六进制数字的 a-f 或 A-F) |
%o |
基本整数类型 | 格式化为无符号八进制数 |
%B |
基本整数类型 | 格式化为无符号二进制数字 |
%e 、%E |
基本浮点类型 | 格式为有符号值 [-]d.dddde[sign]ddd ,其中 d 是一个十进制数字,dd 是一个或多个十进制数字,d 正好是三个十进制数字,符号为 + 或 - |
%f 、%F |
基本浮点类型 | 格式设置为具有格式 [-]dddd.dddd 的带符号值,其中 dddd 包含一个或多个十进制数字。 小数点之前的位数取决于数字的大小,小数点后的位数取决于请求的精度。 |
%g 、%G |
基本浮点类型 | 使用采用或采用格式%e 打印%f 的带符号值进行格式化,无论哪种格式,给定值和精度都更紧凑。 |
%M |
a decimal (System.Decimal ) 值 |
使用 "G" 格式说明符设置格式 System.Decimal.ToString(format) |
%O |
任何值 | 通过装箱对象并调用其 System.Object.ToString() 方法进行格式化 |
%A |
任何值 | 使用带默认布局设置 的结构化纯文本格式 设置进行格式化 |
%a |
任何值 | 需要两个参数:接受上下文参数和值的格式化函数,以及要打印的特定值 |
%t |
任何值 | 需要一个参数:接受输出或返回相应文本的上下文参数的格式设置函数 |
%% |
(无) | 无需任何参数并打印纯百分比符号: % |
基本整数类型为 byte
(System.Byte
)、()、 int16
sbyte
(System.Int16
System.SByte
)、 uint16
(System.UInt16
)、int32
System.Int32
()、 uint32
int64
System.UInt32
(System.Int64
)、()、(System.UInt64
、 uint64
、 nativeint
(System.IntPtr
)和 unativeint
()。System.UIntPtr
基本浮点类型为 float
(System.Double
)、 float32
(System.Single
) 和 decimal
(System.Decimal
)。
可选宽度是一个整数,指示结果的最小宽度。 例如, %6d
打印一个整数,以空格作为前缀,以填充至少六个字符。 如果宽度为 *
,则采用额外的整数参数来指定相应的宽度。
有效标志包括:
旗帜 | 影响 |
---|---|
0 |
添加零而不是空格以构成所需的宽度 |
- |
左对齐指定宽度内的结果 |
+ |
如果数字为正数,则添加字符 + (以匹配 - 负号) |
空格字符 | 如果数字为正数,则添加额外的空间(以匹配负数的“-”符号) |
printf #
标志无效,如果使用编译时错误,将报告错误。
值使用固定区域性进行格式化。 区域性设置与printf
格式无关,除非它们影响和%A
格式设置的结果%O
。 有关详细信息,请参阅 结构化纯文本格式。
%A
格式
格式 %A
说明符用于以人工可读的方式设置值的格式,也可用于报告诊断信息。
基元值
使用 %A
说明符设置纯文本格式时,F# 数值的格式带有其后缀和固定区域性。 浮点值使用浮点精度的 10 个位置进行格式化。 例如,
printfn "%A" (1L, 3n, 5u, 7, 4.03f, 5.000000001, 5.0000000001)
生产
(1L, 3n, 5u, 7, 4.03000021f, 5.000000001, 5.0)
使用 %A
说明符时,使用引号格式化字符串。 不添加转义代码,而是打印原始字符。 例如,
printfn "%A" ("abc", "a\tb\nc\"d")
生产
("abc", "a b
c"d")
.NET 值
使用 %A
说明符设置纯文本格式时,非 F# .NET 对象使用 x.ToString()
给定 System.Globalization.CultureInfo.CurrentCulture
的 .NET 默认设置进行 System.Globalization.CultureInfo.CurrentUICulture
格式化。 例如,
open System.Globalization
let date = System.DateTime(1999, 12, 31)
CultureInfo.CurrentCulture <- CultureInfo.GetCultureInfo("de-DE")
printfn "Culture 1: %A" date
CultureInfo.CurrentCulture <- CultureInfo.GetCultureInfo("en-US")
printfn "Culture 2: %A" date
生产
Culture 1: 31.12.1999 00:00:00
Culture 2: 12/31/1999 12:00:00 AM
结构化值
使用 %A
说明符设置纯文本格式时,块缩进用于 F# 列表和元组。 如上例所示。
还使用了数组的结构,包括多维数组。 使用语法显示 [| ... |]
单维数组。 例如,
printfn "%A" [| for i in 1 .. 20 -> (i, i*i) |]
生产
[|(1, 1); (2, 4); (3, 9); (4, 16); (5, 25); (6, 36); (7, 49); (8, 64); (9, 81);
(10, 100); (11, 121); (12, 144); (13, 169); (14, 196); (15, 225); (16, 256);
(17, 289); (18, 324); (19, 361); (20, 400)|]
默认打印宽度为 80。 可以使用格式说明符中的打印宽度来自定义此宽度。 例如,
printfn "%10A" [| for i in 1 .. 5 -> (i, i*i) |]
printfn "%20A" [| for i in 1 .. 5 -> (i, i*i) |]
printfn "%50A" [| for i in 1 .. 5 -> (i, i*i) |]
生产
[|(1, 1);
(2, 4);
(3, 9);
(4, 16);
(5, 25)|]
[|(1, 1); (2, 4);
(3, 9); (4, 16);
(5, 25)|]
[|(1, 1); (2, 4); (3, 9); (4, 16); (5, 25)|]
指定打印宽度为 0 将导致不使用打印宽度。 单个文本行将产生,输出中的嵌入字符串包含换行符。 例如:
printfn "%0A" [| for i in 1 .. 5 -> (i, i*i) |]
printfn "%0A" [| for i in 1 .. 5 -> "abc\ndef" |]
生产
[|(1, 1); (2, 4); (3, 9); (4, 16); (5, 25)|]
[|"abc
def"; "abc
def"; "abc
def"; "abc
def"; "abc
def"|]
深度限制为 4 用于序列(IEnumerable
)值,如下所示 seq { ...}
。 深度限制为 100 用于列表值和数组值。
例如,
printfn "%A" (seq { for i in 1 .. 10 -> (i, i*i) })
生产
seq [(1, 1); (2, 4); (3, 9); (4, 16); ...]
块缩进还用于公共记录和联合值的结构。 例如,
type R = { X : int list; Y : string list }
printfn "%A" { X = [ 1;2;3 ]; Y = ["one"; "two"; "three"] }
生产
{ X = [1; 2; 3]
Y = ["one"; "two"; "three"] }
如果使用 %+A
,则使用反射还会显示记录和联合的私有结构。 例如:
type internal R =
{ X : int list; Y : string list }
override _.ToString() = "R"
let internal data = { X = [ 1;2;3 ]; Y = ["one"; "two"; "three"] }
printfn "external view:\n%A" data
printfn "internal view:\n%+A" data
生产
external view:
R
internal view:
{ X = [1; 2; 3]
Y = ["one"; "two"; "three"] }
大型、循环或深度嵌套值
大型结构化值的格式设置为最大总对象节点计数 10000。
深度嵌套值的格式设置为深度为 100。 在这两种情况下 ...
,都用于对某些输出进行滑动。 例如,
type Tree =
| Tip
| Node of Tree * Tree
let rec make n =
if n = 0 then
Tip
else
Node(Tip, make (n-1))
printfn "%A" (make 1000)
生成包含某些部分的较大输出:
Node(Tip, Node(Tip, ....Node (..., ...)...))
在对象图中检测到周期,并在 ...
检测到周期的位置使用周期。 例如:
type R = { mutable Links: R list }
let r = { Links = [] }
r.Links <- [r]
printfn "%A" r
生产
{ Links = [...] }
延迟、null 和函数值
当尚未计算该值时,延迟值将打印为 Value is not created
或等效文本。
除非将值的静态类型确定为允许表示形式的联合类型null
,否则输出 null
Null 值。
F# 函数值作为其内部生成的闭包名称打印, <fun:it@43-7>
例如。
使用
使用 %A
说明符时,将尊重类型声明上属性的存在 StructuredFormatDisplay
。 这可用于指定代理项文本和属性以显示值。 例如:
[<StructuredFormatDisplay("Counts({Clicks})")>]
type Counts = { Clicks:int list}
printfn "%20A" {Clicks=[0..20]}
生产
Counts([0; 1; 2; 3;
4; 5; 6; 7;
8; 9; 10; 11;
12; 13; 14;
15; 16; 17;
18; 19; 20])
通过重写自定义纯文本格式 ToString
F# 编程中可观测到的默认实现 ToString
。 通常,默认结果不适用于面向程序员的信息显示或用户输出,因此通常重写默认实现。
默认情况下,F# 记录和联合类型将替代使用sprintf "%+A"
实现的实现ToString
。 例如,
type Counts = { Clicks:int list }
printfn "%s" ({Clicks=[0..10]}.ToString())
生产
{ Clicks = [0; 1; 2; 3; 4; 5; 6; 7; 8; 9; 10] }
对于类类型,未提供默认实现 ToString
,并且使用 .NET 默认值来报告类型的名称。 例如,
type MyClassType(clicks: int list) =
member _.Clicks = clicks
let data = [ MyClassType([1..5]); MyClassType([1..5]) ]
printfn "Default structured print gives this:\n%A" data
printfn "Default ToString gives:\n%s" (data.ToString())
生产
Default structured print gives this:
[MyClassType; MyClassType]
Default ToString gives:
[MyClassType; MyClassType]
添加替代 ToString
可以提供更好的格式设置。
type MyClassType(clicks: int list) =
member _.Clicks = clicks
override _.ToString() = sprintf "MyClassType(%0A)" clicks
let data = [ MyClassType([1..5]); MyClassType([1..5]) ]
printfn "Now structured print gives this:\n%A" data
printfn "Now ToString gives:\n%s" (data.ToString())
生产
Now structured print gives this:
[MyClassType([1; 2; 3; 4; 5]); MyClassType([1; 2; 3; 4; 5])]
Now ToString gives:
[MyClassType([1; 2; 3; 4; 5]); MyClassType([1; 2; 3; 4; 5])]
使用
若要实现一 %A
致的格式说明符和 %O
格式说明符,请结合其用法 StructuredFormatDisplay
和替代 ToString
项。 例如,
[<StructuredFormatDisplay("{DisplayText}")>]
type MyRecord =
{
a: int
}
member this.DisplayText = this.ToString()
override _.ToString() = "Custom ToString"
评估以下定义
let myRec = { a = 10 }
let myTuple = (myRec, myRec)
let s1 = sprintf $"{myRec}"
let s2 = sprintf $"{myTuple}"
let s3 = sprintf $"%A{myTuple}"
let s4 = sprintf $"{[myRec; myRec]}"
let s5 = sprintf $"%A{[myRec; myRec]}"
提供文本
val myRec: MyRecord = Custom ToString
val myTuple: MyRecord * MyRecord = (Custom ToString, Custom ToString)
val s1: string = "Custom ToString"
val s2: string = "(Custom ToString, Custom ToString)"
val s3: string = "(Custom ToString, Custom ToString)"
val s4: string = "[Custom ToString; Custom ToString]"
val s5: string = "[Custom ToString; Custom ToString]"
在支持DisplayText
属性中使用StructuredFormatDisplay
意味着myRec
在结构化打印过程中忽略结构记录类型,并且在所有情况下都首选重写ToString()
。
可以添加接口的 System.IFormattable
实现,以便在存在 .NET 格式规范的情况下进一步自定义。
F# 交互式结构化打印
F# Interactive (dotnet fsi
) 使用结构化纯文本格式的扩展版本来报告值并允许其他可自定义性。 有关详细信息,请参阅 F# 交互。
自定义调试显示
用于 .NET 的调试器遵循属性的使用DebuggerDisplay
DebuggerTypeProxy
,这些属性会影响调试器检查窗口中对象的结构化显示。
F# 编译器自动生成这些属性来区分联合和记录类型,但不是类、接口或结构类型。
这些属性在 F# 纯文本格式中被忽略,但实现这些方法可以在调试 F# 类型时改进显示。