有人可以向我解释依赖打字吗?我在Haskell,Cayenne,Epigram或其他函数式语言方面缺乏经验,因此您可以使用的术语越简单,我就越感激它!
答案 0 :(得分:87)
考虑一下:在所有体面的编程语言中,您都可以编写函数,例如
def f(arg) = result
此处,f
获取值arg
并计算值result
。它是从值到值的函数。
现在,某些语言允许您定义多态(也称为通用)值:
def empty<T> = new List<T>()
此处,empty
采用类型T
并计算值。它是从类型到值的函数。
通常,您也可以使用泛型类型定义:
type Matrix<T> = List<List<T>>
此定义采用类型并返回类型。它可以被视为从类型到类型的函数。
普通语言提供的内容非常多。如果一种语言也提供第四种可能性,即从值到类型定义函数,则称其为依赖类型。或者换句话说,通过值参数化类型定义:
type BoundedInt(n) = {i:Int | i<=n}
一些主流语言有一些假的形式,不要混淆。例如。在C ++中,模板可以将值作为参数,但在应用时它们必须是编译时常量。在一种真正依赖类型的语言中并非如此。例如,我可以使用上面的类型:
def min(i : Int, j : Int) : BoundedInt(j) =
if i < j then i else j
这里,函数的结果类型取决于实际参数值j
,因此是术语。
答案 1 :(得分:12)
如果您碰巧了解C ++,那么很容易提供一个激励性的例子:
我们说我们有一些容器类型和两个实例
typedef std::map<int,int> IIMap;
IIMap foo;
IIMap bar;
并考虑此代码片段(您可能认为foo非空):
IIMap::iterator i = foo.begin();
bar.erase(i);
这显然是垃圾(并且可能会破坏数据结构),但它会进行类型检查,因为&#34;迭代到foo&#34;和&#34;迭代到吧&#34;是同一类型IIMap::iterator
,即使它们在语义上完全不兼容。
问题是迭代器类型不应该只依赖于容器类型,而实际上只取决于容器对象,即它应该是& #34;非静态成员类型&#34;:
foo.iterator i = foo.begin();
bar.erase(i); // ERROR: bar.iterator argument expected
这样的特征,能够表达依赖于术语(foo)的类型(foo.iterator),正是依赖类型的意思。
你不经常看到这个功能的原因是因为它打开了一大堆蠕虫:你突然遇到这样的情况:在编译时检查两种类型是否相同,你最终必须证明两个表达式是等价的(在运行时总是会产生相同的值)。因此,如果您将维基百科的list of dependently typed languages与其list of theorem provers进行比较,您可能会发现可疑的相似性。 ; - )
答案 2 :(得分:8)
依赖类型允许在编译时删除更大的logic errors集。为了说明这一点,请考虑以下关于函数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
答案 3 :(得分:5)
引用书籍类型和编程语言(30.5):
本书的大部分内容都与形式化抽象有关 各种机制。在简单类型的lambda演算中,我们 正式化了一个术语的运作并抽象出一个 subterm,产生一个稍后可以实例化的函数 将它应用于不同的术语。在系统
F
中,我们考虑了 采用一个术语并抽象出一个类型的术语,产生一个术语 可以通过将其应用于各种类型来实例化。在λω
,我们 概括了简单类型lambda演算的机制“一 升级,“采取一种类型并抽象出一个子表达式来获得 一个类型运算符,以后可以通过应用它来实例化 不同种类。一种方便的思考所有这些形式的方式 抽象是表达式的家族,由其他人索引 表达式。普通的lambda抽象λx:T1.t2
是一个家族 条款[x -> s]t1
以条款s
编制索引。同样,一种类型的抽象λX::K1.t2
是由类型索引的术语族和类型运算符 是一个由类型索引的类型族。
λx:T1.t2
以术语索引的术语系列
λX::K1.t2
以类型索引的术语系列
λX::K1.T2
按类型索引的类型系列看一下这个清单,很明显有一种可能性 我们还没有考虑过:按术语索引的类型系列。这个 抽象形式也得到了广泛的研究 依赖类型的量规。