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 structs o tipos de referencia. Son tipos de referencia de forma predeterminada.

Sintaxis

En los ejemplos siguientes se muestra la sintaxis de 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 han pensado mejor como tipos de registros de F# que no es necesario declarar antes de la creación de instancias.

Por ejemplo, aquí se muestra 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 amplía el anterior con una printCircleStats función 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

La llamada a con cualquier tipo de registro anónimo que no tenga la misma "forma" que el tipo de entrada printCircleStats 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"]'

Struct anonymous records (Estructurar registros anónimos)

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

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 struct también permiten la "inferencia de estructura" donde no es necesario especificar la palabra struct clave en el sitio de llamada. En este ejemplo, se deseide la struct palabra clave 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 struct, no se compilará.

Inserción de registros anónimos dentro de 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 mutuamente recursivos. El uso de registros anónimos evita esta restricción. A continuación se muestra un tipo de ejemplo y una función que el patrón coincide con él:

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

Copiar y actualizar 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 de uno existente:

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 struct:

// 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 que son esenciales para comprender completamente cómo se pueden usar.

Los registros anónimos son nominales

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

Considere el ejemplo siguiente con dos declaraciones de registros anónimos:

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

Los x valores y tienen tipos diferentes y no son compatibles entre y sí. No son equivalentes 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 en los registros anónimos cuando se comparan con sus equivalentes de registros con nombre cuando se trata de la equivalencia de tipos o la comparación.

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 equivalentes 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 la 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 de uso de 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 deserialados por adelantado.

Los 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 muchas otras API usadas en .NET que requieren el uso de pasar un tipo anónimo. Los registros anónimos son la 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 de la coincidencia de patrones

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

  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 subtipos estructurales: son tipos nominales.
  2. Debido a (1), no hay capacidad de tener patrones adicionales en una expresión de coincidencia de patrón, 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 "punto".

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

Limitaciones de mutability

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

Limitaciones de los registros anónimos de struct

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