Dela via


Oformaterad textformatering

F# stöder typkontrollerad formatering av oformaterad text med hjälp av printf, printfn, sprintfoch relaterade funktioner. Exempel:

dotnet fsi

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

ger utdata

Hello world, 2 + 2 is 4

F# tillåter också att strukturerade värden formateras som oformaterad text. Tänk dig till exempel följande exempel som formaterar utdata som en matrisliknande visning av tupplar.

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

Strukturerad oformaterad textformatering aktiveras när du använder %A formatet i printf formateringssträngar. Det aktiveras också när du formaterar utdata från värden i interaktiv F# där utdata innehåller extra information och dessutom kan anpassas. Oformaterad textformatering kan också observeras genom alla anrop till x.ToString() på F#-union- och postvärden, inklusive de som sker implicit vid felsökning, loggning och andra verktyg.

Kontroll av printfsträngar i -format

Ett kompileringsfel rapporteras om en printf formateringsfunktion används med ett argument som inte matchar formatspecificerarna i formatsträngen. Exempel:

sprintf "Hello %s" (2+2)

ger utdata

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

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

Tekniskt sett, när du använder printf och andra relaterade funktioner, kontrollerar en särskild regel i F#-kompilatorn strängliteralen som skickas som formatsträngen, vilket säkerställer att de efterföljande argumenten som tillämpas är av rätt typ för att matcha formatspecificerarna som används.

Formatera specificerare för printf

Formatspecifikationer för printf format är strängar med % markörer som anger format. Platshållarna för format består av %[flags][width][.precision][type] där typen tolkas på följande sätt:

Formatspecificerare Typ(er) Kommentarer
%b bool (System.Boolean) Formaterad som true eller false
%s string (System.String) Formaterat som dess ej inkapslade innehåll
%c char (System.Char) Formaterad som teckenliteral
%d, %i en grundläggande heltalstyp Formaterat som ett decimal heltal, signerat om den grundläggande heltalstypen är signerad
%u en grundläggande heltalstyp Formaterat som ett intesignerat decimal heltal
%x, %X en grundläggande heltalstyp Formaterat som ett osignerat hexadecimalt tal (a-f eller A-F för hexsiffror respektive)
%o en grundläggande heltalstyp Formaterat som ett osignerat oktalt tal
%B en grundläggande heltalstyp Formaterat som ett osignerat binärt tal
%e, %E en grundläggande flyttalstyp Formaterat som ett signerat värde med formuläret [-]d.dddde[sign]ddd där d är en enda decimalsiffra, dddd är en eller flera decimaler, ddd är exakt tre decimaltal och tecken är + eller -
%f, %F en grundläggande flyttalstyp Formaterat som ett signerat värde med formuläret [-]dddd.dddd, där dddd är en eller flera decimalsiffror. Antalet siffror före decimaltecknet beror på talets storlek och antalet siffror efter decimaltecknet beror på den begärda precisionen.
%g, %G en grundläggande flyttalstyp Formaterat med ett signerat värde som skrivs ut i %f eller %e format, beroende på vilket som är mer kompakt för det angivna värdet och precisionen.
%M a decimal (System.Decimal) värde Formaterad med formatspecificeraren "G" för System.Decimal.ToString(format)
%O valfritt värde Formaterad genom att boxa objektet och anropa dess System.Object.ToString() metod
%A valfritt värde Formaterad med strukturerad oformaterad textformatering med standardlayoutinställningarna
%a valfritt värde Kräver två argument: en formateringsfunktion som accepterar en kontextparameter och värdet och det specifika värde som ska skrivas ut
%t valfritt värde Kräver ett argument: en formateringsfunktion som accepterar en kontextparameter som antingen matar ut eller returnerar lämplig text
%% (inget) Kräver inga argument och skriver ut ett vanligt procenttecken: %

Grundläggande heltalstyper är 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) och unativeint (System.UIntPtr). Grundläggande flyttalstyper är float (System.Double), float32 (System.Single) och decimal (System.Decimal).

Den valfria bredden är ett heltal som anger resultatets minimala bredd. Skriver till exempel %6d ut ett heltal och prefixerar det med blanksteg för att fylla minst sex tecken. Om bredden är *används ett extra heltalsargument för att ange motsvarande bredd.

Giltiga flaggor är:

Flagga Effekt
0 Lägg till nollor i stället för blanksteg för att skapa den bredd som krävs
- Vänsterjustera resultatet inom den angivna bredden
+ Lägg till ett + tecken om talet är positivt (för att matcha ett - tecken på negativa värden)
blankstegstecken Lägg till ett extra utrymme om talet är positivt (för att matcha ett "-"-tecken för negativa värden)

Utskriftsflaggan # är ogiltig och ett kompileringsfel rapporteras om den används.

Värden formateras med hjälp av invariant kultur. Kulturinställningar är irrelevanta printf för formatering, förutom när de påverkar resultatet av %O och %A formateringen. Mer information finns i formaterad oformaterad text.

%A Formatering

Formatspecificeraren %A används för att formatera värden på ett sätt som kan läsas av människor och kan också vara användbart för rapportering av diagnostikinformation.

Primitiva värden

