Formattazione testo normale

F# supporta la formattazione con controllo dei tipi di testo normale usando printfle funzioni correlate , printfnsprintf, e . ad esempio:

dotnet fsi

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

restituisce l'output

Hello world, 2 + 2 is 4

F# consente anche di formattare i valori strutturati come testo normale. Si consideri ad esempio l'esempio seguente che formatta l'output come visualizzazione di tuple di tipo matrice.

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

La formattazione testo normale strutturato viene attivata quando si usa il %A formato nelle printf stringhe di formattazione. Viene inoltre attivato quando si formatta l'output dei valori in F# interactive, in cui l'output include informazioni aggiuntive ed è anche personalizzabile. La formattazione del testo normale è osservabile anche tramite qualsiasi chiamata a x.ToString() su valori di unione e record F#, inclusi quelli che si verificano in modo implicito nel debug, nella registrazione e in altri strumenti.

Verifica delle printfstringhe in formato

Se una funzione di formattazione viene usata con un argomento che non corrisponde agli identificatori di formato printf nella stringa di formato, verrà segnalato printf un errore in fase di compilazione. ad esempio:

sprintf "Hello %s" (2+2)

restituisce l'output

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

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

Tecnicamente, quando si usano printf e altre funzioni correlate, una regola speciale nel compilatore F# controlla il valore letterale stringa passato come stringa di formato, assicurando che gli argomenti successivi applicati siano del tipo corretto in modo che corrispondano agli identificatori di formato usati.

Identificatori di formato per printf

Le specifiche di formato per printf i formati sono stringhe con % marcatori che indicano il formato. I segnaposto di formato sono costituiti da %[flags][width][.precision][type] dove il tipo viene interpretato come segue:

Identificatore di formato Tipi Osservazioni:
%b bool (System.Boolean) Formattato come true o false
%s string (System.String) Formattato come contenuto senza caratteri di escape
%c char (System.Char) Formattato come valore letterale carattere
%d, %i un tipo integer di base Formattato come intero decimale, con segno se il tipo integer di base è con segno
%u un tipo integer di base Formattato come intero decimale senza segno
%x, %X un tipo integer di base Formattato come numero esadecimale senza segno (rispettivamente a-f o A-F per le cifre esadecimali)
%o un tipo integer di base Formattato come numero ottale senza segno
%B un tipo integer di base Formattato come numero binario senza segno
%e, %E un tipo a virgola mobile di base Formattato come valore con segno con il formato [-]d.dddde[sign]ddd in cui d è una singola cifra decimale, dddd è una o più cifre decimali, ddd è esattamente tre cifre decimali e il segno è + o -
%f, %F un tipo a virgola mobile di base Formattato come valore con segno con il formato [-]dddd.dddd, dove dddd è una o più cifre decimali. Il numero di cifre prima del separatore decimale dipende dalla grandezza del numero e il numero di cifre dopo il separatore decimale dipende dalla precisione richiesta.
%g, %G un tipo a virgola mobile di base Formattato utilizzando come valore firmato stampato in %f o %e in formato, a qualsiasi valore più compatto per il valore e la precisione specificati.
%M valore a decimal (System.Decimal) Formattato usando l'identificatore "G" di formato per System.Decimal.ToString(format)
%O qualsiasi valore Formattato eseguendo il boxing dell'oggetto e chiamando il relativo System.Object.ToString() metodo
%A qualsiasi valore Formattazione con formattazione testo normale strutturato con le impostazioni di layout predefinite
%a qualsiasi valore Richiede due argomenti: una funzione di formattazione che accetta un parametro di contesto e il valore e il valore specifico da stampare
%t qualsiasi valore Richiede un argomento: una funzione di formattazione che accetta un parametro di contesto che restituisce o restituisce il testo appropriato
%% (nessuno) Non richiede argomenti e stampa un segno di percentuale normale: %

I tipi Integer di base sono byte (System.Byte), sbyte (System.SByte), int16 (System.Int16), uint16 (System.UInt16), int32 (), uint32 (System.Int32System.UInt32), int64 (System.Int64), uint64 (System.UInt64), (System.IntPtr) nativeint e unativeint (System.UIntPtr). I tipi a virgola mobile di base sono float (System.Double), float32 (System.Single) e decimal (System.Decimal).

La larghezza facoltativa è un numero intero che indica la larghezza minima del risultato. Ad esempio, %6d stampa un numero intero, anteponendolo a spazi per riempire almeno sei caratteri. Se width è *, viene preso un argomento integer aggiuntivo per specificare la larghezza corrispondente.

I flag validi sono:

Flag Effetto
0 Aggiungere zeri invece di spazi per creare la larghezza necessaria
- Giustifica a sinistra il risultato all'interno della larghezza specificata
+ Aggiungere un + carattere se il numero è positivo (per trovare una corrispondenza con un - segno di negativi)
carattere spazio Aggiungere uno spazio aggiuntivo se il numero è positivo (per trovare una corrispondenza con un segno '-' per i negativi)

Il flag printf # non è valido e verrà segnalato un errore in fase di compilazione se viene usato.

I valori vengono formattati usando impostazioni cultura invarianti. Le impostazioni cultura sono irrilevanti per printf la formattazione tranne quando influiscono sui risultati di %O e %A la formattazione. Per altre informazioni, vedere Formattazione di testo normale strutturato.

%A Formattazione

L'identificatore %A di formato viene usato per formattare i valori in modo leggibile e può essere utile anche per la creazione di report sulle informazioni di diagnostica.

Valori primitivi

Quando si formatta testo normale usando l'identificatore %A , i valori numerici F# vengono formattati con il relativo suffisso e le impostazioni cultura invarianti. I valori a virgola mobile vengono formattati usando 10 posizioni di precisione a virgola mobile. ad esempio:

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

