Registros Anônimos

Registros anônimos são agregações simples de valores nomeados que não precisam ser declarados antes do uso. Você pode declará-los como structs ou tipos de referência. Eles são tipos de referência por padrão.

Syntax

Os exemplos a seguir demonstram a sintaxe de registro anônimo. Itens delimitados como [item] são opcionais.

// 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

Registros anônimos são mais bem considerados como tipos de registro F# que não precisam ser declarados antes da instanciação.

Por exemplo, aqui como você pode interagir com uma função que produz um 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

O exemplo a seguir se expande no anterior com uma função printCircleStats que usa um 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

Chamar printCircleStats com qualquer tipo de registro anônimo que não tenha a mesma "forma" que o tipo de entrada falhará ao 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 struct

Registros anônimos também podem ser definidos como struct com a palavra-chave opcional struct. O exemplo a seguir aumenta o anterior produzindo e consumindo um registro anônimo 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

Inferência de structness

Os registros anônimos struct também permitem "inferência de structness" onde você não precisa especificar a palavra-chave struct no site de chamada. Neste exemplo, você elide a palavra-chave struct ao chamar 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 |}

O padrão inverso - especificando struct quando o tipo de entrada não é um registro anônimo struct - não será compilado.

Inserindo registros anônimos em outros tipos

É útil declarar uniões discriminados cujos casos são registros. Mas se os dados nos registros forem do mesmo tipo que a união discriminada, você deverá definir todos os tipos como mutuamente recursivos. O uso de registros anônimos evita essa restrição. O que se segue é um tipo de exemplo e uma função que o padrão corresponde sobre ele:

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

Expressões de cópia e atualização

Registros anônimos dão suporte à construção com expressões de cópia e atualização. Por exemplo, veja como você pode construir uma nova instância de um registro anônimo que copia os dados de um existente:

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

No entanto, ao contrário dos registros nomeados, os registros anônimos permitem que você construa formulários totalmente diferentes com expressões de cópia e atualização. O exemplo a seguir usa o mesmo registro anônimo do exemplo anterior e o expande em um novo registro anônimo:

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

Também é possível construir registros anônimos a partir de instâncias de registros nomeados:

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

Você também pode copiar dados de e para registros anônimos de referência e 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 |}

Propriedades de registros anônimos

Os registros anônimos têm várias características essenciais para reconhecer completamente como eles podem ser usados.

Registros anônimos são nominais

Registros anônimos são tipos nominais. É melhor pensar neles como tipos de registro nomeados (que também são nominais) que não exigem uma declaração antecipada.

Considere o exemplo a seguir com duas declarações de registro anônimo:

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

Os valores x e y têm tipos diferentes e não são compatíveis uns com os outros. Eles não são equacionáveis nem comparáveis. Para ilustrar isso, considere um equivalente de registro nomeado:

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

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

Não há nada inerentemente diferente sobre registros anônimos quando comparados com seus equivalentes de registros nomeados no que diz respeito à equivalência ou comparação de tipos.

Registros anônimos usam igualdade e comparação estruturais

Como tipos de registro, os registros anônimos são estruturalmente equacionáveis e comparáveis. Isso só será verdade se todos os tipos constituintes deem suporte à igualdade e à comparação, como com tipos de registro. Para dar suporte à igualdade ou à comparação, dois registros anônimos devem ter a mesma "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|}

Registros anônimos são serializáveis

Você pode serializar registros anônimos da mesma forma que com registros nomeados. Aqui temos um exemplo usando 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}"

Registros anônimos são úteis para enviar dados leves em uma rede sem a necessidade de definir um domínio para seus tipos serializados/desserializados antecipadamente.

Registros anônimos interoperam com tipos anônimos em C#

É possível usar uma API do .NET que requer o uso de tipos anônimos em C#. Tipos anônimos em C# são triviais para interoperar usando registros anônimos. O exemplo a seguir mostra como usar registros anônimos para chamar uma sobrecarga de LINQ que requer um 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}"

Há uma infinidade de outras APIs usadas em todo o .NET que exigem o uso da passagem de um tipo anônimo. Registros anônimos são sua ferramenta para trabalhar com eles.

Limitações

Registros anônimos têm algumas restrições em seu uso. Alguns são inerentes ao seu design, mas outros são passíveis de mudança.

Limitações com padrões correspondentes

Registros anônimos não dão suporte à padrões correspondentes, ao contrário dos registros nomeados. Há três motivos:

  1. Um padrão teria que levar em conta cada campo de um registro anônimo, ao contrário dos tipos de registro nomeados. Isso ocorre porque os registros anônimos não dão suporte à subtipagem estrutural – eles são tipos nominais.
  2. Devido a (1), não há capacidade de ter padrões adicionais em uma expressão de correspondência de padrão, pois cada padrão distinto implicaria um tipo de registro anônimo diferente.
  3. Devido a (2), qualquer padrão de registro anônimo seria mais detalhado do que o uso da notação "ponto".

Há uma sugestão de linguagem aberta para permitir padrões correspondentes em contextos limitados.

Limitações com mutabilidade

No momento, não é possível definir um registro anônimo com dados mutable. Há uma sugestão de linguagem aberta para permitir dados mutáveis.

Limitações com registros anônimos struct

Não é possível declarar registros anônimos struct como IsByRefLike ou IsReadOnly. Há uma sugestão de linguagem aberta para registros anônimos IsByRefLike e IsReadOnly.