Registos Anónimos
Registros anônimos são simples agregados 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.
Sintaxe
Os exemplos a seguir demonstram a sintaxe do 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; ...|}) ...
Utilização básica
Os registros anônimos são melhor pensados 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 expande o anterior com uma printCircleStats
função 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 não será compilado:
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"]'
Estruturar registos anónimos
Os registos 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 struct anonymous:
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 estrutura
Os registros anônimos Struct também permitem a "inferência de structness", onde você não precisa especificar a struct
palavra-chave no site de chamada. Neste exemplo, você elide a struct
palavra-chave 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.
Incorporação de registos anónimos noutros tipos
É útil declarar sindicatos discriminados cujos casos são registrados. Mas se os dados nos registros forem do mesmo tipo que a união discriminada, você deve definir todos os tipos como mutuamente recursivos. A utilização de registos anónimos evita esta restrição. O que se segue é um exemplo de tipo e 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
Copiar e atualizar expressões
Os registos anónimos suportam a 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 para 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 referência e estruturar registros anônimos:
// 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 registos anónimos
Os registos anónimos têm uma série de características que são essenciais para compreender plenamente como podem ser utilizados.
Os registos anónimos são nominais
Os registos anónimos são tipos nominais. Eles são melhor pensados como tipos de registro nomeados (que também são nominais) que não exigem uma declaração inicial.
Considere o exemplo a seguir com duas declarações de registro anônimo:
let x = {| X = 1 |}
let y = {| Y = 1 |}
Os x
valores e y
têm tipos diferentes e não são compatíveis entre si. Não são equânimes e não são 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 nos registros anônimos quando comparados com seus equivalentes de registro nomeados quando se trata de equivalência de tipo ou comparação.
Os registos anónimos utilizam a igualdade estrutural e a comparação
Tal como os tipos de registo, os registos anónimos são estruturalmente equáveis e comparáveis. Isto só é verdade se todos os tipos constituintes apoiarem a igualdade e a comparação, como acontece com os tipos de registo. Para apoiar a igualdade ou a comparação, dois registos 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|}
Os registros anônimos são serializáveis
Você pode serializar registros anônimos da mesma forma que pode fazer com registros nomeados. Aqui está 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}"
Os registros anônimos são úteis para enviar dados leves por uma rede sem a necessidade de definir um domínio para seus tipos serializados/desserializados antecipadamente.
Registros anônimos interoperam com tipos anônimos C#
É possível usar uma API .NET que requer o uso de tipos anônimos C#. Os tipos anônimos C# são triviais para interoperar usando registros anônimos. O exemplo a seguir mostra como usar registros anônimos para chamar uma sobrecarga 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 de passar em um tipo anônimo. Os registos anónimos são a sua ferramenta para trabalhar com eles.
Limitações
Os registos anónimos têm algumas restrições na sua utilização. Alguns são inerentes ao seu design, mas outros são passíveis de mudança.
Limitações com correspondência de padrões
Os registos anónimos não suportam a correspondência de padrões, ao contrário dos registos nomeados. Existem três razões:
- Um padrão teria que levar em conta todos os campos de um registro anônimo, ao contrário dos tipos de registro nomeados. Isto porque os registos anónimos não suportam subtipagem estrutural – são tipos nominais.
- 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.
- 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 a correspondência de padrões em contextos limitados.
Limitações com mutabilidade
Atualmente, não é possível definir um registo anónimo com mutable
dados. 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 como IsByRefLike
ou IsReadOnly
. Há uma sugestão de linguagem aberta para registros anônimos IsReadOnly
IsByRefLike
.