Record (записи) представляет типы, которые позволяет сгруппировать именнованные значения. То есть каждому значению внутри внутри record сопоставляется некоторое имя.
Для определения record применяется оператор type, после которого идет имя записи:
type имя_записи =
{ метка1 : тип_метки1
метка2 : тип_метки2
..........................
меткаN : тип_меткиN
}
Внутри фигурных скобок указываются метки/свойства записи в виде пар имя - тип метки, которые разделены двоеточием. Если пары имя-тип размещаются на одной строке, то они разделяются точкой с запятой. Например, определим простейшую запись:
type person = {Name:string; Age:int}
Данная запись называется person. Она определяет два свойства - Name, которое представляет строку, и Age, которое представляет число.
Для определения значения подобного типа применяются фигурные скобки, внутри которых перечисляются свойства их значения
После этого мы можем получить значение этого типа:
type person = {Name:string; Age:int}
let tom = {Name="Tom"; Age=39}
Здесь определено одно значение типа person - значение tom, в котором свойство Name равно "Tom", а Age равно 39.
Для обращения к меткам записи применяется точечная нотация:
запись.свойство
Например:
type person = {Name:string; Age:int}
let tom = {Name= "Tom"; Age=39}
printfn $"Name {tom.Name} Age:{tom.Age}" // получение значений
Стоит отметить, что по умолчанию значения меток записей неизменяемы. Чтобы их можно было изменить, они должны быть определены с оператором mutable:
type person = {
Name:string
mutable Age:int // изменяемое метка
}
let tom = {Name= "Tom"; Age=39}
tom.Age <- 22
printfn $"Name: {tom.Name} Age: {tom.Age}" // Name: Tom Age: 22
Выше было продемонстрировано, как можно изменять отдельные свойства записей. Однако это не всегда желательно. В этом случае мы можем создать новую запись на основе существующей, установив значения для определенных свойств:
type person = { Name:string; Age:int }
let tom = {Name= "Tom"; Age=39}
let bob = {tom with Name="Bob"} // создаем запись bob на основе tom
printfn $"Name: {bob.Name} Age: {bob.Age}" // Name: Bob Age: 39
В данном случае создаем значение bob на основе значения tom, при этом устанавливая другое значение для свойства Name.
Кроме меток записи могут определять дополнительные свойства и методы, которые предваряются ключевым словом member. Например:
type Person =
{ Name:string
Age:int }
member this.RecordName = "Person"
member this.Print() =
printfn "Name: %s" this.Name
printfn "Age: %d" this.Age
let tom = {Name= "Tom"; Age=39}
tom.Print() // Name: Tom Age: 39
printfn "Type: %s" tom.RecordName // Type: Person
В данном случае дополнительно определено два члена записи - свойство RecordName и функция Print. RecordName представляет название типа записи, а функция Print выводит значения записи на консоль. Причем эти компоненты определены как компоненты экземпляра типа - через ключевое слово this, которое указывает на текущий объект записи.
member this.RecordName member this.Print()
Благодаря этому внутри функции Print мы можем обратиться к значениям текущей записи опять же через слово this, а при обращении к этим компонентам
применяется имя объекта записи (в примере выше объекта tom):
let tom = {Name= "Tom"; Age=39}
tom.Print() // Name: Tom Age: 39
printfn "Type: %s" tom.RecordName // Type: Person
Стоит отметить, что свойства записей неизменяемы, и в примере выше мы бы не смогли изменить значение свойства RecordName. Однако в данном случае значение этого свойства не зависит от конкретного объекта записи Person - оно будет для всех одинаково. В этом случае мы могли бы сделать его статическим:
type Person =
{ Name:string
Age:int }
static member RecordName = "Person" // статическое свойство
member this.Print() =
printfn "Name: %s" this.Name
printfn "Age: %d" this.Age
printfn "Type: %s" Person.RecordName // обращение через имя типа
В данном случае свойство RecordName определено как статическое - с ключевым словом static, поэтому оно относится не к отдельным экземплярам Person, а ко всему типа Person в целом. Соотвественно для обращения к нему применяется имя типа, а не имя объекта:
Person.RecordName
По умолчанию значения типов Record представляют ссылочные типы, которые размещаются в хипе. Однако мы также можем определить их как структуры, которые будут размещаться в стеке.
Для этого перед типом указывается атрибут [
[<Struct>]
type Person =
{ Name:string
Age:int }
member this.Print() = printfn $"Name: {this.Name} Age: {this.Age}"
let tom = {Name= "Tom"; Age=39}
tom.Print() // Name: Tom Age: 39