Formato de texto sem formatação

O F# é compatível com formatação com verificação de tipo de texto sem formatação usando printf, printfn, sprintf e funçõ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)]]

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

Verificação de cadeias de caracteres de formato printf

Um erro de tempo de compilação será relatado se uma função de formatação printf 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 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 formulário para printf

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

Especificador de formato Tipo(s) Comentários
%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 inteiro básico Formatado como um inteiro decimal, assinado se o tipo de inteiro básico for 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 semsinal (a-f ou A-F para dígitos hexadecimais, 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 com sinal com a forma [-]d.dddde[sign]ddd, em que d é um dígito decimal único, 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 com sinal com a forma [-]dddd.dddd, em que dddd é 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 valor com sinal impresso em %f ou %e, o que for mais compacto para o valor e a precisão especificados.
%M um valor decimal (System.Decimal) Formatado usando o especificador de formato "G" para System.Decimal.ToString(format)
%O qualquer valor Formatado por boxing do objeto e chamando seu método System.Object.ToString()
%A qualquer valor Formatado usando o formato 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 a ser impresso
%t qualquer valor Requer um argumento: uma função de formatação que aceita um parâmetro de contexto que gera 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 (System.IntPtr) e unativeint (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 a largura for *, um argumento inteiro extra será usado para especificar a largura correspondente.

Os sinalizadores válidos são:

Sinalizador 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 negativos)
caractere de espaço Adicione um espaço extra se o número for positivo (para corresponder a um sinal “-” de negativos)

O sinalizador # de printf é inválido e um erro em 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 a formatação printf, exceto quando afetam os resultados de %O e a formatação %A. Para obter mais informações, confira a formatação de texto sem formatação estruturada.

formatação %A

O especificador de formato %A é 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 %A, 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 %A, as 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 especificador %A, os objetos .NET não F# são formatados com x.ToString() usando 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 %A, o recuo do bloco é usado para listas e tuplas do 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 faz com que nenhuma largura de impressão seja usada. O resultado será uma única linha de texto, exceto quando cadeias de caracteres inseridas na saída contêm 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á revelados 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 omitir 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 omitidas:

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

Os ciclos são detectados nos grafos de objeto e ... é usado 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.

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 que null é 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 o formato de texto sem formatação com StructuredFormatDisplay

Ao usar o especificador %A, a presença do atributo StructuredFormatDisplay 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])

Personalize o formato de texto sem formatação substituindo ToString

A implementação padrão de 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 de ToString com 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 de ToString pode oferecer 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 o formato de texto sem formatação com StructuredFormatDisplay e ToString

Para obter formatação consistente para especificadores de formato %A e %O, 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"

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 de StructuredFormatDisplay a propriedade de DisplayText de apoio significa que myRec é um tipo de registro estrutural ignorado durante a impressão estruturada e a substituição de ToString() é preferencial em todas as circunstâncias.

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

Impressão estruturada do F# Interativo

F# Interativo(dotnet fsi) usa uma versão estendida de texto sem formatação estruturada para relatar valores e permite personalização adicional. Para mais informações, confira 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 em janelas de inspeção do depurador. O compilador F# gerou automaticamente esses atributos para tipos de união e registro 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#.

Confira também