Partilhar via


Formatação de texto simples

O F# suporta formatação verificada por tipo de texto sem formatação usando printf, printfn, sprintfe funções relacionadas. Por exemplo,

dotnet fsi

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

dá a saída

Hello world, 2 + 2 is 4

F# também permite que valores estruturados sejam formatados como texto sem formatação. Por exemplo, considere o exemplo a seguir que formata a saída como uma exibição de tuplas semelhante a uma matriz.

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

A formatação de texto sem formatação estruturado é ativada quando você usa o %A formato em printf cadeias de caracteres de formatação. Ele também é ativado ao formatar a saída de valores em F# interativo, onde a saída inclui informações extras e é adicionalmente personalizável. A formatação de texto simples também é observável por meio de quaisquer chamadas para x.ToString() valores de união e registro em F#, incluindo aqueles que ocorrem implicitamente na depuração, registro em log e outras ferramentas.

Verificação de cadeias de caracteres - printfformat

Um erro em tempo de compilação será relatado se uma printf função de formatação for usada com um argumento que não corresponda aos especificadores de formato printf na cadeia de caracteres de formato. Por exemplo,

sprintf "Hello %s" (2+2)

dá a saída

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

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

Tecnicamente falando, ao usar printf e outras funções relacionadas, uma regra especial no compilador F# verifica o literal de cadeia de caracteres passado como a cadeia de caracteres de formato, garantindo que os argumentos subsequentes aplicados sejam do tipo correto para corresponder aos especificadores de formato usados.

Especificadores de formato para printf

As especificações de formato para printf formatos são cadeias de caracteres com % marcadores que indicam o formato. Os espaços reservados de %[flags][width][.precision][type] formato consistem em onde o tipo é interpretado da seguinte forma:

Especificador de formato Tipo(s) Observações
%b bool (System.Boolean) Formatado como true ou false
%s string (System.String) Formatado como seu conteúdo sem escape
%c char (System.Char) Formatado como o literal do caractere
%d, %i um tipo de inteiro básico Formatado como um inteiro decimal, assinado se o tipo de inteiro básico estiver assinado
%u um tipo de inteiro básico Formatado como um inteiro decimal não assinado
%x, %X um tipo de inteiro básico Formatado como um número hexadecimal não assinado (a-f ou A-F para dígitos hexadecimais, respectivamente)
%o um tipo de inteiro básico Formatado como um número octal não assinado
%B um tipo de inteiro básico Formatado como um número binário não assinado
%e, %E um tipo básico de ponto flutuante Formatado como um valor assinado com a forma [-]d.dddde[sign]ddd onde d é um único dígito decimal, dddd é um ou mais dígitos decimais, ddd é exatamente três dígitos decimais e sinal é + ou -
%f, %F um tipo básico de ponto flutuante Formatado como um valor assinado com o formulário [-]dddd.dddd, onde dddd é um ou mais dígitos decimais. O número de dígitos antes da vírgula decimal depende da magnitude do número, e o número de dígitos após a vírgula decimal depende da precisão solicitada.
%g, %G um tipo básico de ponto flutuante Formatado usando como um valor assinado impresso em %f ou %e formato, o que for mais compacto para o valor e precisão fornecidos.
%M a decimal (System.Decimal) valor Formatado usando o especificador de "G" formato para System.Decimal.ToString(format)
%O qualquer valor Formatado encaixotando o objeto e chamando seu System.Object.ToString() método
%A qualquer valor Formatado usando formatação de texto sem formatação estruturada com as configurações de layout padrão
%a qualquer valor Requer dois argumentos: uma função de formatação que aceita um parâmetro de contexto e o valor, e o valor específico para imprimir
%t qualquer valor Requer um argumento: uma função de formatação que aceita um parâmetro de contexto que produz ou retorna o texto apropriado
%% (nenhum) Não requer argumentos e imprime um sinal de porcentagem simples: %

Os tipos inteiros básicos são 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 (), unativeint eSystem.IntPtr (System.UIntPtr). Os tipos básicos de ponto flutuante são float (System.Double), float32 (System.Single) e decimal (System.Decimal).

A largura opcional é um inteiro que indica a largura mínima do resultado. Por exemplo, %6d imprime um inteiro, prefixando-o com espaços para preencher pelo menos seis caracteres. Se width for *, então um argumento inteiro extra será usado para especificar a largura correspondente.

As bandeiras válidas são:

Sinalizador Efeito
0 Adicione zeros em vez de espaços para compor a largura necessária
- Esquerda justificar o resultado dentro da largura especificada
+ Adicionar um + caractere se o número for positivo (para corresponder a um - sinal para negativos)
caractere de espaço Adicione um espaço extra se o número for positivo (para corresponder a um sinal '-' para negativos)

O sinalizador printf # é inválido e um erro em tempo de compilação será relatado se for usado.

Os valores são formatados usando cultura invariante. As configurações de cultura são irrelevantes para a printf formatação, exceto quando afetam os resultados e %O%A a formatação. Para obter mais informações, consulte Formatação de texto sem formatação estruturada.

%A formatação

O %A especificador de formato é usado para formatar valores de forma legível por humanos e também pode ser útil para relatar informações de diagnóstico.

Valores primitivos

