Záznamy (F#)
Záznamy představují jednoduché agregace pojmenovaných hodnot, volitelně s členy. Mohou to být struktury nebo odkazové typy. Ve výchozím nastavení se jedná o odkazové typy.
Syntaxe
[ attributes ]
type [accessibility-modifier] typename =
{ [ mutable ] label1 : type1;
[ mutable ] label2 : type2;
... }
[ member-list ]
Poznámky
V předchozí syntaxi je název typu záznamu, popisek1 a popisek2 jsou názvy hodnot, které se označují jako popisky, a type1 a type2 jsou typy těchto hodnot. seznam členů je volitelný seznam členů pro typ. Atribut můžete použít [<Struct>]
k vytvoření záznamu struktury místo záznamu, který je typem odkazu.
Dále je uvedeno několik příkladů.
// 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 }
Pokud je každý popisek na samostatném řádku, je středník nepovinný.
Hodnoty můžete nastavit ve výrazech, které se označují jako výrazy záznamů. Kompilátor odvodí typ z použitých popisků (pokud jsou popisky dostatečně odlišné od popisků jiných typů záznamů). Složené závorky ({ }) uzavře výraz záznamu. Následující kód ukazuje výraz záznamu, který inicializuje záznam se třemi plovoucími prvky s popisky x
y
a z
.
let mypoint = { X = 1.0; Y = 1.0; Z = -1.0 }
Nepoužívejte zkrácený formulář, pokud může existovat jiný typ, který má také stejné popisky.
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 }
Popisky naposledy deklarovaného typu mají přednost před popisky dříve deklarovaného typu, takže v předchozím příkladu mypoint3D
je odvozen jako Point3D
. Typ záznamu můžete explicitně zadat jako v následujícím kódu.
let myPoint1 = { Point.X = 1.0; Y = 1.0; Z = 0.0 }
Metody lze definovat pro typy záznamů stejně jako pro typy tříd.
Vytváření záznamů pomocí výrazů záznamů
Záznamy můžete inicializovat pomocí popisků definovaných v záznamu. Výraz, který to dělá, se označuje jako výraz záznamu. Pomocí složených závorek uzavřete výraz záznamu a jako oddělovač použijte středník.
Následující příklad ukazuje, jak vytvořit záznam.
type MyRecord = { X: int; Y: int; Z: int }
let myRecord1 = { X = 1; Y = 2; Z = 3 }
Středníky za posledním polem ve výrazu záznamu a v definici typu jsou volitelné bez ohledu na to, jestli jsou pole všechna na jednom řádku.
Při vytváření záznamu je nutné zadat hodnoty pro každé pole. Nelze odkazovat na hodnoty jiných polí ve výrazu inicializace pro jakékoli pole.
V následujícím kódu je typ myRecord2
odvozený z názvů polí. Volitelně můžete explicitně zadat název typu.
let myRecord2 =
{ MyRecord.X = 1
MyRecord.Y = 2
MyRecord.Z = 3 }
Další forma vytváření záznamů může být užitečná, když potřebujete zkopírovat existující záznam a případně změnit některé hodnoty polí. Následující řádek kódu to ilustruje.
let myRecord3 = { myRecord2 with Y = 100; Z = 2 }
Tato forma výrazu záznamu se nazývá výraz pro kopírování a aktualizaci záznamu.
Záznamy jsou ve výchozím nastavení neměnné; upravené záznamy však můžete snadno vytvořit pomocí výrazu kopírování a aktualizace. Můžete také explicitně zadat proměnlivé pole.
type Car =
{ Make: string
Model: string
mutable Odometer: int }
let myCar =
{ Make = "Fabrikam"
Model = "Coupe"
Odometer = 108112 }
myCar.Odometer <- myCar.Odometer + 21
Nepoužívejte atribut DefaultValue s poli záznamů. Lepším přístupem je definovat výchozí instance záznamů s poli inicializovanými na výchozí hodnoty a pak pomocí výrazu kopírování a aktualizace záznamu nastavit všechna pole, která se liší od výchozích hodnot.
// 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 }
Vytváření vzájemně rekurzivních záznamů
Někdy při vytváření záznamu může být vhodné, aby závisel na jiném typu, který byste chtěli definovat později. Jedná se o chybu kompilace, pokud nedefinujete typy záznamů, které se mají vzájemně rekurzivně rekurzivní.
Definování vzájemně rekurzivních záznamů se provádí pomocí klíčového and
slova. To vám umožní propojit 2 nebo více typů záznamů dohromady.
Například následující kód definuje Person
a Address
typ jako vzájemně rekurzivní:
// 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 }
Pokud chcete vytvořit instance obou instancí, postupujte takto:
// 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
}
}
Pokud byste definovali předchozí příklad bez klíčového and
slova, nezkompiloval by se. Klíčové and
slovo se vyžaduje pro vzájemně rekurzivní definice.
Porovnávání vzorů se záznamy
Záznamy lze použít s porovnávání vzorů. Můžete zadat některá pole explicitně a zadat proměnné pro jiná pole, která budou přiřazena při výskytu shody. Následující příklad kódu to dokládá.
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 }
Výstup tohoto kódu je následující.
Point is at the origin.
Point is on the x-axis. Value is 100.000000.
Point is at (10.000000, 0.000000, -1.000000).
Záznamy a členové
Členy můžete u záznamů zadat podobně jako u tříd. Pole nejsou podporována. Běžným přístupem je definovat Default
statický člen pro snadnou konstrukci záznamů:
type Person =
{ Name: string
Age: int
Address: string }
static member Default =
{ Name = "Phillip"
Age = 12
Address = "123 happy fun street" }
let defaultPerson = Person.Default
Pokud použijete identifikátor sebe sama, odkazuje tento identifikátor na instanci záznamu, jehož člen je volána:
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()
Rozdíly mezi záznamy a třídami
Pole záznamů se liší od polí třídy v tom, že jsou automaticky vystavena jako vlastnosti a používají se při vytváření a kopírování záznamů. Konstrukce záznamů se také liší od konstrukce třídy. V typu záznamu nelze definovat konstruktor. Místo toho platí syntaxe konstrukce popsaná v tomto tématu. Třídy nemají žádný přímý vztah mezi parametry konstruktoru, poli a vlastnostmi.
Stejně jako sjednocení a typy struktur mají záznamy sémantiku strukturální rovnosti. Třídy mají sémantiku rovnosti odkazů. Následující příklad kódu ukazuje toto.
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."
Výstup tohoto kódu je následující:
The records are equal.
Pokud napíšete stejný kód s třídami, dva objekty třídy by byly nerovné, protože dvě hodnoty by představovaly dva objekty v haldě a pouze adresy by byly porovnány (pokud typ třídy přepíše metodu System.Object.Equals
).
Pokud potřebujete odkazovat na rovnost záznamů, přidejte atribut [<ReferenceEquality>]
nad záznam.