Mise en forme en texte brut

F# prend en charge la mise en forme vérifiée par type du texte brut à l’aide de printf, printfn, sprintf et des fonctions associées. Par exemple,

dotnet fsi

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

génère la sortie

Hello world, 2 + 2 is 4

F# permet également de mettre en forme des valeurs structurées en texte brut. L’exemple suivant met en forme la sortie sous la forme d’un affichage de tuples de type 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 mise en forme structurée de texte brut est activée quand vous utilisez le format %A dans les chaînes de mise en forme printf. Elle est également activée lors de la mise en forme de la sortie des valeurs en F# Interactive, où la sortie inclut des informations supplémentaires et est également personnalisable. La mise en forme de texte brut est également observable via tous les appels à x.ToString() sur les valeurs d’union et d’enregistrement F#, notamment celles qui se produisent implicitement dans le débogage, la journalisation et d’autres outils.

Vérification des chaînes au format printf

Une erreur de compilation est signalée si une fonction de mise en forme printf est utilisée avec un argument qui ne correspond pas aux spécificateurs de format printf dans la chaîne de format. Par exemple,

sprintf "Hello %s" (2+2)

génère la sortie

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

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

Techniquement parlant, quand vous utilisez printf et d’autres fonctions associées, une règle spéciale du compilateur F# vérifie le littéral de chaîne transmis en tant que chaîne de format. Cela permet de s’assurer que les arguments suivants appliqués sont du type approprié pour correspondre aux spécificateurs de format utilisés.

Spécificateurs de format pour printf

Les spécifications de format pour les formats printf sont des chaînes avec des marqueurs % qui indiquent le format. Les espaces réservés de format se composent de %[flags][width][.precision][type] où le type est interprété comme suit :

Spécificateur de format Type(s) Notes
%b bool (System.Boolean) Mis en forme en tant que true ou false
%s string (System.String) Mise en forme en tant que contenu sans séquence d’échappement
%c char (System.Char) Mise en forme en tant que littéral de caractère
%d, %i type entier de base Mis en forme comme un entier décimal, signé si le type d'entier de base est signé
%u type entier de base Mise en forme en tant qu’entier décimal non signé
%x, %X type entier de base Mise en forme en tant que nombre hexadécimal non signé (a-f ou A-F pour les chiffres hexadécimaux respectivement)
%o type entier de base Mise en forme en tant que nombre octal non signé
%B type entier de base Mise en forme en tant que nombre binaire non signé
%e, %E type à virgule flottante de base Mise en forme en tant que valeur signée au format [-]d.dddde[sign]ddd où d est un chiffre décimal unique, dddd est un ou plusieurs chiffres décimaux, ddd est exactement trois chiffres décimaux et le signe est + ou -
%f, %F type à virgule flottante de base Mise en forme en tant que valeur signée au format [-]dddd.dddd, où dddd est un ou plusieurs chiffres décimaux. Le nombre de chiffres avant la virgule décimale dépend de l'ampleur du nombre, et le nombre de chiffres après que la virgule décimale dépend de la précision demandée.
%g, %G type à virgule flottante de base Mise en forme en tant que valeur signée imprimée au format %f ou %e, selon celui qui est le plus compact pour la valeur et la précision données.
%M Valeur de type decimal (System.Decimal) Mis en forme à l’aide du spécificateur de format "G" pour System.Decimal.ToString(format)
%O toute valeur Mise en forme à l’aide du boxing de l’objet et l’appel à sa méthode System.Object.ToString()
%A toute valeur Mise en forme à l’aide de la mise en forme structurée de texte brut avec les paramètres de disposition par défaut
%a toute valeur Nécessite deux arguments : une fonction de mise en forme acceptant un paramètre de contexte et la valeur, et la valeur particulière à imprimer
%t toute valeur Nécessite un argument : une fonction de mise en forme acceptant un paramètre de contexte qui génère ou retourne le texte approprié
%% (aucun) Ne nécessite aucun argument et imprime un signe de pourcentage simple : %

