Registros (F#)
Registros representam agregações simples de valores nomeados, opcionalmente com membros. Eles podem ser estruturas ou tipos de referência. Eles são tipos de referência por padrão.
Sintaxe
[ attributes ]
type [accessibility-modifier] typename =
{ [ mutable ] label1 : type1;
[ mutable ] label2 : type2;
... }
[ member-list ]
Comentários
Na sintaxe anterior, typename é o nome do tipo de registro, label1 e label2 são nomes de valores, conhecidos como labels, e type1 e type2 são os tipos desses valores. member-list é a lista opcional de membros do tipo. É possível usar o atributo [<Struct>]
para criar um registro de struct em vez de um registro que seja um tipo de referência.
Estes são alguns exemplos:
// Labels are separated by semicolons when defined on the same line.
type Point = { X: float; Y: float; Z: float }
// You can define labels on their own line with or without a semicolon.
type Customer =
{ First: string
Last: string
SSN: uint32
AccountNumber: uint32 }
// A struct record.
[<Struct>]
type StructPoint = { X: float; Y: float; Z: float }
Quando cada rótulo está em uma linha separada, o ponto e vírgula é opcional.
É possível definir valores em expressões conhecidas como expressões de registro. O compilador infere o tipo dos rótulos usados (se eles forem suficientemente distintos daqueles de outros tipos de registro). As chaves ({ }) delimitam a expressão de registro. O código a seguir mostra uma expressão de registro que inicializa um registro com três elementos flutuantes com rótulos x
, y
e z
.
let mypoint = { X = 1.0; Y = 1.0; Z = -1.0 }
Não use a forma abreviada se houver outro tipo com os mesmos rótulos.
type Point = { X: float; Y: float; Z: float }
type Point3D = { X: float; Y: float; Z: float }
// Ambiguity: Point or Point3D?
let mypoint3D = { X = 1.0; Y = 1.0; Z = 0.0 }
Os rótulos do tipo declarado mais recentemente têm precedência sobre os do tipo declarado anteriormente, portanto, mypoint3D
é inferido como Point3D
no exemplo anterior. É possível especificar explicitamente o tipo de registro, como no código a seguir.
let myPoint1 = { Point.X = 1.0; Y = 1.0; Z = 0.0 }
Métodos podem ser definidos para tipos de registro e tipos de classe.
Criar registros usando expressões de registro
É possível inicializar registros usando os rótulos definidos neles. A expressão de registro faz isso. Use chaves para delimitar a expressão de registro e o ponto e vírgula como delimitador.
O exemplo a seguir mostra como criar um registro.
type MyRecord = { X: int; Y: int; Z: int }
let myRecord1 = { X = 1; Y = 2; Z = 3 }
Os pontos e vírgulas após o último campo na expressão de registro e na definição de tipo são opcionais, independentemente de os campos estarem todos em uma linha.
Ao criar um registro, é necessário fornecer valores para cada campo. Não é possível fazer referência aos valores de outros campos na expressão de inicialização de um campo.
No código a seguir, o tipo de myRecord2
é inferido dos nomes dos campos. Opcionalmente, é possível especificar o nome do tipo explicitamente.
let myRecord2 =
{ MyRecord.X = 1
MyRecord.Y = 2
MyRecord.Z = 3 }
Outra forma de construção de registro pode ser útil quando você precisa copiar um registro existente e, possivelmente, alterar alguns dos valores de campo. A linha de código a seguir ilustra esse caso.
let myRecord3 = { myRecord2 with Y = 100; Z = 2 }
Esta forma da expressão de registro é chamada de expressão de registro de cópia e atualização.
Os registros são imutáveis por padrão. No entanto, é possível criar registros modificados com facilidade usando uma expressão de cópia e atualização. Também é possível especificar explicitamente um campo mutável.
type Car =
{ Make: string
Model: string
mutable Odometer: int }
let myCar =
{ Make = "Fabrikam"
Model = "Coupe"
Odometer = 108112 }
myCar.Odometer <- myCar.Odometer + 21
Não use o atributo DefaultValue com campos de registro. Uma abordagem melhor é definir instâncias padrão de registros com campos que são inicializados com valores padrão e, em seguida, usar uma expressão de registro de cópia e atualização para definir quaisquer campos que sejam diferentes dos valores padrão.
// Rather than use [<DefaultValue>], define a default record.
type MyRecord =
{ Field1 : int
Field2 : int }
let defaultRecord1 = { Field1 = 0; Field2 = 0 }
let defaultRecord2 = { Field1 = 1; Field2 = 25 }
// Use the with keyword to populate only a few chosen fields
// and leave the rest with default values.
let rr3 = { defaultRecord1 with Field2 = 42 }
Criar registros mutuamente recursivos
Às vezes, ao criar um registro, você pode querer que ele dependa de outro tipo que você definirá posteriormente. Isso é um erro de compilação, a menos os tipos de registro sejam mutuamente recursivos.
A definição de registros mutuamente recursivos é feita com a palavra-chave and
. Isso permite vincular dois ou mais tipos de registro.
Por exemplo, o seguinte código define os tipos Person
e Address
como mutuamente recursivos:
// Create a Person type and use the Address type that is not defined
type Person =
{ Name: string
Age: int
Address: Address }
// Define the Address type which is used in the Person record
and Address =
{ Line1: string
Line2: string
PostCode: string
Occupant: Person }
Para criar instâncias de ambos, faça o seguinte:
// Create a Person type and use the Address type that is not defined
let rec person =
{
Name = "Person name"
Age = 12
Address =
{
Line1 = "line 1"
Line2 = "line 2"
PostCode = "abc123"
Occupant = person
}
}
Ao definir o exemplo anterior sem a palavra-chave and
, ele não é compilado. A palavra-chave and
é necessária para definições mutuamente recursivas.
Correspondência de padrões com registros
Os registros podem ser usados com a correspondência de padrões. É possível especificar alguns campos explicitamente e fornecer variáveis para outros que serão atribuídos no caso de uma correspondência. O exemplo de código a seguir ilustra isso.
type Point3D = { X: float; Y: float; Z: float }
let evaluatePoint (point: Point3D) =
match point with
| { X = 0.0; Y = 0.0; Z = 0.0 } -> printfn "Point is at the origin."
| { X = xVal; Y = 0.0; Z = 0.0 } -> printfn "Point is on the x-axis. Value is %f." xVal
| { X = 0.0; Y = yVal; Z = 0.0 } -> printfn "Point is on the y-axis. Value is %f." yVal
| { X = 0.0; Y = 0.0; Z = zVal } -> printfn "Point is on the z-axis. Value is %f." zVal
| { X = xVal; Y = yVal; Z = zVal } -> printfn "Point is at (%f, %f, %f)." xVal yVal zVal
evaluatePoint { X = 0.0; Y = 0.0; Z = 0.0 }
evaluatePoint { X = 100.0; Y = 0.0; Z = 0.0 }
evaluatePoint { X = 10.0; Y = 0.0; Z = -1.0 }
Veja a seguir a saída desse código.
Point is at the origin.
Point is on the x-axis. Value is 100.000000.
Point is at (10.000000, 0.000000, -1.000000).
Registros e membros
É possível especificar membros em registros da mesma forma que em classes. Não há suporte para campos. Uma abordagem comum é definir um membro estático Default
para facilitar a construção do registro:
type Person =
{ Name: string
Age: int
Address: string }
static member Default =
{ Name = "Phillip"
Age = 12
Address = "123 happy fun street" }
let defaultPerson = Person.Default
Ao usar um identificador próprio, esse identificador se refere à instância do registro cujo membro é chamado:
type Person =
{ Name: string
Age: int
Address: string }
member this.WeirdToString() =
this.Name + this.Address + string this.Age
let p = { Name = "a"; Age = 12; Address = "abc123" }
let weirdString = p.WeirdToString()
Diferenças entre registros e classes
Os campos de registro diferem dos campos de classe porque são expostos automaticamente como propriedades e são usados na criação e na cópia de registros. A construção de registros também difere da construção de classes. Em um tipo de registro, não é possível definir um construtor. Em vez disso, aplica-se a sintaxe de construção descrita neste tópico. As classes não têm relação direta entre os parâmetros, os campos e as propriedades do construtor.
Assim como os tipos de união e estrutura, os registros têm semântica de igualdade estrutural. As classes têm semântica de igualdade de referência. O código de exemplo a seguir demonstra isso.
type RecordTest = { X: int; Y: int }
let record1 = { X = 1; Y = 2 }
let record2 = { X = 1; Y = 2 }
if (record1 = record2) then
printfn "The records are equal."
else
printfn "The records are unequal."
A saída deste código é a seguinte:
The records are equal.
Ao escrever o mesmo código com classes, os dois objetos de classe seriam desiguais porque os dois valores representariam dois objetos no heap e somente os endereços seriam comparados (a menos que o tipo de classe substitua o método System.Object.Equals
).
Se você precisar de igualdade de referência para registros, adicione o atributo [<ReferenceEquality>]
acima do registro.