Nur-Text-Formatierung

F# unterstützt typgeprüfte Formatierungen für Nur-Text über printf, printfn, sprintf und verwandte Funktionen. Beispiel:

dotnet fsi

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

ergibt die Ausgabe

Hello world, 2 + 2 is 4

Mit F# können auch strukturierte Werte als Nur-Text formatiert werden. Im folgenden Beispiel wird die Ausgabe als matrixähnliche Anzeige von Tupeln formatiert:

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

Die strukturierte Nur-Text-Formatierung wird aktiviert, wenn das Format %A in printf-Formatierungszeichenfolgen verwendet wird. Sie wird auch aktiviert, wenn Sie die Ausgabe von Werten in F# Interactive formatieren. Dort enthält die Ausgabe zusätzliche Informationen und kann weiter angepasst werden. Nur-Text-Formatierung gibt es auch bei Aufrufen von x.ToString() für Union- und Datensatzwerte von F#. Das gilt auch für Werte, die implizit beim Debuggen, Protokollieren und in anderen Tools auftreten.

Überprüfen von printf-Formatzeichenfolgen

Wenn eine printf-Formatierungsfunktion mit einem Argument verwendet wird, das nicht mit den printf-Formatspezifizierern in der Formatzeichenfolge übereinstimmt, wird ein Kompilierzeitfehler gemeldet. Beispiel:

sprintf "Hello %s" (2+2)

ergibt die Ausgabe

  sprintf "Hello %s" (2+2)
  ----------------------^

stdin(3,25): error FS0001: The type 'string' does not match the type 'int'

Technisch gesehen überprüft bei Verwendung von printf und anderen verwandten Funktionen eine spezielle Regel im F#-Compiler das als Formatzeichenfolge übergebene Zeichenfolgenliteral, um sicherzustellen, dass die angewendeten nachfolgenden Argumente den richtigen Typ haben, um den verwendeten Formatspezifizierern zu entsprechen.

Formatspezifizierer für printf

Formatspezifikationen für printf-Formate sind Zeichenfolgen mit %-Markern, die ein Format angeben. Formatplatzhalter setzen sich wie folgt zusammen: %[flags][width][.precision][type]. Der Typ wird dabei wie folgt interpretiert:

Formatbezeichner Typ(en) Bemerkungen
%b bool (System.Boolean) Formatierung als true oder false
%s string (System.String) Formatierung als Inhalt ohne Escapezeichen
%c char (System.Char) Formatierung als Zeichenliteral
%d, %i Ein einfacher ganzzahliger Typ Als ganze Dezimalzahl formatiert, mit Vorzeichen, wenn der zugrunde liegende Ganzzahltyp mit Vorzeichen angegeben ist
%u Ein einfacher ganzzahliger Typ Formatierung als ganzzahlige Dezimalzahl ohne Vorzeichen
%x, %X Ein einfacher ganzzahliger Typ Formatierung als hexadezimale Zahl ohne Vorzeichen (a–f bzw. A–F für Hexadezimalstellen)
%o Ein einfacher ganzzahliger Typ Formatierung als Oktalzahl ohne Vorzeichen
%B Ein einfacher ganzzahliger Typ Formatierung als Binärzahl ohne Vorzeichen
%e, %E Ein einfacher Gleitkommatyp Formatierung als Wert mit Vorzeichen im Format [-]d.dddde[sign]ddd. Hierbei ist „d“ eine einzelne Dezimalstelle, „dddd“ steht für eine oder mehrere Dezimalstellen, und „ddd“ sind genau drei Dezimalstellen. Die Vorzeichen sind + und -.
%f, %F Ein einfacher Gleitkommatyp Formatierung als Wert mit Vorzeichen im Format [-]dddd.dddd, wobei dddd für eine oder mehrere Dezimalstellen steht. Die Anzahl der Ziffern vor dem Dezimaltrennzeichen ist abhängig von der Größe der Zahl, und die Anzahl der Ziffern nach dem Dezimaltrennzeichen ist abhängig von der angeforderten Genauigkeit.
%g, %G Ein einfacher Gleitkommatyp Formatierung unter Verwendung eines Werts mit Vorzeichen im Format %f oder %e – je nachdem, was für den angegebenen Wert und die Genauigkeit kompakter ist
%M Ein Wert vom Typ decimal (System.Decimal) Formatierung mit dem Formatspezifizierer "G" für System.Decimal.ToString(format)
%O Beliebiger Wert Formatierung durch Boxing des Objekts und Aufrufen der zugehörigen Methode System.Object.ToString()
%A Beliebiger Wert Formatierung unter Verwendung der strukturierten Nur-Text-Formatierung mit den Standardlayouteinstellungen
%a Beliebiger Wert Erfordert zwei Argumente: eine Formatierungsfunktion, die einen Kontextparameter und den Wert akzeptiert, sowie den auszugebenden Wert
%t Beliebiger Wert Erfordert ein einzelnes Argument: eine Formatierungsfunktion, die einen Kontextparameter akzeptiert, der den entsprechenden Text entweder aus- oder zurückgibt
%% (none) Erfordert keine Argumente und gibt ein einfaches Prozentzeichen aus: %

