Formatowanie zwykłego tekstu

Język F# obsługuje formatowanie sprawdzane przez typ zwykłego tekstu przy użyciu printffunkcji , printfn, sprintfi powiązanych. Przykład:

dotnet fsi

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

udostępnia dane wyjściowe

Hello world, 2 + 2 is 4

Język F# umożliwia również formatowanie wartości strukturalnych jako zwykłego tekstu. Rozważmy na przykład następujący przykład, który formatuje dane wyjściowe jako macierzowy wyświetlacz krotki.

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

Formatowanie zwykłego tekstu ze strukturą jest aktywowane w przypadku używania %A formatu w printf ciągach formatowania. Jest on również aktywowany podczas formatowania danych wyjściowych wartości w interaktywnym języku F#, gdzie dane wyjściowe zawierają dodatkowe informacje i można je dodatkowo dostosowywać. Formatowanie zwykłego tekstu można również zaobserwować za pomocą wywołań w x.ToString() unii i wartości rekordów języka F#, w tym tych, które występują niejawnie podczas debugowania, rejestrowania i innych narzędzi.

printfSprawdzanie ciągów -format

Błąd czasu kompilacji zostanie zgłoszony, jeśli printf funkcja formatowania jest używana z argumentem, który nie pasuje do specyfikatorów formatu printf w ciągu formatu. Przykład:

sprintf "Hello %s" (2+2)

udostępnia dane wyjściowe

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

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

Technicznie rzecz biorąc, jeśli używasz printf i innych powiązanych funkcji, specjalna reguła w kompilatorze języka F# sprawdza literał ciągu przekazany jako ciąg formatu, zapewniając, że kolejne zastosowane argumenty są poprawnego typu, aby dopasować używane specyfikatory formatu.

Specyfikatory formatu dla printf

Specyfikacje formatu dla printf formatów to ciągi ze % znacznikami wskazującymi format. Symbole zastępcze formatu składają się z %[flags][width][.precision][type] tego, gdzie typ jest interpretowany w następujący sposób:

Specyfikator formatu Typ(-y) Uwagi
%b bool (System.Boolean) Sformatowane jako true lub false
%s string (System.String) Sformatowany jako jego niezaobejmowana zawartość
%c char (System.Char) Sformatowany jako literał znaku
%d, %i podstawowy typ liczby całkowitej Sformatowany jako liczba całkowita dziesiętna, podpisany, jeśli jest podpisany podstawowy typ liczby całkowitej
%u podstawowy typ liczby całkowitej Sformatowane jako niepodpisana liczba całkowita dziesiętna
%x, %X podstawowy typ liczby całkowitej Sformatowany jako niepodpisany numer szesnastkowy (odpowiednio cyfry szesnastkowe a-f lub A-F)
%o podstawowy typ liczby całkowitej Sformatowany jako niepodpisany numer ósemkowy
%B podstawowy typ liczby całkowitej Sformatowany jako niepodpisany numer binarny
%e, %E podstawowy typ zmiennoprzecinkowa Sformatowany jako wartość ze znakiem o formularzu [-]d.dddde[sign]ddd , w którym d jest pojedynczą cyfrą dziesiętną, dddd jest co najmniej jedną cyfrą dziesiętną, ddd jest dokładnie trzy cyfry dziesiętne, a znak jest + lub -
%f, %F podstawowy typ zmiennoprzecinkowa Sformatowany jako wartość ze znakiem o formularzu [-]dddd.dddd, gdzie dddd jest co najmniej jedną cyfrą dziesiętną. Liczba cyfr przed punktem dziesiętnym zależy od wielkości liczby, a liczba cyfr po przecinku dziesiętnym zależy od żądanej precyzji.
%g, %G podstawowy typ zmiennoprzecinkowa Sformatowane przy użyciu wartości podpisanej wydrukowanej w formacie lub %e w %f zależności od tego, która wartość jest bardziej kompaktowa dla danej wartości i precyzji.
%M a decimal (System.Decimal) wartość Sformatowane przy użyciu specyfikatora "G" formatu dla System.Decimal.ToString(format)
%O dowolna wartość Sformatowane przez pole obiektu i wywoływanie jego System.Object.ToString() metody
%A dowolna wartość Sformatowane przy użyciu formatowania zwykłego tekstu ze strukturą z domyślnymi ustawieniami układu
%a dowolna wartość Wymaga dwóch argumentów: funkcja formatowania akceptująca parametr kontekstu i wartość oraz konkretną wartość do wydrukowania
%t dowolna wartość Wymaga jednego argumentu: funkcja formatowania akceptująca parametr kontekstu, który zwraca lub zwraca odpowiedni tekst
%% (brak) Nie wymaga żadnych argumentów i drukuje zwykły znak procentu: %