Voici les types entiers de base : 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) et unativeint (System.UIntPtr). Voici les types à virgule flottante de base : float (System.Double), float32 (System.Single) et decimal (System.Decimal).

La largeur facultative est un entier indiquant la largeur minimale du résultat. Par exemple, %6d imprime un entier, en ajoutant en préfixe des espaces pour remplir au moins six caractères. Si width est définie sur *, un argument entier supplémentaire est pris pour spécifier la largeur correspondante.

Voici les indicateurs valides :

Indicateur Effet
0 Ajouter des zéros à la place des espaces pour constituer la largeur requise
- Justifier à gauche le résultat dans le cadre de la largeur spécifiée
+ Ajouter un caractère + si le nombre est positif (un signe - correspond aux nombres négatifs)
Espace Ajouter un caractère supplémentaire si le nombre est positif (un signe « - » correspond aux nombres négatifs)

L’indicateur printf # n’est pas valide et une erreur de compilation est signalée s’il est utilisé.

Les valeurs sont mises en forme à l’aide d’une culture invariante. Les paramètres de culture ne sont pas pertinents pour la mise en forme printf, sauf s’ils affectent les résultats de mise en forme %O et %A. Pour plus d’informations, consultez Mise en forme structurée de texte brut.

Mise en forme %A

Le spécificateur de format %A est utilisé pour mettre en forme des valeurs de manière lisible par les êtres humains. Il est également utile pour la création d’informations de diagnostic.

Valeurs primitives

Lors de la mise en forme du texte brut à l’aide du spécificateur %A, les valeurs numériques F# sont mises en forme avec leur suffixe et leur culture invariante. Les valeurs à virgule flottante sont mises en forme à l’aide de 10 emplacements de précision de virgule flottante. Par exemple,

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

produit

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

Quand vous utilisez le spécificateur %A, les chaînes sont mises en forme à l’aide de guillemets. Les codes d’échappement ne sont pas ajoutés et les caractères bruts sont imprimés à la place. Par exemple,

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

produit

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

Valeurs .NET

Lors de la mise en forme du texte brut à l’aide du spécificateur %A, les objets .NET non F# sont mis en forme à l’aide de x.ToString() avec les paramètres par défaut de .NET déterminés par System.Globalization.CultureInfo.CurrentCulture et System.Globalization.CultureInfo.CurrentUICulture. Par exemple,

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

produit

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

Valeurs structurées

Lors de la mise en forme du texte brut à l’aide du spécificateur %A, la mise en retrait en bloc est utilisée pour les listes et les tuples F#. Cette caractéristique apparaît dans l'exemple précédent. La structure des tableaux est également utilisée, notamment les tableaux multidimensionnels. Les tableaux unidimensionnels sont affichés avec la syntaxe [| ... |]. Par exemple,

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

produit