Einfache ganzzahlige Typen sind 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) und unativeint (System.UIntPtr). Einfache Gleitkommatypen sind float (System.Double), float32 (System.Single) und decimal (System.Decimal).

Die optionale Breite ist eine ganze Zahl, der die minimale Breite des Ergebnisses angibt. Mit %6d wird beispielsweise eine ganze Zahl mit vorangestellten Leerzeichen ausgegeben, um mindestens sechs Zeichen zu erhalten. Wenn für die Breite * angegeben wird, wird ein zusätzliches Ganzzahl-Argument verwendet, um die entsprechende Breite anzugeben.

Gültige Flags sind:

Flag Auswirkung
0 Fügt Nullen anstelle von Leerzeichen hinzu, um die erforderliche Breite zu erreichen.
- Richtet das Ergebnis innerhalb der angegebenen Breite linksbündig aus.
+ Fügt ein +-Zeichen hinzu, wenn die Zahl positiv ist (als Entsprechung für ein --Zeichen bei negativen Werten).
Leerzeichen Fügt ein zusätzliches Leerzeichen hinzu, wenn die Zahl positiv ist (als Entsprechung für ein „-“-Zeichen bei negativen Werten).

Das printf-Flag # ist ungültig, und seine Verwendung führt zu einem Kompilierzeitfehler.

Werte werden mit invarianter Kultur formatiert. Kultureinstellungen sind für die printf-Formatierung irrelevant – es sei denn, sie wirken sich auf die Ergebnisse der %O- und %A-Formatierung aus. Weitere Informationen finden Sie unter Nur-Text-Formatierung.

%A-Formatierung

Der Formatspezifizierer %A wird verwendet, um Werte für Menschen lesbar zu formatieren, und kann auch für die Meldung von Diagnoseinformationen nützlich sein.

Primitive Werte

Wenn Nur-Text mit dem Spezifizierer %A formatiert wird, werden numerische F#-Werte mit ihrem Suffix und ihrer invarianten Kultur formatiert. Gleitkommawerte werden mit einer Gleitkommagenauigkeit von zehn Stellen formatiert. Beispiel:

printfn "%A" (1L, 3n, 5u, 7, 4.03f, 5.000000001, 5.0000000001)

ergibt

(1L, 3n, 5u, 7, 4.03000021f, 5.000000001, 5.0)

Bei Verwendung des Spezifizierers %A werden Zeichenfolgen mit Anführungszeichen formatiert. Escapecodes werden nicht hinzugefügt. Stattdessen werden die unformatierten Zeichen ausgegeben. Beispiel:

printfn "%A" ("abc", "a\tb\nc\"d")

ergibt

("abc", "a      b
c"d")

.NET-Werte

Wenn Nur-Text mit dem Spezifizierer %A formatiert wird, werden F#-fremde .NET-Objekte mithilfe von x.ToString() unter Verwendung der durch System.Globalization.CultureInfo.CurrentCulture und System.Globalization.CultureInfo.CurrentUICultureangegebenen Standardeinstellungen von .NET formatiert. Beispiel:

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

