Registros anónimos

Los registros anónimos son agregados simples de valores con nombre que no es necesario declarar antes de su uso. Puede declararlos como estructuras o tipos de referencia. De forma predeterminada, son tipos de referencia.

Sintaxis

Los ejemplos siguientes muestran el uso de la sintaxis de los registros anónimos. Los elementos delimitados como [item] son opcionales.

// Construct an anonymous record
let value-name = [struct] {| Label1: Type1; Label2: Type2; ...|}

// Use an anonymous record as a type parameter
let value-name = Type-Name<[struct] {| Label1: Type1; Label2: Type2; ...|}>

// Define a parameter with an anonymous record as input
let function-name (arg-name: [struct] {| Label1: Type1; Label2: Type2; ...|}) ...

Uso básico

Los registros anónimos se consideran mejor como tipos de registro de F# que no es necesario declarar antes de la creación de la creación de una instancia.

Por ejemplo, aquí cómo puede interactuar con una función que genera un registro anónimo:

open System

let getCircleStats radius =
    let d = radius * 2.0
    let a = Math.PI * (radius ** 2.0)
    let c = 2.0 * Math.PI * radius

    {| Diameter = d; Area = a; Circumference = c |}

let r = 2.0
let stats = getCircleStats r
printfn "Circle with radius: %f has diameter %f, area %f, and circumference %f"
    r stats.Diameter stats.Area stats.Circumference

En el ejemplo siguiente se expande el anterior con una función printCircleStats que toma un registro anónimo como entrada:

open System

let getCircleStats radius =
    let d = radius * 2.0
    let a = Math.PI * (radius ** 2.0)
    let c = 2.0 * Math.PI * radius

    {| Diameter = d; Area = a; Circumference = c |}

let printCircleStats r (stats: {| Area: float; Circumference: float; Diameter: float |}) =
    printfn "Circle with radius: %f has diameter %f, area %f, and circumference %f"
        r stats.Diameter stats.Area stats.Circumference

let r = 2.0
let stats = getCircleStats r
printCircleStats r stats

Al llamar a printCircleStats con cualquier tipo de registro anónimo que no tenga la misma "forma" que el tipo de entrada no se compilará:

printCircleStats r {| Diameter = 2.0; Area = 4.0; MyCircumference = 12.566371 |}
// Two anonymous record types have mismatched sets of field names
// '["Area"; "Circumference"; "Diameter"]' and '["Area"; "Diameter"; "MyCircumference"]'

Registros anónimos de estructura

Los registros anónimos también se pueden definir como estructura con la palabra clave struct opcional. En el ejemplo siguiente se aumenta el anterior mediante la generación y el consumo de un registro anónimo de estructura:

open System

let getCircleStats radius =
    let d = radius * 2.0
    let a = Math.PI * (radius ** 2.0)
    let c = 2.0 * Math.PI * radius

    // Note that the keyword comes before the '{| |}' brace pair
    struct {| Area = a; Circumference = c; Diameter = d |}

// the 'struct' keyword also comes before the '{| |}' brace pair when declaring the parameter type
let printCircleStats r (stats: struct {| Area: float; Circumference: float; Diameter: float |}) =
    printfn "Circle with radius: %f has diameter %f, area %f, and circumference %f"
        r stats.Diameter stats.Area stats.Circumference

let r = 2.0
let stats = getCircleStats r
printCircleStats r stats

Inferencia de estructura

Los registros anónimos de estructura también permiten la "inferencia de estructura" donde no es necesario especificar la palabra clave struct en el sitio de llamada. En este ejemplo, se elide la palabra clave struct al llamar a printCircleStats:


let printCircleStats r (stats: struct {| Area: float; Circumference: float; Diameter: float |}) =
    printfn "Circle with radius: %f has diameter %f, area %f, and circumference %f"
        r stats.Diameter stats.Area stats.Circumference

printCircleStats r {| Area = 4.0; Circumference = 12.6; Diameter = 12.6 |}

El patrón inverso, que especifica struct cuándo el tipo de entrada no es un registro anónimo de estructura, no se compilará.

Inserción de registros anónimos en otros tipos

Resulta útil declarar uniones discriminadas cuyos casos son registros. Pero si los datos de los registros son del mismo tipo que la unión discriminada, debe definir todos los tipos como recursivos mutuamente. El uso de registros anónimos evita esta restricción. Lo siguiente es un tipo de ejemplo y una función que el que coincide el patrón:

type FullName = { FirstName: string; LastName: string }

// Note that using a named record for Manager and Executive would require mutually recursive definitions.
type Employee =
    | Engineer of FullName
    | Manager of {| Name: FullName; Reports: Employee list |}
    | Executive of {| Name: FullName; Reports: Employee list; Assistant: Employee |}

let getFirstName e =
    match e with
    | Engineer fullName -> fullName.FirstName
    | Manager m -> m.Name.FirstName
    | Executive ex -> ex.Name.FirstName

Copia y actualización de expresiones

Los registros anónimos admiten la construcción con expresiones de copia y actualización. Por ejemplo, aquí se muestra cómo se puede construir una nueva instancia de un registro anónimo que copia los datos existentes:

let data = {| X = 1; Y = 2 |}
let data' = {| data with Y = 3 |}