Podstawowe typy liczb całkowitych to byte (System.Byte), sbyte (System.SByte), int16System.Int16(), uint16 (System.UInt16), (), int32 (System.Int32), uint32 (System.Int64System.UInt64int64System.UInt32nativeintSystem.IntPtruint64) i unativeint ().System.UIntPtr Podstawowe typy zmiennoprzecinkowe to float (System.Double), float32 (System.Single) i decimal (System.Decimal).

Opcjonalna szerokość to liczba całkowita wskazująca minimalną szerokość wyniku. Na przykład %6d drukuje liczbę całkowitą, prefiksując ją spacjami, aby wypełnić co najmniej sześć znaków. Jeśli szerokość to *, zostanie pobrany dodatkowy argument liczby całkowitej, aby określić odpowiednią szerokość.

Prawidłowe flagi to:

Flaga Efekt
0 Dodaj zera zamiast spacji, aby utworzyć wymaganą szerokość
- W lewo uzasadniaj wynik w określonej szerokości
+ + Dodaj znak, jeśli liczba jest dodatnia (aby dopasować - znak dla wartości ujemnych)
znak spacji Dodaj dodatkowe miejsce, jeśli liczba jest dodatnia (aby dopasować znak "-" dla wartości ujemnych)

Flaga printf # jest nieprawidłowa i zostanie zgłoszony błąd czasu kompilacji, jeśli jest używany.

Wartości są formatowane przy użyciu niezmiennej kultury. Ustawienia kultury nie mają znaczenia dla printf formatowania, z wyjątkiem sytuacji, gdy mają wpływ na wyniki %O i %A formatowanie. Aby uzyskać więcej informacji, zobacz Formatowanie zwykłego tekstu ze strukturą.

%A Formatowania

Specyfikator %A formatu służy do formatowania wartości w sposób czytelny dla człowieka i może być również przydatny w przypadku raportowania informacji diagnostycznych.

Wartości pierwotne

Podczas formatowania zwykłego %A tekstu przy użyciu specyfikatora wartości liczbowe języka F# są formatowane z sufiksem i niezmienną kulturą. Wartości zmiennoprzecinkowe są formatowane przy użyciu 10 miejsc precyzji zmiennoprzecinkowych. Przykład:

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

Produkuje

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

W przypadku korzystania z specyfikatora %A ciągi są formatowane przy użyciu cudzysłowów. Kody ucieczki nie są dodawane i zamiast tego są drukowane nieprzetworzone znaki. Przykład:

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

Produkuje

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

Wartości platformy .NET

Podczas formatowania zwykłego %A tekstu przy użyciu specyfikatora obiekty inne niż F# platformy .NET są formatowane przy użyciu x.ToString() domyślnych ustawień platformy .NET podanych przez System.Globalization.CultureInfo.CurrentCulture i System.Globalization.CultureInfo.CurrentUICulture. Przykład:

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

Produkuje

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

Wartości ustrukturyzowane

Podczas formatowania zwykłego tekstu przy użyciu specyfikatora %A wcięcie bloku jest używane dla list i krotek języka F#. Jest to pokazane w poprzednim przykładzie. Używana jest również struktura tablic, w tym tablic wielowymiarowych. Tablice jednowymiarowe są wyświetlane ze składnią [| ... |] . Przykład:

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

Produkuje

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

Domyślna szerokość wydruku to 80. Tę szerokość można dostosować przy użyciu szerokości wydruku w specyfikatorze formatu. Przykład:

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

Produkuje

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

Określenie szerokości wydruku 0 powoduje, że nie jest używana szerokość wydruku. Zostanie utworzony pojedynczy wiersz tekstu, z wyjątkiem sytuacji, w których osadzone ciągi w danych wyjściowych zawierają podziały wierszy. Na przykład

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

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

Produkuje

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

Limit głębokości 4 jest używany dla wartości sekwencji (IEnumerable), które są wyświetlane jako seq { ...}. Limit głębokości 100 jest używany dla wartości listy i tablicy. Przykład:

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

Produkuje

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

