Texto sin formato

F# admite el formato con comprobación de tipos de texto sin formato mediante printf, printfn, sprintf y funciones relacionadas. Por ejemplo,

dotnet fsi

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

produce la salida

Hello world, 2 + 2 is 4

F# también permite que los valores estructurados se formateen como texto sin formato. Por ejemplo, considere el ejemplo siguiente que da formato a la salida como una presentación similar a una matriz de tuplas.

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

El formato de texto sin formato estructurado se activa cuando se usa el formato %A en cadenas de formato printf. También se activa al dar formato a la salida de valores en F# interactivo, donde la salida incluye información adicional y además se puede personalizar. El formato de texto sin formato también es observable a través de las llamadas a x.ToString() en los valores de unión y registro de F#, incluidos los que se producen implícitamente en la depuración, el registro y otras herramientas.

Comprobación de cadenas de formato printf

Se notificará un error en tiempo de compilación si se usa una función de formato printf con un argumento que no coincide con los especificadores de formato printf en la cadena de formato. Por ejemplo,

sprintf "Hello %s" (2+2)

produce la salida

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

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

Técnicamente, al usar printf y otras funciones relacionadas, una regla especial en el compilador de F# comprueba el literal de cadena pasado como cadena de formato, lo que garantiza que los argumentos subsiguientes aplicados sean del tipo correcto para que coincida con los especificadores de formato usados.

Especificadores de formato para printf

Las especificaciones de formato para los formatos printfson cadenas con marcadores % que indican el formato. Los marcadores de posición de formato se componen de %[flags][width][.precision][type], donde el tipo se interpreta de la manera siguiente:

Especificador de formato Tipo o tipos Observaciones
%b bool (System.Boolean) Con formato true o false
%s string (System.String) Con formato como su contenido sin escape
%c char (System.Char) Con formato como literal de carácter
%d, %i un tipo entero básico Con formato de entero decimal, con signo si el tipo entero básico tiene signo
%u un tipo entero básico Con formato como entero decimal sin signo
%x, %X un tipo entero básico Con formato como un número hexadecimal sin signo (a-f o A-F para dígitos hexadecimales respectivamente)
%o un tipo entero básico Con formato como número octal sin signo
%B un tipo entero básico Con formato como un número binario sin signo
%e, %E un tipo de punto flotante básico Con formato como un valor con signo que tiene el formato [-]d.dddde[sign]ddd, donde d es un único dígito decimal, dddd es uno o más dígitos decimales, dddd es exactamente tres dígitos decimales y el signo es + o -.
%f, %F un tipo de punto flotante básico Con formato como un valor con signo que tiene el formato [-]dddd.dddd, donde dddd es uno o más dígitos decimales. El número de dígitos que hay delante del separador decimal depende de la magnitud del número y el número de dígitos que hay detrás del separador decimal depende de la precisión solicitada.
%g, %G un tipo de punto flotante básico Con formato como un valor con signo en formato %f o %e, lo que sea más compacto para el valor y la precisión especificados.
%M un valor decimal (System.Decimal) Con formato mediante el especificador de formato "G" para System.Decimal.ToString(format)
%O cualquier valor Con formato mediante la conversión boxing del objeto y la llamada a su método System.Object.ToString()
%A cualquier valor Formato con formato de texto sin formato estructurado con la configuración de diseño predeterminada
%a cualquier valor Requiere dos argumentos: una función de formato que acepta un parámetro de contexto y el valor, y el valor concreto que se va a imprimir.
%t cualquier valor Requiere un argumento: una función de formato que acepta un parámetro de contexto que genera o devuelve el texto adecuado.
%% (ninguno) No requiere argumentos e imprime un signo de porcentaje sin formato: %

Los tipos enteros básicos son 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) y unativeint (System.UIntPtr). Los tipos de punto flotante básicos son float (System.Double), float32 (System.Single) y decimal (System.Decimal).

El ancho opcional es un entero que indica el ancho mínimo del resultado. Por ejemplo, %6d imprime un entero, prefijándolo con espacios en blanco para rellenar al menos 6 caracteres. Si el ancho es *, se toma un argumento entero adicional para especificar el ancho correspondiente.

Las marcas válidas son:

Marca Efecto
0 Se agregan ceros en lugar de espacios para obtener el ancho necesario.
- Se justifica a la izquierda el resultado dentro del ancho especificado.
+ Agregar un carácter + si el número es positivo (como correspondencia a un signo - en el caso de números negativos).
carácter de espacio Agrega un espacio adicional si el número es positivo (como correspondencia a un signo "-" en el caso de números negativos).

La marca # de printf no es válida y se notificará un error en tiempo de compilación si se usa.

Los valores tienen formato mediante la referencia cultural invariable. La configuración de referencia cultural es irrelevante para dar formato printf, excepto cuando afectan a los resultados de %O y %A al formato. Para obtener más información, consulte Formato de texto sin formato estructurado.

Formato %A

El especificador de formato %A se usa para dar formato a los valores de una manera legible y también puede ser útil para notificar información de diagnóstico.

Valores primitivos

Al dar formato al texto sin formato mediante el especificador %A, los valores numéricos de F# tienen el formato de sufijo y la referencia cultural invariable. Los valores de punto flotante tienen el formato de 10 lugares de precisión de punto flotante. Por ejemplo,

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

