共用方式為


純文字格式

F# 支援使用 printfprintfnsprintf 和相關函式對純文字進行型別檢查格式化。 例如,

dotnet fsi

> printfn "Hello %s, %d + %d is %d" "world" 2 2 (2+2);;

提供輸出

Hello world, 2 + 2 is 4

F# 也允許將結構化值格式化為純文字。 例如,請考量下列將輸出格式化為類似矩陣的 Tuple 顯示的範例。

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 任意值 對物件進行 boxing 處理並呼叫其 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# 清單和 Tuple 使用區塊縮排。 如先前的範例所示。 此外也會使用陣列的結構,包括多維度陣列。 單一維度陣列會以 [| ... |] 語法顯示。 例如,

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

如果有 .NET 格式規格存在,您可以新增 System.IFormattable 介面的實作以進一步自訂。

F# 互動結構化列印

F# 互動 (dotnet fsi) 會使用擴充版本的結構化純文字格式來報告值,並且允許額外的自訂。 如需詳細資訊,請參閱 F# 互動

自訂偵錯顯示

.NET 的偵錯工具會顧及 DebuggerDisplayDebuggerTypeProxy 等屬性的使用,而這會對偵錯工具檢查視窗中的物件結構化顯示產生影響。 F# 編譯器會自動為差別聯集和記錄型別產生這些屬性,但不會為類別、介面或結構型別產生。

F# 純文字格式會忽略這些屬性,但實作這些方法可能有助於改善偵錯 F# 型別時的顯示。

另請參閱