纯文本格式

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

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)]]

在格式字符串中使用%Aprintf格式时,将激活结构化纯文本格式。 当在 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 boolSystem.Boolean 格式为 truefalse
%s stringSystem.String 格式化为其未转义的内容
%c charSystem.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 decimalSystem.Decimal) 值 使用 "G" 格式说明符设置格式 System.Decimal.ToString(format)
%O 任何值 通过装箱对象并调用其 System.Object.ToString() 方法进行格式化
%A 任何值 使用带默认布局设置 的结构化纯文本格式 设置进行格式化
%a 任何值 需要两个参数:接受上下文参数和值的格式化函数,以及要打印的特定值
%t 任何值 需要一个参数:接受输出或返回相应文本的上下文参数的格式设置函数
%% (无) 无需任何参数并打印纯百分比符号: %

基本整数类型为 byteSystem.Byte)、()、 int16sbyteSystem.Int16System.SByte)、 uint16System.UInt16)、int32System.Int32()、 uint32int64System.UInt32System.Int64)、()、(System.UInt64uint64nativeintSystem.IntPtr)和 unativeint ()。System.UIntPtr 基本浮点类型为 floatSystem.Double)、 float32System.Single) 和 decimalSystem.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 的调试器遵循属性的使用DebuggerDisplayDebuggerTypeProxy,这些属性会影响调试器检查窗口中对象的结构化显示。 F# 编译器自动生成这些属性来区分联合和记录类型,但不是类、接口或结构类型。

这些属性在 F# 纯文本格式中被忽略,但实现这些方法可以在调试 F# 类型时改进显示。

另请参阅