Может ли кто-нибудь объяснить мне зависимую типизацию? У меня мало опыта работы с Haskell, Cayenne, Epigram или другими функциональными языками, поэтому чем проще термины, которые вы можете использовать, тем больше я буду признателен!
84
Ответы:
Учтите: на всех достойных языках программирования вы можете писать функции, например
Здесь
f
принимает значениеarg
и вычисляет значениеresult
. Это функция от значений к значениям.Теперь некоторые языки позволяют определять полиморфные (также известные как общие) значения:
Здесь
empty
принимает типT
и вычисляет значение. Это функция от типов к значениям.Обычно у вас также могут быть определения универсального типа:
Это определение принимает тип и возвращает тип. Его можно рассматривать как функцию от типов к типам.
Вот и все, что предлагают обычные языки. Язык называется зависимо типизированным, если он также предлагает четвертую возможность, а именно определение функций от значений к типам. Или, другими словами, параметризация определения типа над значением:
В некоторых основных языках есть фальшивка, которую не следует путать. Например, в C ++ шаблоны могут принимать значения в качестве параметров, но при применении они должны быть константами времени компиляции. Не так в языке с истинно зависимой типизацией. Например, я мог бы использовать приведенный выше тип следующим образом:
Здесь тип результата функции зависит от фактического значения аргумента и
j
, следовательно, от терминологии.источник
BoundedInt
пример не является типом уточнения? Это «довольно близкие», но не совсем те «зависимые типы», которые, например, Идрис упоминает первыми в учебнике по типу типов.Зависимые типы позволяют устранить больший набор логических ошибок во время компиляции . Чтобы проиллюстрировать это, рассмотрим следующую спецификацию функции
f
:Без зависимых типов вы могли бы сделать что-то вроде этого:
Здесь компилятор не может определить,
n
действительно ли он четный, то есть с точки зрения компилятора следующее выражение приемлемо:Эта программа будет запускаться, а затем генерировать исключение во время выполнения, то есть ваша программа имеет логическую ошибку.
Теперь зависимые типы позволяют вам быть более выразительными и позволяют писать что-то вроде этого:
Здесь
n
зависимого типа{n: Integer | n mod 2 == 0}
. Было бы полезно прочитать это вслух какВ этом случае компилятор обнаружит во время компиляции логическую ошибку, в которой вы передали нечетное число,
f
и в первую очередь предотвратит выполнение программы:Вот иллюстративный пример использования зависящих от пути типов Scala того, как мы можем попытаться реализовать функцию,
f
удовлетворяющую такому требованию:case class Integer(v: Int) { object IsEven { require(v % 2 == 0) } object IsOdd { require(v % 2 != 0) } } def f(n: Integer)(implicit proof: n.IsEven.type) = { // do something with n safe in the knowledge it is even } val `42` = Integer(42) implicit val proof42IsEven = `42`.IsEven val `1` = Integer(1) implicit val proof1IsOdd = `1`.IsOdd f(`42`) // OK f(`1`) // compile-time error
Ключ в том, чтобы заметить, как значение
n
появляется в типе значения,proof
а именноn.IsEven.type
:Мы говорим, что тип
n.IsEven.type
зависит от значения,n
отсюда и термин зависимые типы .источник
f(random())
к ошибке компиляции?f
к какому-либо выражению потребовало бы, чтобы компилятор (с вашей помощью или безrandom()
нее ) обеспечивал, чтобы выражение всегда было четным, и для этого не существует такого доказательства (поскольку оно может быть на самом деле нечетным), поэтомуf(random())
компиляция не удастся.Если вы знакомы с C ++, легко привести мотивирующий пример:
Допустим, у нас есть тип контейнера и два его экземпляра
typedef std::map<int,int> IIMap; IIMap foo; IIMap bar;
и рассмотрим этот фрагмент кода (вы можете предположить, что foo не пусто):
Это очевидный мусор (и, вероятно, повреждает структуры данных), но он отлично справится с проверкой типов, поскольку «итератор в foo» и «итератор в бар» относятся к одному типу,
IIMap::iterator
хотя они полностью несовместимы семантически.Проблема в том, что тип итератора не должен зависеть только от типа контейнера, но фактически от объекта контейнера , то есть он должен быть «нестатическим типом члена»:
foo.iterator i = foo.begin(); bar.erase(i); // ERROR: bar.iterator argument expected
Такая особенность, способность выражать тип (foo.iterator), который зависит от термина (foo), - это именно то, что означает зависимая типизация.
Причина, по которой вы не часто видите эту функцию, заключается в том, что она открывает большую банку червей: вы внезапно попадаете в ситуации, когда для проверки во время компиляции, являются ли два типа одинаковыми, вам в конечном итоге придется доказывать два выражения эквивалентны (всегда будут давать одно и то же значение во время выполнения). В результате, если вы сравните список языков с зависимой типизацией в Википедии со списком средств доказательства теорем, вы можете заметить подозрительное сходство. ;-)
источник
Цитата из книги Типы и языки программирования (30.5):
источник