ergibt

Culture 1: 31.12.1999 00:00:00
Culture 2: 12/31/1999 12:00:00 AM

Strukturierte Werte

Wenn Nur-Text mit dem Spezifizierer %A formatiert wird, wird Blockeinzug für F#-Listen und -Tupel verwendet. Dies ist im vorherigen Beispiel dargestellt. Die Struktur von Arrays wird ebenfalls verwendet, einschließlich mehrdimensionaler Arrays. Eindimensionale Arrays werden mit der Syntax [| ... |] angezeigt. Beispiel:

printfn "%A" [| for i in 1 .. 20 -> (i, i*i) |]

ergibt

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

Die Ausgabebreite ist standardmäßig auf 80 festgelegt. Diese Breite kann durch Verwendung einer Ausgabebreite im Formatspezifizierer angepasst werden. Beispiel:

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

ergibt

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

Bei einer Ausgabebreite mit dem Wert 0 wird keine Ausgabebreite verwendet. Das Ergebnis ist eine einzelne Textzeile, es sei denn, eingebettete Zeichenfolgen in der Ausgabe enthalten Zeilenumbrüche. Beispiel:

printfn "%0A" [| for i in 1 .. 5 -> (i, i*i) |]

printfn "%0A" [| for i in 1 .. 5 -> "abc\ndef" |]

ergibt

[|(1, 1); (2, 4); (3, 9); (4, 16); (5, 25)|]
[|"abc
def"; "abc
def"; "abc
def"; "abc
def"; "abc
def"|]

Für Sequenzwerte (IEnumerable) wird ein Tiefengrenzwert von 4 verwendet. Die Anzeige sieht wie folgt aus: seq { ...}. Für Listen- und Arraywerte wird ein Tiefengrenzwert von 100 verwendet. Beispiel:

printfn "%A" (seq { for i in 1 .. 10 -> (i, i*i) })

ergibt

seq [(1, 1); (2, 4); (3, 9); (4, 16); ...]

Blockeinzug wird auch für die Struktur öffentlicher Datensatz- und Union-Werte verwendet. Beispiel:

type R = { X : int list; Y : string list }

printfn "%A" { X =  [ 1;2;3 ]; Y = ["one"; "two"; "three"] }

ergibt

{ X = [1; 2; 3]
  Y = ["one"; "two"; "three"] }

Bei Verwendung von %+A wird durch Reflexion auch die private Struktur von Datensätzen und Union-Werten offengelegt. Beispiel:

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

ergibt

external view:
R

internal view:
{ X = [1; 2; 3]
  Y = ["one"; "two"; "three"] }

Große, zyklische oder tief geschachtelte Werte

Große strukturierte Werte werden bis zu einer maximalen Gesamtanzahl von 10.000 Objektknoten formatiert. Tief geschachtelte Werte werden bis zu einer Tiefe von 100 formatiert. In beiden Fällen wird ... verwendet, um einen Teil der Ausgabe wegzulassen. Beispiel:

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)

liefert eine große Ausgabe, bei der einige Teile weggelassen werden:

Node(Tip, Node(Tip, ....Node (..., ...)...))

Zyklen werden in den Objektgraphen erkannt, und an Stellen, an denen Zyklen erkannt werden, wird ... verwendet. Beispiel:

type R = { mutable Links: R list }
let r = { Links = [] }
r.Links <- [r]
printfn "%A" r

ergibt

{ Links = [...] }

Verzögerte Werte, NULL-Werte und Funktionswerte

Verzögerte Werte werden als Value is not created oder als entsprechender Text ausgegeben, wenn der Wert noch nicht ausgewertet wurde.

NULL-Werte werden als null ausgegeben, es sei denn, der statische Typ des Werts wird als Union-Typ erkannt. In diesem Fall ist null eine zulässige Darstellung.

F#-Funktionswerte werden als intern generierter Abschlussname ausgegeben. Beispiel: <fun:it@43-7>