Produce

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

Quando si usa l'identificatore %A , le stringhe vengono formattate usando virgolette. I codici di escape non vengono aggiunti e vengono stampati i caratteri non elaborati. ad esempio:

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

Produce

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

Valori .NET

Quando si formatta testo normale usando l'identificatore %A , gli oggetti .NET non F# vengono formattati usando x.ToString() le impostazioni predefinite di .NET specificate da System.Globalization.CultureInfo.CurrentCulture e System.Globalization.CultureInfo.CurrentUICulture. ad esempio:

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

Produce

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

Valori strutturati

Quando si formatta testo normale usando l'identificatore %A , il rientro a blocchi viene usato per gli elenchi e le tuple F#. come illustrato nell'esempio precedente. Viene usata anche la struttura delle matrici, incluse matrici multidimensionali. Le matrici unidimensionali vengono visualizzate con [| ... |] la sintassi. ad esempio:

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

Produce

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

La larghezza di stampa predefinita è 80. Questa larghezza può essere personalizzata utilizzando una larghezza di stampa nell'identificatore di formato. ad esempio:

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

Produce

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

Se si specifica una larghezza di stampa pari a 0, non viene utilizzata alcuna larghezza di stampa. Verrà restituita una singola riga di testo, ad eccezione della posizione in cui le stringhe incorporate nell'output contengono interruzioni di riga. Ad esempio:

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

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

Produce

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

Per i valori di sequenza (IEnumerable) viene usato un limite di profondità pari a 4, che vengono visualizzati come seq { ...}. Per i valori di elenco e matrice viene usato un limite di profondità di 100. ad esempio:

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

Produce

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

Il rientro a blocchi viene usato anche per la struttura dei valori di unione e record pubblici. ad esempio:

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

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

Produce

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

Se %+A viene utilizzata, la struttura privata dei record e delle unioni viene anche rilevata tramite reflection. Ad esempio:

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

Produce

external view:
R

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

Valori grandi, ciclici o annidati in modo profondo

I valori strutturati di grandi dimensioni vengono formattati in un numero massimo di nodi oggetto complessivo pari a 10000. I valori annidati in modo profondo vengono formattati con una profondità di 100. In entrambi i casi ... viene usato per elidere parte dell'output. ad esempio:

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)

produce un output di grandi dimensioni con alcune parti elide:

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

I cicli vengono rilevati nei grafici degli oggetti e ... vengono usati in posizioni in cui vengono rilevati cicli. Ad esempio:

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

Produce

{ Links = [...] }

Valori di funzione lazy, null e

I valori lazy vengono stampati come Value is not created testo equivalente o quando il valore non è ancora stato valutato.

I valori Null vengono stampati come null a meno che il tipo statico del valore non sia determinato come tipo di unione in cui null è una rappresentazione consentita.

I valori della funzione F# vengono stampati come nome di chiusura generato internamente, ad esempio <fun:it@43-7>.

Personalizzare la formattazione del testo normale con StructuredFormatDisplay

Quando si usa l'identificatore %A , viene rispettata la presenza dell'attributo StructuredFormatDisplay nelle dichiarazioni di tipo. Può essere usato per specificare testo surrogato e proprietà per visualizzare un valore. Ad esempio:

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

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

Produce

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

Personalizzare la formattazione del testo normale eseguendo l'override ToString

L'implementazione predefinita di ToString è osservabile nella programmazione F#. Spesso, i risultati predefiniti non sono adatti per l'uso nella visualizzazione delle informazioni rivolte ai programmatori o nell'output dell'utente e di conseguenza è comune eseguire l'override dell'implementazione predefinita.

Per impostazione predefinita, i tipi di record e unione F# eseguono l'override dell'implementazione di ToString con un'implementazione che usa sprintf "%+A". ad esempio:

type Counts = { Clicks:int list }

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

Produce

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

Per i tipi di classe, non viene fornita alcuna implementazione predefinita di ToString e viene usata l'impostazione predefinita .NET, che indica il nome del tipo. ad esempio:

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

Produce

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

L'aggiunta di un override per ToString può offrire una formattazione migliore.

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

Produce

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

Personalizzare la formattazione di testo normale con StructuredFormatDisplay e ToString

Per ottenere una formattazione coerente per %A gli identificatori di formato e %O , combinare l'uso di StructuredFormatDisplay con un override di ToString. ad esempio:

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

    override _.ToString() = "Custom ToString"

Valutazione delle definizioni seguenti

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

fornisce il testo

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

L'utilizzo di StructuredFormatDisplay con la proprietà di supporto DisplayText indica il fatto che myRec è un tipo di record strutturale viene ignorato durante la stampa strutturata e l'override di ToString() è preferibile in tutte le circostanze.

È possibile aggiungere un'implementazione dell'interfaccia System.IFormattable per un'ulteriore personalizzazione in presenza di specifiche di formato .NET.

Stampa strutturata interattiva F#

F# Interactive (dotnet fsi) usa una versione estesa della formattazione testo normale strutturato per i valori del report e consente una personalizzazione aggiuntiva. Per altre informazioni, vedere F# Interactive.

Personalizzare le visualizzazioni di debug

I debugger per .NET rispettano l'uso di attributi come DebuggerDisplay e DebuggerTypeProxye e questi influiscono sulla visualizzazione strutturata degli oggetti nelle finestre di ispezione del debugger. Il compilatore F# ha generato automaticamente questi attributi per tipi di unione e record discriminati, ma non per tipi di classe, interfaccia o struct.

Questi attributi vengono ignorati nella formattazione di testo normale F#, ma può essere utile implementare questi metodi per migliorare le visualizzazioni durante il debug dei tipi F#.

Vedi anche