Registos (F#)
Os registros representam agregações simples de valores nomeados, opcionalmente com membros. 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 ]
Observações
Na sintaxe anterior, typename é o nome do tipo de registro, label1 e label2 são nomes de valores, referidos como rótulos, e type1 e type2 são os tipos desses valores. member-list é a lista opcional de membros para o tipo. Você pode usar o [<Struct>]
atributo para criar um registro struct em vez de um registro que é um tipo de referência.
Abaixo, encontram-se 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.
Você pode definir valores em expressões conhecidas como expressões de registro. O compilador infere o tipo a partir dos rótulos usados (se os rótulos forem suficientemente distintos dos de outros tipos de registro). As chaves ({ }) incluem a expressão de registo. O código a seguir mostra uma expressão de registro que inicializa um registro com três elementos float com rótulos x
e y
z
.
let mypoint = { X = 1.0; Y = 1.0; Z = -1.0 }
Não use o formulário abreviado se puder haver outro tipo que também tenha 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, no exemplo anterior, mypoint3D
infere-se que seja Point3D
. Você pode especificar explicitamente o tipo de registro, como no código a seguir.
let myPoint1 = { Point.X = 1.0; Y = 1.0; Z = 0.0 }
Os métodos podem ser definidos para tipos de registro, assim como para tipos de classe.
Criando registros usando expressões de registro
Você pode inicializar registros usando os rótulos definidos no registro. Uma expressão que faz isso é chamada de expressão de registro. Use chaves para incluir a expressão de registro e use 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 ponto-e-vírgula 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, você deve fornecer valores para cada campo. Não é possível fazer referência aos valores de outros campos na expressão de inicialização de qualquer campo.
No código a seguir, o tipo de é inferido a partir dos myRecord2
nomes dos campos. Opcionalmente, você pode 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 isso.
let myRecord3 = { myRecord2 with Y = 100; Z = 2 }
Essa 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, você pode criar facilmente registros modificados usando uma expressão copy and update. Você também pode 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 para valores padrão e, em seguida, usar uma expressão de registro de cópia e atualização para definir quaisquer campos que diferem 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 }
Criando registros mutuamente recursivos
Em algum momento ao criar um registro, você pode querer que ele dependa de outro tipo que você gostaria de definir depois. Este é um erro de compilação, a menos que você defina os tipos de registro para serem mutuamente recursivos.
A definição de registros mutuamente recursivos é feita com a palavra-chave and
. Isso permite vincular 2 ou mais tipos de registro.
Por exemplo, o código a seguir define a Person
e Address
type como mutuamente recursivo:
// 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
}
}
Se você definisse o exemplo anterior sem a and
palavra-chave, ele não seria compilado. A and
palavra-chave é necessária para definições mutuamente recursivas.
Correspondência de padrões com registros
Os registros podem ser usados com correspondência de padrões. Você pode especificar alguns campos explicitamente e fornecer variáveis para outros campos que serão atribuídos quando ocorrer 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 }
A saída deste código é a seguinte.
Point is at the origin.
Point is on the x-axis. Value is 100.000000.
Point is at (10.000000, 0.000000, -1.000000).
Registos e membros
Você pode especificar membros em registros da mesma forma que pode fazer com as classes. Não há suporte para campos. Uma abordagem comum é definir um Default
membro estático para facilitar a construção de registros:
type Person =
{ Name: string
Age: int
Address: string }
static member Default =
{ Name = "Phillip"
Age = 12
Address = "123 happy fun street" }
let defaultPerson = Person.Default
Se você 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 automaticamente expostos como propriedades e são usados na criação e cópia de registros. A construção de registros também difere da construção de classe. Em um tipo de registro, você não pode 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 parâmetros, campos e 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 exemplo de código 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.
Se você escrever o mesmo código com classes, os dois objetos de classe serão desiguais porque os dois valores representariam dois objetos no heap e apenas os endereços seriam comparados (a menos que o tipo de classe substitua o System.Object.Equals
método).
Se você precisar de igualdade de referência para registros, adicione o atributo [<ReferenceEquality>]
acima do registro.