Ao formatar texto sem formatação usando o especificador, os valores numéricos F# são formatados com seu sufixo %A e cultura invariante. Os valores de ponto flutuante são formatados usando 10 locais de precisão de ponto flutuante. Por exemplo,

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

produz

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

Ao usar o %A especificador, as cadeias de caracteres são formatadas usando aspas. Os códigos de escape não são adicionados e, em vez disso, os caracteres brutos são impressos. Por exemplo,

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

produz

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

Valores .NET

Ao formatar texto sem formatação usando o %A especificador, objetos .NET não F# são formatados usando x.ToString() as configurações padrão do .NET fornecidas por System.Globalization.CultureInfo.CurrentCulture e System.Globalization.CultureInfo.CurrentUICulture. Por exemplo,

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

produz

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

Valores estruturados

Ao formatar texto sem formatação usando o especificador, o recuo em bloco é usado para listas e tuplas %A F#. Isso é mostrado no exemplo anterior. A estrutura de matrizes também é usada, incluindo matrizes multidimensionais. Matrizes unidimensionais são mostradas com [| ... |] sintaxe. Por exemplo,

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

produz

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

A largura de impressão padrão é 80. Essa largura pode ser personalizada usando uma largura de impressão no especificador de formato. Por exemplo,

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

produz

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

Especificar uma largura de impressão de 0 resulta em nenhuma largura de impressão sendo usada. Uma única linha de texto resultará, exceto quando as cadeias de caracteres incorporadas na saída contiverem quebras de linha. Por exemplo

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

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

produz

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

Um limite de profundidade de 4 é usado para valores de sequência (IEnumerable), que são mostrados como seq { ...}. Um limite de profundidade de 100 é usado para valores de lista e matriz. Por exemplo,

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

produz

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

O recuo de bloco também é usado para a estrutura de valores de registro público e união. Por exemplo,

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

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

produz

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

Se %+A for usada, então a estrutura privada de registros e sindicatos também é revelada pelo uso da reflexão. Por exemplo

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

produz

external view:
R

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

Valores grandes, cíclicos ou profundamente aninhados

Grandes valores estruturados são formatados para uma contagem máxima geral de nós de objeto de 10000. Os valores profundamente aninhados são formatados com uma profundidade de 100. Em ambos os casos ... é usado para elidir parte da saída. Por exemplo,

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)

produz uma grande produção com algumas partes elididas:

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

Os ciclos são detetados nos gráficos de objetos e ... são usados em locais onde os ciclos são detetados. Por exemplo

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

produz

{ Links = [...] }

Valores preguiçosos, nulos e de função

Os valores preguiçosos são impressos como Value is not created texto ou texto equivalente quando o valor ainda não foi avaliado.

Os valores nulos são impressos como null a menos que o tipo estático do valor seja determinado como um tipo de união onde null é uma representação permitida.

Os valores da função F# são impressos como seu nome de fechamento gerado internamente, por exemplo, <fun:it@43-7>.

Personalize a formatação de texto sem formatação com StructuredFormatDisplay

Ao usar o %A especificador, a presença do StructuredFormatDisplay atributo nas declarações de tipo é respeitada. Isso pode ser usado para especificar texto substituto e propriedade para exibir um valor. Por exemplo:

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

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

produz

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

Personalizar a formatação de texto sem formatação substituindo ToString

A implementação padrão de ToString é observável na programação F#. Muitas vezes, os resultados padrão não são adequados para uso na exibição de informações voltadas para o programador ou na saída do usuário e, como resultado, é comum substituir a implementação padrão.

Por padrão, os tipos de registro F# e união substituem a implementação de ToString por uma implementação que usa sprintf "%+A". Por exemplo,

type Counts = { Clicks:int list }

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

produz

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

Para tipos de classe, nenhuma implementação padrão de ToString é fornecida e o padrão .NET é usado, que relata o nome do tipo. Por exemplo,

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

produz

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

Adicionar uma substituição para ToString pode proporcionar uma melhor formatação.

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

produz

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

Personalize a formatação de texto sem formatação com StructuredFormatDisplay e ToString

Para obter uma formatação consistente e %A%O especificadores de formato, combine o uso de StructuredFormatDisplay com uma substituição de ToString. Por exemplo,

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

    override _.ToString() = "Custom ToString"

Avaliação das seguintes definições

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

dá o texto

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

O uso de com a propriedade supporting DisplayText significa que o fato de que o myRec é um tipo de StructuredFormatDisplay registro estrutural é ignorado durante a impressão estruturada, e a substituição de ToString() é preferível em todas as circunstâncias.

Uma implementação da System.IFormattable interface pode ser adicionada para personalização adicional na presença de especificações de formato .NET.

F# Impressão estruturada interativa

O F# Interactive (dotnet fsi) usa uma versão estendida da formatação de texto sem formatação estruturada para relatar valores e permite personalização adicional. Para obter mais informações, consulte F# interativo.

Personalizar exibições de depuração

Os depuradores para .NET respeitam o uso de atributos como DebuggerDisplay e DebuggerTypeProxy, e eles afetam a exibição estruturada de objetos nas janelas de inspeção do depurador. O compilador F# gerou automaticamente esses atributos para tipos de união e registro discriminados, mas não para tipos de classe, interface ou estrutura.

Esses atributos são ignorados na formatação de texto sem formatação F#, mas pode ser útil implementar esses métodos para melhorar as exibições ao depurar tipos F#.

Consulte também