Sin embargo, a diferencia de los registros con nombre, los registros anónimos permiten construir formularios completamente diferentes con expresiones de copia y actualización. En el ejemplo siguiente se toma el mismo registro anónimo del ejemplo anterior y se expande en un nuevo registro anónimo:

let data = {| X = 1; Y = 2 |}
let expandedData = {| data with Z = 3 |} // Gives {| X=1; Y=2; Z=3 |}

También es posible construir registros anónimos a partir de instancias de registros con nombre:

type R = { X: int }
let data = { X = 1 }
let data' = {| data with Y = 2 |} // Gives {| X=1; Y=2 |}

También puede copiar datos hacia y desde registros anónimos de referencia y estructura:

// Copy data from a reference record into a struct anonymous record
type R1 = { X: int }
let r1 = { X = 1 }

let data1 = struct {| r1 with Y = 1 |}

// Copy data from a struct record into a reference anonymous record
[<Struct>]
type R2 = { X: int }
let r2 = { X = 1 }

let data2 = {| r1 with Y = 1 |}

// Copy the reference anonymous record data into a struct anonymous record
let data3 = struct {| data2 with Z = r2.X |}

Propiedades de registros anónimos

Los registros anónimos tienen una serie de características esenciales para comprender completamente cómo se pueden usar.

Los registros anónimos son nominales

Los registros anónimos son tipos nominales. Se consideran mejor como tipos de registro con nombre (que también son nominales) que no requieren una declaración inicial.

Considere el ejemplo siguiente con dos declaraciones de registro anónimas:

let x = {| X = 1 |}
let y = {| Y = 1 |}

Los valores x y y tienen tipos diferentes y no son compatibles entre sí. No son iguales y no son comparables. Para ilustrar esto, considere un registro con nombre equivalente:

type X = { X: int }
type Y = { Y: int }

let x = { X = 1 }
let y = { Y = 1 }

No hay nada inherentemente diferente sobre los registros anónimos en comparación con sus equivalentes de registro con nombre cuando se refiere a la equivalencia o comparación de tipos.

Los registros anónimos usan la igualdad estructural y la comparación

Al igual que los tipos de registro, los registros anónimos son estructuralmente iguales y comparables. Esto solo es cierto si todos los tipos constituyentes admiten igualdad y comparación, como con los tipos de registro. Para admitir la igualdad o comparación, dos registros anónimos deben tener la misma "forma".

{| a = 1+1 |} = {| a = 2 |} // true
{| a = 1+1 |} > {| a = 1 |} // true

// error FS0001: Two anonymous record types have mismatched sets of field names '["a"]' and '["a"; "b"]'
{| a = 1 + 1 |} = {| a = 2;  b = 1|}

Los registros anónimos son serializables

Puede serializar registros anónimos igual que con registros con nombre. Este es un ejemplo con Newtonsoft.Json:

open Newtonsoft.Json

let phillip' = {| name="Phillip"; age=28 |}
let philStr = JsonConvert.SerializeObject(phillip')

let phillip = JsonConvert.DeserializeObject<{|name: string; age: int|}>(philStr)
printfn $"Name: {phillip.name} Age: %d{phillip.age}"

Los registros anónimos son útiles para enviar datos ligeros a través de una red sin necesidad de definir un dominio para los tipos serializados o deserializados por adelantado.

Registros anónimos interoperan con tipos anónimos de C#

Es posible usar una API de .NET que requiera el uso de tipos anónimos de C#. Los tipos anónimos de C# son triviales para interoperar con mediante registros anónimos. En el ejemplo siguiente se muestra cómo usar registros anónimos para llamar a una sobrecarga LINQ que requiere un tipo anónimo:

open System.Linq

let names = [ "Ana"; "Felipe"; "Emilia"]
let nameGrouping = names.Select(fun n -> {| Name = n; FirstLetter = n[0] |})
for ng in nameGrouping do
    printfn $"{ng.Name} has first letter {ng.FirstLetter}"

Hay una multitud de otras API que se usan en .NET que requieren el uso de pasar un tipo anónimo. Los registros anónimos son su herramienta para trabajar con ellos.

Limitaciones

Los registros anónimos tienen algunas restricciones en su uso. Algunos son inherentes a su diseño, pero otros son amenables para cambiar.

Limitaciones con la coincidencia de patrones

Los registros anónimos no admiten la coincidencia de patrones, a diferencia de los registros con nombre. Existen tres casos:

  1. Un patrón tendría que tener en cuenta cada campo de un registro anónimo, a diferencia de los tipos de registro con nombre. Esto se debe a que los registros anónimos no admiten subtipado estructural: son tipos nominales.
  2. Debido a (1), no hay ninguna capacidad de tener patrones adicionales en una expresión de coincidencia de patrones, ya que cada patrón distinto implicaría un tipo de registro anónimo diferente.
  3. Debido a (2), cualquier patrón de registro anónimo sería más detallado que el uso de la notación "dot".

Hay una sugerencia de lenguaje abierto para permitir la coincidencia de patrones en contextos limitados.

Limitaciones con la mutabilidad

Actualmente no es posible definir un registro anónimo con datos mutable. Hay una sugerencia de idioma abierto para permitir datos mutables.

Limitaciones con registros anónimos de estructura

No es posible declarar registros anónimos de estructura como IsByRefLike o IsReadOnly. Hay una sugerencia de idioma abierto para registros anónimos IsByRefLike y IsReadOnly.