纯文本格式
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)]]
在 printf
格式字符串中使用 %A
格式时,会激活结构化纯文本格式。
在 F# 交互窗口中设置值的输出格式时,它也会被激活,其中的输出包含额外信息,还可自定义。
纯文本格式设置也可通过对 F# 联合和记录值上的 x.ToString()
的任何调用观察,包括在调试、日志记录和其他工具中隐式发生的那些。
检查 printf
格式字符串
如果 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 是单个十进制数字,dddd 是一个或多个十进制数字,ddd 是刚好三个十进制数字,符号是 + 或 - |
%f ,%F |
基本浮点类型 | 格式设置为有符号的值,格式为 [-]dddd.dddd ,其中 dddd 是一个或多个十进制数字。 小数点前的数字位数取决于数字的度量值,小数点后的数字位数取决于所需精度。 |
%g ,%G |
基本浮点类型 | 格式设置为采用 %f 或 %e 格式打印的有符号的值,以在给定值和精度下更简洁者为准。 |
%M |
一个 decimal (System.Decimal ) 值 |
格式设置为采用 System.Decimal.ToString(format) 的 "G" 格式说明符 |
%O |
任意值 | 通过对对象进行装箱并调用其 System.Object.ToString() 方法设置格式 |
%A |
任意值 | 格式设置为将结构化纯文本格式与默认布局设置一起使用 |
%a |
任意值 | 需要两个参数:一个格式设置函数接受上下文参数和值以及要打印的特定值 |
%t |
任意值 | 需要一个参数:一个格式设置函数接受输出或返回适当文本的上下文参数 |
%% |
(无) | 不需要任何参数,打印普通百分号:% |
基本整数类型是 byte
(System.Byte
)、sbyte
(System.SByte
)、int16
(System.Int16
)、uint16
(System.UInt16
)、int32
(System.Int32
), uint32
(System.UInt32
)、int64
(System.Int64
)、uint64
(System.UInt64
)、nativeint
(System.IntPtr
) 和 unativeint
(System.UIntPtr
)。
基本浮点类型是 float
(System.Double
)、float32
(System.Single
) 和 decimal
(System.Decimal
)。
可选的宽度是一个指示结果最小宽度的整数。 例如,%6d
打印一个整数,用空格作为前缀,以至少填写六个字符。 如果宽度为 *
,则使用额外的整数参数来指定相应的宽度。
有效标志为:
标志 | 效果 |
---|---|
0 |
添加零而不是空格以构成所需宽度 |
- |
在指定的宽度内将结果左对齐 |
+ |
如果数字为正数,则添加一个 + 字符,(以针对负数匹配 - 符号) |
空格字符 | 如果数字为正数,则添加一个额外的空格,(以针对负数匹配 '-' 符号) |
printf #
标志无效,如果使用该标志,将报告编译时错误。
使用固定区域性设置值的格式。 区域性设置与 printf
格式无关,但它们影响 %O
和 %A
格式设置的结果。 有关详细信息,请参阅结构化纯文本格式设置。
%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
和 System.Globalization.CultureInfo.CurrentUICulture
提供的 .NET 默认设置。 例如,
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>
。
使用 StructuredFormatDisplay
自定义纯文本格式设置
使用 %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
自定义纯文本格式设置
ToString
的默认实现可在 F# 编程中观察到。 通常,默认结果不适合在面向程序员的信息显示或用户输出中使用,因此通常会重写默认实现。
默认情况下,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])]
使用 StructuredFormatDisplay
和 ToString
自定义纯文本格式设置
为 %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]"
将 StructuredFormatDisplay
与支持 DisplayText
属性一起使用意味着在结构打印过程中忽略 myRec
是结构性记录类型,且 ToString()
的替代在所有情况下都是首选的。
可以添加 System.IFormattable
接口的实现,以在存在 .NET 格式规范的情况下进一步自定义。
F# 交互窗口结构化打印
F# 交互窗口 (dotnet fsi
) 使用结构化纯文本格式的扩展版本来报告值,并允许其他可自定义性。 有关详细信息,请参阅 F# 交互窗口。
自定义调试显示
适用于 .NET 的调试器遵循 DebuggerDisplay
和 DebuggerTypeProxy
等属性的使用,并且这些会影响调试器检查窗口中对象的结构化显示。
F# 编译器自动生成这些属性以区分联合和记录类型,但不区分类、接口或结构类型。
这些属性在 F# 纯文本格式设置中被忽略,但在调试 F# 类型时,实现这些方法来改善显示可能会很有用。