Wcięcie bloku jest również używane dla struktury wartości rekordów publicznych i unii. Przykład:

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

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

Produkuje

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

Jeśli %+A jest używany, prywatna struktura rekordów i związków jest również ujawniana przy użyciu odbicia. Na przykład

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

Produkuje

external view:
R

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

Duże, cykliczne lub głęboko zagnieżdżone wartości

Duże wartości ustrukturyzowane są sformatowane do maksymalnej ogólnej liczby węzłów obiektu 10000. Głęboko zagnieżdżone wartości są formatowane na głębokość 100. W obu przypadkach ... służy do elide niektórych danych wyjściowych. Przykład:

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)

generuje duże dane wyjściowe z niektórymi częściami elided:

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

Cykle są wykrywane w grafach obiektów i ... są używane w miejscach, w których wykryto cykle. Na przykład

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

Produkuje

{ Links = [...] }

Wartości z opóźnieniem, null i funkcji

Wartości z opóźnieniem są drukowane jako Value is not created tekst lub równoważne, gdy wartość nie została jeszcze obliczona.

Wartości null są drukowane jako null , chyba że statyczny typ wartości jest określany jako typ unii, gdzie null jest dozwoloną reprezentacją.

Wartości funkcji języka F# są drukowane jako ich wewnętrznie wygenerowana nazwa zamknięcia, na przykład <fun:it@43-7>.

Dostosowywanie formatowania zwykłego tekstu za pomocą polecenia StructuredFormatDisplay

W przypadku używania %A specyfikatora obecność atrybutu StructuredFormatDisplay w deklaracjach typów jest przestrzegana. Może to służyć do określania zastępczego tekstu i właściwości w celu wyświetlenia wartości. Na przykład:

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

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

Produkuje

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

Dostosowywanie formatowania zwykłego tekstu przez zastąpienie ToString

Domyślna implementacja ToString programu jest zauważalna w programowaniu języka F#. Często domyślne wyniki nie są odpowiednie do użycia w wyświetlaniu informacji programisty lub danych wyjściowych użytkownika, a w związku z tym często przesłaniają domyślną implementację.

Domyślnie typy rekordów ToString i unii języka F# zastępują implementację za pomocą implementacji używającej polecenia sprintf "%+A". Przykład:

type Counts = { Clicks:int list }

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

Produkuje

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

W przypadku typów klas nie podano domyślnej implementacji ToString programu i jest używana wartość domyślna platformy .NET, która zgłasza nazwę typu. Przykład:

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

Produkuje

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

Dodanie przesłonięcia dla ToString elementu może zapewnić lepsze formatowanie.

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

Produkuje

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

Dostosowywanie formatowania zwykłego tekstu za pomocą polecenia StructuredFormatDisplay i ToString

Aby uzyskać spójne formatowanie dla %A specyfikatorów formatu i %O , połącz użycie funkcji StructuredFormatDisplay z przesłonięciem ToStringelementu . Przykład:

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

    override _.ToString() = "Custom ToString"

Ocenianie następujących definicji

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

wyświetla tekst

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

Użycie StructuredFormatDisplay z właściwością nośną DisplayText oznacza fakt, że myRec jest to typ rekordu strukturalnego jest ignorowany podczas drukowania strukturalnego, a zastąpienie elementu jest preferowane ToString() we wszystkich okolicznościach.

Implementację interfejsu System.IFormattable można dodać do dalszego dostosowywania w przypadku specyfikacji formatu platformy .NET.

Interaktywne drukowanie strukturalne języka F#

Język F# Interactive (dotnet fsi) używa rozszerzonej wersji formatowania zwykłego tekstu do wartości raportu i umożliwia dodatkowe dostosowywanie. Aby uzyskać więcej informacji, zobacz F# Interactive.

Dostosowywanie wyświetlania debugowania

Debugery dla platformy .NET szanują użycie atrybutów, takich jak DebuggerDisplay i DebuggerTypeProxy, i mają wpływ na ustrukturyzowane wyświetlanie obiektów w oknach inspekcji debugera. Kompilator języka F# automatycznie wygenerował te atrybuty dla typów związków i rekordów dyskryminowanych, ale nie klas, interfejsów lub typów struktur.

Te atrybuty są ignorowane w formatowaniu zwykłego tekstu języka F#, ale może być przydatne zaimplementowanie tych metod w celu ulepszenia wyświetlania podczas debugowania typów języka F#.

Zobacz też