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