Anpassen der Nur-Text-Formatierung mit StructuredFormatDisplay

Bei Verwendung des Spezifizierers %A wird das Attribut StructuredFormatDisplay in Typdeklarationen berücksichtigt, wenn es vorhanden ist. Dadurch können Sie Ersatztext und eine Eigenschaft für die Anzeige eines Werts angeben. Zum Beispiel:

[<StructuredFormatDisplay("Counts({Clicks})")>]
type Counts = { Clicks:int list}

printfn "%20A" {Clicks=[0..20]}

ergibt

Counts([0; 1; 2; 3;
        4; 5; 6; 7;
        8; 9; 10; 11;
        12; 13; 14;
        15; 16; 17;
        18; 19; 20])

Anpassen der Nur-Text-Formatierung durch Außerkraftsetzen von ToString

Die Standardimplementierung von ToString ist in der F#-Programmierung zu beobachten. Oftmals eignen sich die Standardergebnisse nicht für die Anzeige von Informationen für Programmierer*innen oder als Benutzerausgabe. Daher wird die Standardimplementierung häufig außer Kraft gesetzt.

Standardmäßig setzen Datensatz- und Union-Typen von F# die Implementierung von ToString mit einer Implementierung außer Kraft, die sprintf "%+A" verwendet. Beispiel:

type Counts = { Clicks:int list }

printfn "%s" ({Clicks=[0..10]}.ToString())

ergibt

{ Clicks = [0; 1; 2; 3; 4; 5; 6; 7; 8; 9; 10] }

Für Klassentypen wird keine Standardimplementierung von ToString bereitgestellt, und der .NET-Standardwert wird verwendet, der den Namen des Typs meldet. Beispiel:

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

ergibt

Default structured print gives this:
[MyClassType; MyClassType]
Default ToString gives:
[MyClassType; MyClassType]

Das Hinzufügen einer Außerkraftsetzung für ToString kann die Formatierung verbessern.

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

ergibt

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

Anpassen der Nur-Text-Formatierung mit StructuredFormatDisplay und ToString

Sie können die Verwendung von StructuredFormatDisplay mit einer Außerkraftsetzung von ToString kombinieren, um eine konsistente Formatierung für die Formatspezifizierer %A und %O zu erreichen. Beispiel:

[<StructuredFormatDisplay("{DisplayText}")>]
type MyRecord =
    {
        a: int
    }
    member this.DisplayText = this.ToString()

    override _.ToString() = "Custom ToString"

Die Auswertung der folgenden Definitionen

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

ergibt den Text

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

Wenn StructuredFormatDisplay mit der unterstützenden Eigenschaft DisplayText verwendet wird, wird bei der strukturierten Ausgabe die Tatsache ignoriert, dass myRec ein struktureller Datensatztyp ist, und die Außerkraftsetzung von ToString() wird in allen Fällen bevorzugt.

Eine Implementierung der System.IFormattable-Schnittstelle kann für weitere Anpassungen hinzugefügt werden, wenn .NET-Formatspezifikationen vorhanden sind.

Strukturierte Ausgabe in F# Interactive

F# Interactive (dotnet fsi) verwendet eine erweiterte Version der strukturierten Nur-Text-Formatierung, um Werte zu melden, und bietet zusätzliche Anpassungsmöglichkeiten. Weitere Informationen finden Sie unter F# Interactive-Optionen.

Anpassen von Debuganzeigen

Debugger für .NET berücksichtigen die Verwendung von Attributen wie DebuggerDisplay und DebuggerTypeProxy, und diese wirken sich auf die strukturierte Anzeige von Objekten in Debugger-Überprüfungsfenstern aus. Vom F#-Compiler wurden diese Attribute automatisch für Unterscheidungs-Union- und Datensatztypen generiert, nicht aber für Klassen-, Schnittstellen- oder Strukturtypen.

Diese Attribute werden in der Nur-Text-Formatierung mit F# ignoriert. Es kann jedoch hilfreich sein, diese Methoden zu implementieren, um die Anzeige beim Debuggen von F#-Typen zu verbessern.

Siehe auch