纯文本格式

F# 支持使用 printf, printfnsprintf 和相关函数的纯文本的已经过类型检查的格式。 例如,

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) 格式设置为 truefalse
%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.CurrentCultureSystem.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])]

使用 StructuredFormatDisplayToString 自定义纯文本格式设置

%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 的调试器遵循 DebuggerDisplayDebuggerTypeProxy 等属性的使用,并且这些会影响调试器检查窗口中对象的结构化显示。 F# 编译器自动生成这些属性以区分联合和记录类型,但不区分类、接口或结构类型。

这些属性在 F# 纯文本格式设置中被忽略,但在调试 F# 类型时,实现这些方法来改善显示可能会很有用。

请参阅