Записи (F#)
Записи представляют простые агрегаты именованных значений, дополнительно с членами.
[ attributes ]
type [accessibility-modifier] typename = {
[ mutable ] label1 : type1;
[ mutable ] label2 : type2;
...
}
member-list
Заметки
В предыдущем синтаксисе typename — имя типа записи; label1 и label2 — имена значений, называемые метками; type1 и type2 — типы этих значений.member-list — необязательный список членов для типа.
Ниже приведены некоторые примеры.
type Point = { x : float; y: float; z: float; }
type Customer = { First : string; Last: string; SSN: uint32; AccountNumber : uint32; }
Когда каждая метка находится на отдельной строке, точка с запятой необязательна.
Значения можно задать в выражениях, называемых выражениями записей.Компилятор определяет тип по использованным меткам (если метки в достаточной степени отличаются от меток других типов записи).Выражение записи заключено в фигурные скобки ({ }).В следующем коде показано выражение записи, инициализирующее запись с тремя элементами с плавающей запятой с метками x, y и z.
let mypoint = { x = 1.0; y = 1.0; z = -1.0; }
Не используйте сокращенную форму, если может существовать другой тип, имеющий такие же метки.
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; }
Метки последнего объявленного типа имеют приоритет над метками ранее объявленного типа, поэтому в предыдущем примере mypoint3D определяется как Point3D.Можно явно указать тип записи, как в следующем коде.
let myPoint1 = { Point.x = 1.0; y = 1.0; z = 0.0; }
Методы могут определяться как для типов записей, так и для типов классов.
Создание записей с помощью выражений записей
Записи можно инициализировать, используя метки, которые определяются в записи.Выражение, обеспечивающее такую инициализацию, называется выражением записи.Используйте скобки для обрамления выражения записи и точку с запятой в качестве разделителя.
В следующем примере показано, как создать запись.
type MyRecord = {
X: int;
Y: int;
Z: int
}
let myRecord1 = { X = 1; Y = 2; Z = 3; }
Точки с запятой после последнего поля выражения записи и в определении типа являются необязательными, независимо от того, расположены ли все поля в одной строке.
При создании записи необходимо задать значения для каждого поля.Для любого поля нельзя ссылаться на значения других полей в выражении инициализации.
В следующем коде тип записи myRecord2 определяется на основе имен полей.Дополнительно можно задать имя типа явно.
let myRecord2 = { MyRecord.X = 1; MyRecord.Y = 2; MyRecord.Z = 3 }
Если требуется скопировать существующую запись и, возможно, изменить некоторые значения полей, удобно использовать другую форму конструкции записи.Это показано в следующей строке кода.
let myRecord3 = { myRecord2 with Y = 100; Z = 2 }
Эта форма выражения записи называется формой копирования и обновления выражения записи.
По умолчанию записи являются неизменяемыми; однако можно легко создать измененные записи с помощью операции копирования и обновить выражение.Можно также в явном виде задать изменяемое поле.
type Car = {
Make : string
Model : string
mutable Odometer : int
}
let myCar = { Make = "Fabrikam"; Model = "Coupe"; Odometer = 108112 }
myCar.Odometer <- myCar.Odometer + 21
Не используйте атрибута DefaultValue с полей записи.Лучшим подходом является определение экземпляров по умолчанию записи с полями, которые инициализируются значениями по умолчанию и использовать копию и обновить записи выражения для набора полей, которые отличаются от значений по умолчанию.
// 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 }
Соответствие шаблону с записями
С записями можно использовать соответствие шаблону.Можно в явном виде задать некоторые поля, а для других полей указать переменные, значения которым будут присвоены при нахождении соответствия.Это показано в следующем примере кода.
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 }
Результат выполнения этого кода следующий.
Point is at the origin.
Point is on the x-axis. Value is 100.000000.
Point is at (10.000000, 0.000000, -1.000000).
Различия между записями и классами
Поля записей отличаются от классов в том отношении, что они автоматически представляются как свойства и используются при создании и копировании записей.Построение записей также отличается от построения класса.В типе записи нельзя определить конструктор.Вместо этого применяется синтаксис конструкции, описанный в данной теме.Классы не имеют непосредственной связи между параметрами, полями и свойствами конструктора.
Как и типы объединений и структур, записи обладают семантикой структурного равенства.Классы обладают семантикой равенства ссылок.Это продемонстрировано в следующем примере.
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."
Если написать одинаковый код с классами, два объекта класса не будут равными, так как два значения будут представлять два объекта в куче и будут сравниваться только адреса (если только тип класса не переопределил метод System.Object.Equals).