genera

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

Al usar el especificador %A, las cadenas tienen formato entre comillas. Los códigos de escape no se agregan y, en su lugar, se imprimen los caracteres sin procesar. Por ejemplo,

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

genera

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

Valores .NET

Al dar formato al texto sin formato mediante el especificador %A, se da formato a objetos .NET que no son de F# mediante x.ToString() con la configuración predeterminada de .NET dada por System.Globalization.CultureInfo.CurrentCulture y System.Globalization.CultureInfo.CurrentUICulture. Por ejemplo,

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

genera

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

Valores estructurados

Al dar formato al texto sin formato mediante el especificador %A, la sangría de bloques se usa para listas y tuplas de F#. Esto se muestra en el ejemplo anterior. También se usa la estructura de matrices, incluidas matrices multidimensionales. Las matrices unidimensionales se muestran con la sintaxis [| ... |]. Por ejemplo,

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

genera

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

El ancho de impresión predeterminado es 80. Este ancho se puede personalizar mediante un ancho de impresión en el especificador de formato. Por ejemplo,

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

genera

[|(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 se especifica un ancho de impresión de 0, no se usará ningún ancho de impresión. Se producirá una sola línea de texto, excepto cuando las cadenas insertadas en la salida contengan saltos de línea. Por ejemplo

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

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

genera

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

Se usa un límite de profundidad de 4 para los valores de secuencia (IEnumerable), que se muestran como seq { ...}. Se usa un límite de profundidad de 100 para los valores de lista y matriz. Por ejemplo,

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

genera

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

La sangría de bloque también se usa para la estructura de los valores de registro público y unión. Por ejemplo,

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

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

genera

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

Si se usa %+A, la estructura privada de registros y uniones también se revela mediante reflexión. Por ejemplo

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

genera

external view:
R

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

Valores grandes, cíclicos o profundamente anidados

Los valores estructurados grandes tienen el formato de un número máximo de nodos de objetos globales de 10 000. Los valores profundamente anidados tienen un formato de profundidad de 100. En ambos casos se usa ... para obtener acceso a parte de la salida. Por ejemplo,

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)

genera una salida grande con algunas partes elididas:

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

Los ciclos se detectan en los gráficos de objetos y se usa ... en lugares donde se detectan ciclos. Por ejemplo

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

genera

{ Links = [...] }

Valores diferidos, null y de función

Los valores diferidos se imprimen como Value is not created o texto equivalente cuando el valor aún no se ha evaluado.

Los valores NULL se imprimen como null a menos que el tipo estático del valor se determine como un tipo de unión donde null es una representación permitida.

Los valores de función de F# se imprimen como su nombre de cierre generado internamente, por ejemplo, <fun:it@43-7>.

Personalización del formato de texto sin formato con StructuredFormatDisplay

Cuando se usa el especificador %A, se respeta la presencia del atributo StructuredFormatDisplay en las declaraciones de tipo. Se puede usar para especificar texto suplente y propiedad para mostrar un valor. Por ejemplo:

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

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

genera

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

Personalización del formato de texto sin formato invalidando ToString

La implementación predeterminada de ToString es observable en la programación de F#. A menudo, los resultados predeterminados no son adecuados para su uso en la presentación de información orientada al programador o en la salida del usuario, y como resultado es habitual invalidar la implementación predeterminada.

De forma predeterminada, los tipos de registro y unión de F# invalidan la implementación de ToString con una implementación que usa sprintf "%+A". Por ejemplo,

type Counts = { Clicks:int list }

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

genera

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

En el caso de los tipos de clase, no se proporciona ninguna implementación predeterminada de ToString y se usa el valor predeterminado de .NET, que informa del nombre del tipo. Por ejemplo,

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

genera

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

Agregar una invalidación para ToString puede dar lugar a un mejor formato.

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

genera

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

Personalización del formato de texto sin formato con StructuredFormatDisplay y ToString

Para lograr un formato coherente para los especificadores de formato %A y %O, combine el uso de StructuredFormatDisplay con una invalidación de ToString. Por ejemplo,

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

    override _.ToString() = "Custom ToString"

Evaluación de las siguientes definiciones

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

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

El uso de StructuredFormatDisplay con la propiedad auxiliar DisplayText significa que myRec es un tipo de registro estructural y se omite durante la impresión estructurada, y la invalidación de ToString() se prefiere en todas las circunstancias.

Se puede agregar una implementación de la interfaz System.IFormattable para una mayor personalización en presencia de especificaciones de formato de .NET.

Impresión estructurada de F# interactivo

F# interactivo (dotnet fsi) usa una versión extendida del formato de texto sin formato estructurado para informar de los valores y permite una personalización adicional. Para más información, consulte F# interactivo.

Personalización de las pantallas de depuración

Los depuradores para .NET respetan el uso de atributos como DebuggerDisplay y DebuggerTypeProxy, y afectan a la presentación estructurada de objetos en ventanas de inspección del depurador. El compilador de F# generó automáticamente estos atributos para tipos de unión y registro discriminados, pero no tipos de clase, interfaz o estructura.

Estos atributos se omiten en formato de texto sin formato de F#, pero puede ser útil implementar estos métodos para mejorar las pantallas al depurar tipos de F#.

Vea también