什么是依赖打字?
有人可以解释依赖打字给我吗? 我在Haskell,Cayenne,Epigram或其他函数式语言方面经验不多,所以您可以使用的术语越简单,我越会感激!
考虑到这一点:在所有体面的编程语言,你可以编写函数,例如
def f(arg) = result
这里, f
取值为arg
并计算值result
。 这是一个从价值到价值的function。
现在,一些语言允许你定义多态(又名通用)值:
def empty<T> = new List<T>()
这里, empty
取一个typesT
并计算一个值。 这是一个从types到值的函数。
通常,你也可以有通用的types定义:
type Matrix<T> = List<List<T>>
这个定义需要一个types,并返回一个types。 它可以被看作是从types到types的函数。
对于普通语言提供的东西太多了。 如果一种语言也提供了第四种可能性,那么称它为依赖types,即定义从值到types的函数。 换句话说,通过一个值参数化一个types定义:
type BoundedInt(n) = {i:Int | i<=n}
一些主stream的语言有一些这样的假的forms是不会混淆的。 例如,在C ++中,模板可以将值作为参数,但是在应用时它们必须是编译时常量。 不是一个真正的依赖型语言。 例如,我可以像这样使用上面的types:
def min(i : Int, j : Int) : BoundedInt(j) = if i < j then i else j
编辑:这里,函数的结果types取决于实际的参数值j
,因此是术语。
如果你碰巧知道C ++,很容易提供一个激励的例子:
假设我们有一些容器types和两个实例
typedef std::map<int,int> IIMap; IIMap foo; IIMap bar;
并考虑这个代码片段(你可以假设foo是非空的):
IIMap::iterator i = foo.begin(); bar.erase(i);
这是显而易见的垃圾(可能会破坏数据结构),但它会types检查就好了,因为“迭代器到foo”和“迭代器到吧”是相同的types, IIMap::iterator
,即使它们完全不兼容语义。
问题是一个迭代器types不应该仅仅取决于容器types,而是实际上取决于容器对象 ,即它应该是一个“非静态成员types”:
foo.iterator i = foo.begin(); bar.erase(i); // ERROR: bar.iterator argument expected
这样一个特征,即expression一个依赖于一个词(foo)的types(foo.iterator)的能力正是依赖types意味着什么的。
你不经常看到这个特性的原因是因为它打开了一大堆蠕虫:你在编译时检查两种types是否相同的情况下,最终会出现两个expression式是等价的(在运行时总会产生相同的值)。 因此,如果您将维基百科的依赖types语言 列表与定理certificate列表进行比较,您可能会注意到可疑的相似性。 😉
引用“书本types和编程语言”(30.5):
这本书的大部分内容都涉及forms化各种抽象机制。 在简单types的lambda演算中,我们将抽取一个术语和抽象出一个子项的操作forms化,产生一个函数,稍后可以通过将其应用于不同的术语来实例化该函数。 在系统
F
,我们考虑了抽取一个types的术语和抽象types的操作,产生一个可以通过将其应用于各种types来实例化的术语。 在λω
,我们概括了简单types的lambda演算“上一层”的机制,取一个types并抽象出一个子expression式,以获得一个types运算符,稍后可以通过将其应用于不同types来实例化。 所有这些抽象forms的一种便利的思维方式就是用其他expression方式进行索引的expression系列。 一个普通的lambda抽象λx:T1.t2
是由项s
指数化的一个项[x -> s]t1
。 类似地,types抽象λX::K1.t2
是由types索引的一系列术语,并且types操作符是按types索引的types族。
λx:T1.t2
系列术语索引
λX::K1.t2
系列由types索引的术语
λX::K1.T2
types索引的types看这个清单,显然有一种可能性,我们还没有考虑到:按条款索引的types的家庭。 这种抽象forms也在依赖types的标题下被广泛研究。
相关types使编译时可以消除更多的逻辑错误 。 为了说明这一点,请考虑函数f
的以下规范:
函数
f
只能用偶数作为input。
没有依赖types,你可能会这样做:
def f(n: Integer) := { if n mod 2 != 0 then throw RuntimeException else // do something with n }
这里编译器无法检测到n
是否确实是偶数,也就是说,从编译器的angular度来看下面的expression式是可以的:
f(1) // compiles OK despite being a logic error!
这个程序会运行,然后在运行时抛出exception,也就是说,你的程序有一个逻辑错误。
现在,依赖types使您能够更加富有performance力,并使您能够编写如下所示的内容:
def f(n: {n: Integer | n mod 2 == 0}) := { // do something with n }
这里n
是依赖types{n: Integer | n mod 2 == 0}
{n: Integer | n mod 2 == 0}
。 这可能有助于大声读出这个
n
是一组整数的成员,每个整数可以被2整除。
在这种情况下,编译器会在编译时检测到一个逻辑错误,在那里你传递了一个奇数给f
并且会阻止程序首先被执行:
f(1) // compiler error