Compartilhar via


Formatação de texto sem formatação

O F# dá suporte à formatação marcada por tipo de texto sem formatação usando printf, printfne sprintffunções relacionadas. Por exemplo

dotnet fsi

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

fornece a saída

Hello world, 2 + 2 is 4

O 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 à 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 estruturada é 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 é personalizável adicionalmente. A formatação de texto sem formatação também é observável por meio de chamadas para x.ToString() valores de registro e união F#, incluindo aqueles que ocorrem implicitamente na depuração, registro em log e outras ferramentas.

Verificação de cadeias de printfcaracteres de formato

Um erro de 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)

fornece 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 da 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 formato consistem em %[flags][width][.precision][type] onde o tipo é interpretado da seguinte maneira:

Especificador de formato Type(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 de caractere
%d, %i um tipo inteiro básico Formatado como um inteiro decimal, assinado se o tipo inteiro básico estiver assinado
%u um tipo inteiro básico Formatado como um inteiro decimal sem sinal
%x, %X um tipo inteiro básico Formatado como um número hexadecimal não assinado (a-f ou A-F para dígitos hex, respectivamente)
%o um tipo inteiro básico Formatado como um número octal sem sinal
%B um tipo inteiro básico Formatado como um número binário sem sinal
%e, %E um tipo de ponto flutuante básico Formatado como um valor assinado com o formulário [-]d.dddde[sign]ddd em que d é um único dígito decimal, dddd é um ou mais dígitos decimais, ddd é exatamente três dígitos decimais e o sinal é + ou -
%f, %F um tipo de ponto flutuante básico Formatado como um valor assinado com o formulário [-]dddd.dddd, onde dddd está um ou mais dígitos decimais. O número de dígitos antes do ponto decimal depende da magnitude do número e o número de dígitos após o ponto decimal depende da precisão solicitada.
%g, %G um tipo de ponto flutuante básico Formatado usando como um valor assinado impresso em %f ou %e formato, o que for mais compacto para o valor e a precisão fornecidos.
%M a decimal (System.Decimal) value Formatado usando o "G" especificador de formato para System.Decimal.ToString(format)
%O qualquer valor Formatado por boxing do objeto e chamando seu System.Object.ToString() método
%A qualquer valor Formatado usando a 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 aceitando 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 aceitando um parâmetro de contexto que gera ou retorna o texto apropriado
%% (nenhuma) Não requer argumentos e imprime um sinal de porcentagem simples: %

Os tipos inteiros básicos são (), (), int16 (System.SByteSystem.Int16), uint16 (System.UInt16), int32 (), (System.Int32), uint32 (System.UInt32), int64 (System.Int64), uint64 (System.UInt64), (System.IntPtr) nativeint e unativeint (System.UIntPtr). sbyteSystem.Bytebyte 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 a largura for *, um argumento inteiro extra será usado para especificar a largura correspondente.

Os sinalizadores válidos são:

Bandeira Efeito
0 Adicionar zeros em vez de espaços para compor a largura necessária
- A esquerda justifica o resultado dentro da largura especificada
+ Adicione um + caractere se o número for positivo (para corresponder a um - sinal de negativo)
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 de tempo de compilação será relatado se ele for usado.

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

%A formatação

O %A especificador de formato é usado para formatar valores de maneira 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 %A especificador, os valores numéricos F# são formatados com o sufixo e a cultura invariável. 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 especificador, as %A cadeias de caracteres são formatadas usando aspas. 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 do .NET

Ao formatar texto sem formatação usando o %A especificador, os 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 %A recuo de bloco é usado para listas e tuplas 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 inseridas 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 usado, a estrutura privada de registros e uniões também será revelada usando 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

Valores estruturados grandes são formatados para uma contagem máxima de nó de objeto geral de 10000. Valores profundamente aninhados são formatados para uma profundidade de 100. Em ambos os casos ... , é usado para reexibir 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 saída grande com algumas partes excluídas:

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

Os ciclos são detectados nos grafos de objeto e ... são usados em locais onde os ciclos são detectados. Por exemplo

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

Produz

{ Links = [...] }

Valores lentos, nulos e de função

Valores lentos são impressos como Value is not created 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 em null que é uma representação permitida.

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

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

Ao usar o %A especificador, a presença do StructuredFormatDisplay atributo em declarações de tipo é respeitada. Isso pode ser usado para especificar texto alternativo 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 é ToString observável na programação F#. Geralmente, 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 com ToString 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 ToString classe, nenhuma implementação padrão é 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 pode ToString fornecer 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])]

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

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

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

    override _.ToString() = "Custom ToString"

Avaliando as definições a seguir

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

fornece 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 com StructuredFormatDisplay a propriedade de suporte DisplayText significa que o myRec tipo de registro estrutural é ignorado durante a impressão estruturada e a substituição ToString() é preferencial 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.

Impressão estruturada interativa F#

O F# Interativo (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 do .NET respeitam o uso de atributos como DebuggerDisplay e DebuggerTypeProxy, portanto, afetam a exibição estruturada de objetos nas janelas de inspeção do depurador. O compilador F# gerou automaticamente esses atributos para tipos de registro e união discriminados, mas não tipos de classe, interface ou struct.

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