Примечание: Типовое состояние было удалено из Rust, осталась только ограниченная версия (отслеживание неинициализировано и перемещено из переменных). Смотрите мою заметку в конце.
Мотивация TypeState заключается в том, что типы являются неизменяемыми, однако некоторые их свойства являются динамическими для каждой переменной.
Поэтому идея состоит в том, чтобы создать простые предикаты о типе и использовать анализ Control-Flow, выполняемый компилятором по многим другим причинам, для статического декорирования типа этими предикатами.
Эти предикаты фактически не проверяются самим компилятором, это может быть слишком обременительно, вместо этого компилятор просто рассуждает в терминах графа.
В качестве простого примера вы создаете предикат even
, который возвращает true
, если число четное.
Теперь вы создаете две функции:
halve
, который действует только на even
числа
double
, которые берут любое число и возвращают even
число.
Обратите внимание, что тип number
не изменяется, вы не создаете тип evennumber
и не дублируете все те функции, которые ранее действовали на number
. Вы просто пишете number
с помощью предиката even
.
Теперь давайте построим несколько графиков:
a: number -> halve(a) #! error: `a` is not `even`
a: number, even -> halve(a) # ok
a: number -> b = double(a) -> b: number, even
Просто, не правда ли?
Конечно, становится немного сложнее, когда у вас есть несколько возможных путей:
a: number -> a = double(a) -> a: number, even -> halve(a) #! error: `a` is not `even`
\___________________________________/
Это показывает, что вы рассуждаете в терминах наборов предикатов:
- при объединении двух путей новый набор предикатов является пересечением наборов предикатов, заданных этими двумя путями
Это может быть дополнено общим правилом функции:
- для вызова функции должен быть удовлетворен набор предикатов, которые ей необходимы
- после вызова функции выполняется только набор предикатов, которые она установила (примечание: аргументы, принятые по значению, не затрагиваются)
И, таким образом, строительный блок TypeState in Rust :
check
: проверяет, что предикат удерживается, если он не fail
, в противном случае добавляет предикат к набору предикатов
Обратите внимание, что поскольку Rust требует, чтобы предикаты были чистыми функциями, он может исключить избыточные вызовы check
, если сможет доказать, что предикат уже выполняется в этой точке.
Чего не хватает в Typestate, так это простоты компоновки.
Если вы внимательно прочитаете описание, вы заметите следующее:
- после вызова функции выполняется только набор предикатов, которые она установила (примечание: аргументы, принимаемые по значению, не затрагиваются)
Это означает, что предикаты для типов сами по себе бесполезны, утилита создается из аннотирующих функций. Поэтому введение нового предиката в существующую кодовую базу является занудой, так как существующие функции необходимо пересмотреть и настроить, чтобы объяснить, нужно ли им сохранять / сохранять инвариант.
И это может привести к дублированию функций с экспоненциальной скоростью, когда всплывают новые предикаты: к сожалению, предикаты не являются компонуемыми. Сама проблема дизайна, которую они должны были решить (распространение типов, то есть функций), похоже, не решена.