プレーンテキストの書式設定

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

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] で構成され、type は次のように解釈されます。

書式指定子 解説
%b bool (System.Boolean) true または false と書式設定されます
%s string (System.String) エスケープを解除されたその内容として書式設定されます
%c char (System.Char) 文字リテラルとして書式設定されます
%d%i 基本整数型 10 進整数として書式設定され、基本整数型が符号付きの場合は符号が付きます
%u 基本整数型 符号なし 10 進整数として書式設定されます
%x%X 基本整数型 符号なし 16 進数として書式設定されます (それぞれ、16 進の桁は a-f または A-F)
%o 基本整数型 符号なし 8 進数として書式設定されます
%B 基本整数型 符号なしのバイナリ数値として書式設定されます
%e%E 基本浮動小数点型 [-]d.dddde[sign]ddd 形式の符号付きの値として書式設定されます。d は 1 桁の 10 進数、dddd は 1 桁以上の 10 進数、ddd は正確に 3 桁の 10 進数を、符号は + または - を表します
%f%F 基本浮動小数点型 [-]dddd.dddd 形式の符号付きの値として書式設定されます。dddd は、1 つ以上の 10 進数を表します。 整数部の桁数は、その数値の絶対値によって決定され、小数部の桁数は要求される精度によって決定されます。
%g%G 基本浮動小数点型 書式 %f または %e のうち、指定された値および精度を表現できる短い方で、符号付きの値として書式設定されます。
%M decimal (System.Decimal) 値 System.Decimal.ToString(format)"G" 書式指定子を使用して書式設定されます
%O 任意の値 オブジェクトをボックス化し、その System.Object.ToString() メソッドを呼び出すことによって書式設定されます
%A 任意の値 既定のレイアウト設定で構造化されたプレーンテキストの書式設定を使用して書式設定されます
%a 任意の値 2 つの引数が必要です: コンテキスト パラメーターと値を受け取る書式設定関数と、出力する特定の値
%t 任意の値 1 つの引数が必要です: 適切なテキストを出力するか返す、コンテキスト パラメーターを受け取る書式設定関数
%% (なし) 引数を必要とせず、プレーンなパーセント記号 % を出力します

基本整数型は、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) です。

省略可能な width は、結果の最小幅を示す整数です。 たとえば、%6d の場合は、最低 6 文字になるように前にスペースを追加した整数が出力されます。 幅を * にする場合は、対応する幅を明示するために、追加の整数引数を指定します。

有効なフラグは次のとおりです。

フラグ 結果
0 必要な幅にするために、スペースの代わりにゼロを追加します
- 指定された幅の範囲内で結果を左揃えにします
+ 数値が正の場合は + 文字を追加します (負の値に付ける - 記号に対応させるため)
空白文字 数値が正の場合は余分なスペースを追加します (負の値に付ける "-" 記号に対応させるため)

printf の # フラグは無効であり、使用するとコンパイル時エラーが報告されます。

値は、インバリアント カルチャを使用して書式設定されます。 カルチャ設定は、書式設定 %O%A の結果に影響を与える場合を除き、printf の書式設定には関係ありません。 詳細については、構造化されたプレーンテキストの書式設定に関する記事を参照してください。

%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 オブジェクトは、System.Globalization.CultureInfo.CurrentCulture および System.Globalization.CultureInfo.CurrentUICulture で指定された .NET の既定の設定で、x.ToString() を使用して書式設定されます。 たとえば、次のように入力します。

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# のリストとタプルに対してブロック インデントが使用されます。 上記の例を参照してください。 多次元配列を含む配列の構造も使用されます。 1 次元配列は、[| ... |] 構文で表示されます。 たとえば、次のように入力します。

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 に指定すると、印刷幅が使用されなくなります。 出力内の埋め込み文字列に改行が含まれる場合を除き、1 行のテキストが生成されます。 例

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

シーケンス (IEnumerable) 値に使用される深さの制限は 4 で、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() のオーバーライドが推奨されます。

.NET 形式の仕様が存在する場合は、今後のカスタマイズに備えて、System.IFormattable インターフェイスの実装を追加することができます。

F# インタラクティブの構造化された出力

F# インタラクティブ (dotnet fsi) では、構造化されたプレーンテキストの書式設定の拡張バージョンを使用して、値が報告され、追加のカスタマイズが可能になります。 詳細については、F# インタラクティブに関する記事を参照してください。

デバッグの表示をカスタマイズする

.NET 用のデバッガーでは、DebuggerDisplayDebuggerTypeProxy などの属性の使用が尊重され、デバッガーの検査ウィンドウでのオブジェクトの構造化された表示に反映されます。 判別された共用体型とレコード型については、F# コンパイラによってこれらの属性が自動的に生成されますが、クラス、インターフェイス、または構造体型には対応していません。

これらの属性は、F# のプレーンテキストの書式設定では無視されますが、F# の型をデバッグするときの表示を向上させるためにこれらの方法を実装すると便利な場合があります。

関連項目