Что такое зависимая набор?
-
27-10-2019 - |
Вопрос
Может ли кто -нибудь объяснить мне иждивенную набор? У меня мало опыта в Haskell, Cayenne, Epigram или других функциональных языках, поэтому, чем проще вы можете использовать, тем больше я буду ценить это!
Решение
Рассмотрим это: во всех приличных языках программирования вы можете написать функции, например,
def f(arg) = result
Здесь, f
берет ценность arg
и вычисляет значение result
. Анкет Это функция от значений до значений.
Теперь некоторые языки позволяют вам определить полиморфные (aka generic) значения:
def empty<T> = new List<T>()
Здесь, empty
принимает тип T
и вычисляет значение. Это функция от типов до значений.
Обычно вы также можете иметь определения общего типа:
type Matrix<T> = List<List<T>>
Это определение принимает тип, и оно возвращает тип. Его можно рассматривать как функцию от типов до типов.
Так много для того, что предлагают обычные языки. Язык называется зависимо напечатанным, если он также предлагает 4 -ю возможность, а именно определение функций от значений до типов. Или, другими словами, параметризация определения типа по значению:
type BoundedInt(n) = {i:Int | i<=n}
У некоторых основных языков есть какая -то поддельная форма этого, которую не следует запутать. Например, в C ++ шаблоны могут принимать значения в качестве параметров, но они должны быть постоянными временем компиляции при применении. Не так на действительно зависимого языка. Например, я мог бы использовать приведенный выше тип:
def min(i : Int, j : Int) : BoundedInt(j) =
if i < j then i else j
Здесь тип результата функции зависит от На фактическом значении аргумента j
, таким образом, терминология.
Другие советы
Если вы узнаете C ++, легко дать мотивирующий пример:
Допустим, у нас есть тип контейнера и два экземпляра
typedef std::map<int,int> IIMap;
IIMap foo;
IIMap bar;
и рассмотрим этот фрагмент кода (вы можете предположить, что Foo не пустые):
IIMap::iterator i = foo.begin();
bar.erase(i);
Это очевидный мусор (и, вероятно, повреждает структуры данных), но он будет хорошо проверять, так как «Итератор в Foo» и «Итератор в бар»-это тот же тип,-это один и тот же тип. IIMap::iterator
, хотя они совершенно несовместимы семантически.
Проблема в том, что тип итератора не должен зависеть только от контейнера тип Но на самом деле в контейнере объект, т.е. это должен быть «нестатический тип члена»:
foo.iterator i = foo.begin();
bar.erase(i); // ERROR: bar.iterator argument expected
Такая особенность, способность выражать тип (foo.iterator), которая зависит от термина (Foo), - это именно то, что означает зависимое печатание.
Причина, по которой вы не часто видите эту функцию, заключается в том, что она открывает большую банку червей: вы внезапно оказываетесь в ситуациях, где проверить во время компиляции, являются ли два типа одинаковыми, вам придется доказать два выражения эквивалентны (всегда дадут одинаковое значение во время выполнения). В результате, если вы сравниваете Википедию Список зависимо напечатанных языков с этими Список пособий по теореме Вы можете заметить подозрительное сходство. ;-)
Зависимые типы позволяют большему набору логические ошибки быть исключенным в Время компиляции. Анкет Чтобы проиллюстрировать это, рассмотрим следующую спецификацию по функции f
:
Функция
f
должен взять только даже целые числа в качестве входных данных.
Без зависимых типов вы можете сделать что -то вроде этого:
def f(n: Integer) := {
if n mod 2 != 0 then
throw RuntimeException
else
// do something with n
}
Здесь компилятор не может обнаружить, если n
Действительно, даже с точки зрения компилятора. Следующее выражение в порядке:
f(1) // compiles OK despite being a logic error!
Эта программа будет работать, а затем бросает исключение во время выполнения, то есть ваша программа имеет логическую ошибку.
Теперь зависимые типы позволяют вам быть намного более выразительными и позволили бы вам написать что -то подобное:
def f(n: {n: Integer | n mod 2 == 0}) := {
// do something with n
}
Здесь n
имеет зависимый тип {n: Integer | n mod 2 == 0}
. Анкет Это может помочь прочитать это вслух как
n
является членом набора целых чисел, так что каждое целое число делится на 2.
В этом случае компилятор обнаружит во время компиляции логическую ошибку, когда вы передали нечетное число f
и предотвратит выполнение программы в первую очередь:
f(1) // compiler error
Вот иллюстративный пример с использованием 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
:
def f(n: Integer)(implicit proof: n.IsEven.type)
^ ^
| |
value value
Мы говорим тип n.IsEven.type
зависит от ценность n
Отсюда и термин зависимые типы.
Цитирование типов книг и языков программирования (30,5):
Большая часть этой книги была связана с формализацией механизмов абстракции различных видов. В просто напечатанном лямбда-калькулус мы формализовали операцию по сбору термина и абстрагируют SUBERM, давая функцию, которая впоследствии может быть создана путем применения его к разным терминам. В системе
F
, Мы рассмотрели операцию по сбору термина и абстрагируют тип, давая термин, который может быть создан путем применения его к различным типам. Вλω
, Мы повторили механизмы простого типичного Lambda-Calculus «на один уровень вверх», выводя тип и абстрагируя субэкспрессию, чтобы получить оператора типа, который впоследствии можно создать путем применения к различным типам. Удобный способ мышления обо всех этих формах абстракции связан с семьями выражений, индексируемых другими выражениями. Обычная абстракция лямбдаλx:T1.t2
Семья терминов[x -> s]t1
Индексируется по терминамs
. Анкет Точно так же типовая абстракцияλX::K1.t2
Семейство терминов, индексируемые типами, а оператор типа - это семейство типов, индексированных типами.
λx:T1.t2
Семейство терминов, индексированные терминами
λX::K1.t2
Семейство терминов, индексированные типами
λX::K1.T2
Семейство типов, индексированных типамиГлядя на этот список, ясно, что есть одна возможность, которую мы еще не рассмотрели: семьи типов, индексированные по терминам. Эта форма абстракции также была тщательно изучена под рубрикой зависимых типов.