什么是依赖打字?

时间:2012-02-18 04:58:05

标签: functional-programming dependent-type

有人可以向我解释依赖打字吗?我在Haskell,Cayenne,Epigram或其他函数式语言方面缺乏经验,因此您可以使用的术语越简单,我就越感激它!

4 个答案:

答案 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按类型索引的类型系列

  •   
     

看一下这个清单,很明显有一种可能性   我们还没有考虑过:按术语索引的类型系列。这个   抽象形式也得到了广泛的研究   依赖类型的量规。