När du formaterar oformaterad text med hjälp av %A specificeraren formateras numeriska F#-värden med deras suffix och invarianta kultur. Flyttalsvärden formateras med 10 flyttalsprecisionsplatser. Exempel:

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

Producerar

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

När du använder specificeraren %A formateras strängar med citattecken. Escape-koder läggs inte till och i stället skrivs de råa tecknen ut. Exempel:

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

Producerar

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

.NET-värden

När du formaterar oformaterad text med hjälp %A av specificeraren formateras .NET-objekt som inte är F# med hjälp x.ToString() av standardinställningarna för .NET som anges av System.Globalization.CultureInfo.CurrentCulture och System.Globalization.CultureInfo.CurrentUICulture. Exempel:

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

Producerar

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

Strukturerade värden

När du formaterar oformaterad text med hjälp av %A specificeraren används block indrag för F#-listor och tupplar. Detta visas i föregående exempel. Matrisernas struktur används också, inklusive flerdimensionella matriser. Endimensionella matriser visas med [| ... |] syntax. Exempel:

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

Producerar

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

Standardbredden för utskrift är 80. Den här bredden kan anpassas med hjälp av en utskriftsbredd i formatspecificeraren. Exempel:

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

Producerar

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

Om du anger utskriftsbredden 0 används ingen utskriftsbredd. En enda textrad resulterar, förutom där inbäddade strängar i utdata innehåller radbrytningar. Till exempel

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

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

Producerar

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

En djupgräns på 4 används för sekvensvärden (IEnumerable) som visas som seq { ...}. En djupgräns på 100 används för list- och matrisvärden. Exempel:

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

Producerar

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

Block indrag används också för strukturen för offentliga poster och fackliga värden. Exempel:

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

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

Producerar

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

Om %+A används avslöjas också den privata strukturen för poster och fackföreningar med hjälp av reflektion. Till exempel

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

Producerar

external view:
R

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

Stora, cykliska eller djupt kapslade värden

Stora strukturerade värden formateras till ett maximalt totalt antal objektnoder på 1 0000. Djupt kapslade värden formateras till ett djup på 100. I båda fallen ... används för att elide några av utdata. Exempel:

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)

genererar stora utdata med vissa delar elide:

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

Cykler identifieras i objektdiagram och ... används på platser där cykler identifieras. Till exempel

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

Producerar

{ Links = [...] }

Lata värden, null- och funktionsvärden

Lata värden skrivs ut som Value is not created eller motsvarande text när värdet ännu inte har utvärderats.

Null-värden skrivs ut som null om inte den statiska typen av värdet bestäms vara en unionstyp där null är en tillåten representation.

F#-funktionsvärden skrivs ut som deras internt genererade stängningsnamn, till exempel <fun:it@43-7>.

Anpassa oformaterad textformatering med StructuredFormatDisplay

När du använder %A specificeraren respekteras förekomsten av StructuredFormatDisplay attributet för typdeklarationer. Detta kan användas för att ange surrogattext och egenskap för att visa ett värde. Till exempel:

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

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

Producerar

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

Anpassa oformaterad textformatering genom att åsidosätta ToString

Standardimplementeringen av ToString kan observeras i F#-programmering. Standardresultaten är ofta inte lämpliga för användning i antingen programmerriktad informationsvisning eller användarutdata, och därför är det vanligt att åsidosätta standardimplementeringen.

Som standard åsidosätter F#-post- och unionstyper implementeringen av ToString med en implementering som använder sprintf "%+A". Exempel:

type Counts = { Clicks:int list }

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

Producerar

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

För klasstyper tillhandahålls ingen standardimplementering av ToString och .NET-standardvärdet används, vilket rapporterar namnet på typen. Exempel:

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

Producerar

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

Att lägga till en åsidosättning för ToString kan ge bättre formatering.

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

Producerar

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

Anpassa oformaterad textformatering med StructuredFormatDisplay och ToString

Om du vill uppnå konsekvent formatering för %A och %O formatspecificerare kombinerar du användningen av StructuredFormatDisplay med en åsidosättning av ToString. Exempel:

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

    override _.ToString() = "Custom ToString"

Utvärdera följande definitioner

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

ger texten

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

Användningen av StructuredFormatDisplay med den stödjande DisplayText egenskapen innebär att myRec är en strukturell posttyp ignoreras under strukturerad utskrift, och åsidosättningen av ToString() föredras under alla omständigheter.

En implementering av System.IFormattable gränssnittet kan läggas till för ytterligare anpassning i närvaro av .NET-formatspecifikationer.

Interaktiv strukturerad utskrift i F#

F# Interactive (dotnet fsi) använder en utökad version av strukturerad oformaterad textformatering för att rapportera värden och möjliggör ytterligare anpassning. Mer information finns i F# Interactive.

Anpassa felsökningsskärmar

Felsökningsprogram för .NET respekterar användningen av attribut som DebuggerDisplay och DebuggerTypeProxy, och dessa påverkar den strukturerade visningen av objekt i felsökningsgranskningsfönster. F#-kompilatorn genererade automatiskt dessa attribut för diskriminerade union- och posttyper, men inte klass-, gränssnitts- eller struct-typer.

Dessa attribut ignoreras i Oformaterad F#-formatering, men det kan vara användbart att implementera dessa metoder för att förbättra visning vid felsökning av F#-typer.

Se även