[|(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 largeur d’impression est définie par défaut sur 80. Cette largeur peut être personnalisée via une largeur d’impression dans le spécificateur de format. Par exemple,

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

produit

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

Si vous spécifiez une largeur d’impression sur 0, aucune largeur d’impression n’est utilisée. Le résultat comprend une ligne de texte unique, sauf si les chaînes incorporées dans la sortie contiennent des sauts de ligne. Par exemple

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

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

produit

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

Une limite de profondeur de 4 est utilisée pour les valeurs de séquence (IEnumerable) qui sont affichées en tant que seq { ...}. Une limite de profondeur de 100 est utilisée pour les valeurs de liste et de tableau. Par exemple,

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

produit

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

La mise en retrait en bloc est également utilisée pour la structure des valeurs d’enregistrement public et d’union. Par exemple,

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

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

produit

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

Si %+A est utilisé, la structure privée des enregistrements et des unions est également révélée à l’aide de la réflexion. Par exemple

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

produit

external view:
R

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

Valeurs volumineuses, cycliques ou profondément imbriquées

Les valeurs structurées volumineuses sont mises en forme avec un total de nombre de nœuds d’objets maximal de 10 000. Les valeurs profondément imbriquées sont mises en forme à une profondeur de 100. Dans les deux cas, ... est utilisé pour omettre une partie de la sortie. Par exemple,

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)

produit une sortie volumineuse avec certaines parties supprimées :

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

Les cycles sont détectés dans les graphiques d’objets et ... est utilisé quand des cycles sont détectés. Par exemple

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

produit

{ Links = [...] }

Valeurs différées, nulles et de fonction

Les valeurs différées sont imprimées en tant que Value is not created ou texte équivalent quand la valeur n’a pas encore été évaluée.

Les valeurs nulles sont imprimées en tant que null, sauf si le type statique de la valeur est déterminé comme étant un type d’union où null est une représentation autorisée.

Les valeurs de fonction F# sont imprimées en tant que nom de fermeture généré en interne, par exemple <fun:it@43-7>.

Personnaliser la mise en forme de texte brut avec StructuredFormatDisplay

Lors de l’utilisation du spécificateur %A, la présence de l’attribut StructuredFormatDisplay sur les déclarations de type est respectée. Il permet de spécifier le texte de substitution et la propriété pour afficher une valeur. Par exemple :

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

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

produit

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

Personnaliser la mise en forme de texte brut en écrasant ToString

L’implémentation par défaut de ToString est observable dans la programmation F#. Les résultats par défaut ne sont généralement pas adaptés à une utilisation dans les informations affichées au programmeur ni dans la sortie utilisateur. L’implémentation par défaut est donc souvent écrasée.

Par défaut, les types d’enregistrement F# et d’union remplacent l’implémentation de ToString par une implémentation qui utilise sprintf "%+A". Par exemple,

type Counts = { Clicks:int list }

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

produit

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

Pour les types de classes, aucune implémentation par défaut de ToString n’est fournie et la valeur .NET par défaut est utilisée, qui signale le nom du type. Par exemple,

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

produit

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

L’ajout d’un remplacement pour ToString permet d’obtenir une meilleure mise en forme.

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

produit

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

Personnaliser la mise en forme de texte brut avec StructuredFormatDisplay et ToString

Pour obtenir une mise en forme cohérente des spécificateurs de format %A et %O, combinez l’utilisation de StructuredFormatDisplay et un remplacement de ToString. Par exemple,

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

    override _.ToString() = "Custom ToString"

Évaluation des définitions suivantes

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

donne le texte

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’utilisation de StructuredFormatDisplay avec la propriété DisplayText annexe signifie que myRec est un type d’enregistrement structurel qui est ignoré lors de l’impression structurée et que le remplacement de ToString() est préférable dans toutes les circonstances.

Une implémentation de l’interface System.IFormattable peut être ajoutée pour augmenter le niveau de personnalisation en présence de spécifications au format .NET.

Impression structurée en F# Interactive

F# Interactive (dotnet fsi) utilise une version étendue de la mise en forme structurée de texte brut pour signaler les valeurs et permet une personnalisation supplémentaire. Pour plus d’informations, consultez F# Interactive.

Personnaliser les affichages de débogage

Les débogueurs pour .NET respectent l’utilisation d’attributs comme DebuggerDisplay et DebuggerTypeProxy. Ceux-ci affectent l’affichage structuré des objets dans les fenêtres d’inspection du débogueur. Le compilateur F# a généré ces attributs automatiquement pour les types d’enregistrement et d’union discriminés, mais pas pour les types de classe, d’interface ni struct.

Ces attributs sont ignorés dans la mise en forme de texte brut F#. Toutefois, il peut être utile d’implémenter ces méthodes pour améliorer les affichages lors du débogage des types F#.

Voir aussi