Поля и хранение состояния

Последнее обновление: 11.12.2023

Для хранения состояния объекта в типах F# могут использоваться поля. Есть несколько способов определения полей.

let-привязка значений в классах

Как и в целом в модулях, в F# внутри классов с помощью оператора let можно определять значения, которые будут хранить некоторые данные. Но стоит учитывать, что такие значения доступны только внутри своего класса:

type Person (name, surname, age) = 

    let fullName = $"{name} {surname}"                      // приватное поле
    member _.Print() = printfn $"Name: {fullName}  Age: { age }"
 
let tom = Person("Tom", "Smith", 37)

tom.Print()         // Name: Tom Smith  Age: 37

Здесь определено приватное поле fullName, которое хранит полное имя пользователя на основе его имени и фамилии. Вне класса к этому полю мы обратиться не можем.

При этом подобные поля также могут изменять свои данные при определении с оператором mutable:

type Person (name, surname, _age) = 

    let fullName = $"{name} {surname}"
    let mutable age = _age              // изменяемое поле age

    member _.Print() = printfn $"Name: {fullName}  Age: { age }"
    member _.Grow() =  age <- age + 1
 
let tom = Person("Tom", "Smith", 37)
tom.Print()     // Name: Tom Smith  Age: 37

tom.Grow()
tom.Print()     // Name: Tom Smith  Age: 38

Здесь определено изменяемое поле age, начальное значение которого равно значению параметра конструктора _age. Далее мы можем изменять значение этого поля на единицу с помощью метода Grow(). Консольный вывод:

Name: Tom Smith  Age: 37
Name: Tom Smith  Age: 38

Определение полей с помощью оператора val

Другой способ определения полей в классе представляет оператор val. Формальный синтаксис его применения:

val [ mutable ] [ модификатор_доступа ] имя_поля : тип_данных

В отличие от let-полей val-поля могут иметь модификатор доступа, соответственно поля с модификаторами public/internal могут быть доступны вне класса. По умолчанию val-поля без модификаторов доступны вне класса.

val-поля в классе с первичным конструктором

Может показаться, что val-поля гораздо проще и удобнее использовать, чем let-поля, но на самом деле их применение обставлено рядом ограничений. Например, определим пару таких полей:

type Person(name, surname, _age) = 

    [<DefaultValue>] val mutable fullName: string
    [<DefaultValue>] val mutable age: int

    member this.Print() = printfn $"Name: {this.fullName}  Age: { this.age }"
    member this.Grow() =  this.age <- this.age + 1
 
let tom = Person("Tom", "Smith", 37)
tom.Print()     // Name:  Age: 0

tom.Grow()
tom.Print()     // Name:  Age: 1

tom.age <- tom.age + 4
tom.Print()     // Name:  Age: 5

Здесь определены два поля: fullName и age. При определении полей мы НЕ можем им тут же присвоить некоторое значение, например:

val mutable fullName: string = $"{name} {surname}"		// это работать не будет
val mutable age: int = 6		// это работать не будет

Для инициализации полей нужно использовать другие способы.

Следующее ограничение: если класс имеет первичный конструктор (как в случае выше), то val-поля должны иметь атрибут [<DefaultValue>]. Этот атрибут устанавливает для таких полей значение по умолчанию. Так, для числовых данных это число 0, а для типа string (как и классов) это специальное значение null, которое указывает на отсутствие значения.

Еще одно ограничение - в этом случае val-поля должны быть изменяемыми, то есть определены с оператором mutable.

В итоге при создании объекта Person его поле fullName фактически не будет иметь никакого значения (при выводе на консоль мы увидим пустую строку), а поле age будет иметь значение 0. Консольный вывод программы:

Name:  Age: 0
Name:  Age: 1
Name:  Age: 5

Но что если мы хотим все-таки присвоить этим полям некоторые значения, например, значения первичного конструктора? В этом случае мы можем определить специальный метод, который будет выполять подобную работу:

type Person(name, surname, _age) = 

    [<DefaultValue>] val mutable fullName: string
    [<DefaultValue>] val mutable age: int

    member this.SetValues() =  
        this.fullName <- $"{name} {surname}"
        this.age <- _age

    member this.Print() = printfn $"Name: {this.fullName}  Age: { this.age }"
    member this.Grow() =  this.age <- this.age + 1
 
let tom = Person("Tom", "Smith", 37)
tom.SetValues()
tom.Print()     // Name: Tom Smith  Age: 37

tom.Grow()
tom.Print()     // Name: Tom Smith  Age: 38

tom.age <- tom.age + 4
tom.Print()     // Name: Tom Smith  Age: 42

В данном случае определен метод SetValues, который передает val-полям значения параметров первичного конструктора. При этом значения для полей также можно передавать и через параметры метода или брать их из let-полей (при наличии таковых). Однако кроме определения нам надо еще вызывать этот метод в программе. Консольный вывод:

Name: Tom Smith Age: 37
Name: Tom Smith Age: 38
Name: Tom Smith Age: 42

Классы без первичного конструктора

В классах без первичного конструктора val-поля могут быть неизменяемыми, для них не надо определять атрибут [<DefaultValue>], однако нам надо дополнительно с помощью конструктора определить логику их инициализации:

type Person = 

    val fullName: string 
    val mutable age: int

    new (name, surname, _age) = { fullName = $"{name} {surname}"; age = _age;}

    member this.Print() = printfn $"Name: {this.fullName}  Age: { this.age }"
    member this.Grow() =  this.age <- this.age + 1
 
let tom = Person("Tom", "Smith", 37)
tom.Print()     // Name: Tom Smith  Age: 37

tom.Grow()
tom.Print()     // Name: Tom Smith  Age: 38

tom.age <- tom.age + 5
tom.Print()     // Name: Tom Smith  Age: 43

Здесь изменяемым полем является только поле age, а для инициализации полей определен специальный конструктор, который называется конструктором типа:

new (name, surname, _age) = { fullName = $"{name} {surname}"; age = _age;}

Обратите внимание, как выглядит инициализация - тело конструктора обернуто в фигурные скобки, а присвоение значений полям отделяется точкой с запятой.

Модификаторы доступа

Если надо установить модификатор доступа, то он указывается перед именем поля и после операторов let/mutable:

val internal fullName: string 
val mutable private age: int
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850