2010-12-11 99 views
99

根据this question,Scala的类型系统是Turing complete。有哪些资源可以使新手充分利用类型级编程的力量?Scala类型编程资源

这里是我到目前为止发现的资源:

这些资源是巨大的,但我觉得我错过了基础知识,所以没有坚实的基础来构建。例如,哪里有对类型定义的介绍?我可以在类型上执行哪些操作?

有没有什么好的入门资源?

+5

社区wiki? – 2010-12-11 09:01:37

+0

就我个人而言,我发现有人想要在Scala中进行类型编程已经知道如何在Scala中编程非常合理。即使这意味着我不理解你链接到的那些文章的一个词:-) – 2010-12-11 12:58:17

回答

133

概述

类型层次的编程与传统,价值层次的编程有许多相似之处。但是,与运行时发生计算的值级编程不同,在类型级编程中,计算发生在编译时。我将尝试在价值层次的编程和类型层次的编程之间进行比较。

范式

有在类型级别的编程两个主要范式:“面向对象”和“功能”。从这里链接到的大多数例子遵循面向对象的范例。

在面向对象范型级编程的一个很好的,很简单的例子可以在apocalisp的implementation of the lambda calculus发现,这里复制:

// Abstract trait 
trait Lambda { 
    type subst[U <: Lambda] <: Lambda 
    type apply[U <: Lambda] <: Lambda 
    type eval <: Lambda 
} 

// Implementations 
trait App[S <: Lambda, T <: Lambda] extends Lambda { 
    type subst[U <: Lambda] = App[S#subst[U], T#subst[U]] 
    type apply[U] = Nothing 
    type eval = S#eval#apply[T] 
} 

trait Lam[T <: Lambda] extends Lambda { 
    type subst[U <: Lambda] = Lam[T] 
    type apply[U <: Lambda] = T#subst[U]#eval 
    type eval = Lam[T] 
} 

trait X extends Lambda { 
    type subst[U <: Lambda] = U 
    type apply[U] = Lambda 
    type eval = X 
} 

正如在本例中可以看出,面向对象类型级编程的范例如下进行:

  • 第一:用各种抽象类型字段定义一个抽象特征(参见下文抽象字段是什么)。这是一个用于保证所有实现中都存在某些类型字段而不强制执行的模板。在lambda微积分示例中,这对应于trait Lambda,它确保存在以下类型:subst,applyeval
  • 下一页:定义扩展抽象性状subtraits和实施各种抽象类型字段
    • 通常,这些subtraits将与参数进行参数化。在演算例如,亚型是trait App extends Lambda其是参数化与两种类型(ST,两者都必须是Lambda亚型),trait Lam extends Lambda参数与一种类型(T),并trait X extends Lambda(这不是参数化)。
    • 类型字段通常通过引用减法类型参数来实现,有时通过哈希运算符引用其类型字段:#(与点运算符:值为.非常相似)。在lambda微积分示例的特征App中,类型eval按如下实现:type eval = S#eval#apply[T]。这本质上是调用eval类型的特征参数S,并调用apply与参数T对结果。请注意,S保证有一个eval类型,因为该参数指定它是Lambda的子类型。类似地,eval的结果必须具有apply类型,因为它被指定为Lambda的子类型,如抽象特征Lambda中所指定的那样。

的功能范例包括定义许多未在特征组合在一起,参数化类型构造的。

值电平编程和类型级编程的比较

  • abstract class
    • 值级别:abstract class C { val x }
    • 类型级:trait C { type X }
  • 路径依赖类型
    • C.x(引用字段值/功能在对象C X)
    • C#x(在性状ç引用字段类型为x)
  • 函数签名(未实施)
    • 值电平:def f(x:X) : Y
    • type-level:type f[x <: X] <: Y(这称为“类型构造函数”,通常发生在抽象特征中)
  • 函数实现
    • 值级别:def f(x:X) : Y = x
    • 类型级:type f[x <: X] = x
  • 条件句
  • 检查平等
    • 值级别:a:A == b:B
    • 类型级:implicitly[A =:= B]
    • 值级别:在运行时通过一个单元测试发生在JVM(即没有运行时的错误):
      • 在essense是断言:assert(a == b)
    • 类型级:经由一个类型检测在编译器会发生(即没有编译器错误):
      • 在本质上是类型比较:例如implicitly[A =:= B]
      • A <:< B,编译仅当AB
      • A =:= B一个亚型,编译仅当ABB一个亚型的A
      • A <%< B一个亚型,(“可视”)的编译仅当A可见为B(ie存在从A到的B亚型的隐式转换)
      • an example
      • more comparison operators

类型之间转换和值

  • 在许多的前通过特征定义的类型通常都是抽象的和密封的,因此既不能直接实例化,也不能通过匿名子类实例化。因此,在使用某种兴趣类型进行值级计算时,通常使用null作为占位符值:

    • 例如, val x:A = null,其中A的类型是你所关心的
  • 由于类型擦除,参数化类型看起来都一样。此外,(如上所述),您使用的值往往都是null,因此对对象类型进行条件处理(例如,通过匹配语句)是无效的。

诀窍是使用隐式函数和值。基本情况通常是一个隐含的值,递归情况通常是一个隐式函数。事实上,类型级编程大量使用了implicits。

考虑这个例子(taken from metascalaapocalisp):

sealed trait Nat 
sealed trait _0 extends Nat 
sealed trait Succ[N <: Nat] extends Nat 

这里有自然数的皮亚诺编码。也就是说,每个非负整数都有一个类型:0的特殊类型,即_0;并且每个大于零的整数具有形式为Succ[A]的类型,其中A是表示较小整数的类型。例如,代表2的类型将是:Succ[Succ[_0]](继承者对代表零的类型应用两次)。

我们可以别名各种自然数为更方便的参考。例如:

type _3 = Succ[Succ[Succ[_0]]] 

(这是像限定val是一个函数的结果很多)

现在,假设我们要定义一个值级函数def toInt[T <: Nat](v : T)这需要在一个参数值,v,它符合Nat并返回一个整数,表示在v的类型中编码的自然数。例如,如果我们的值为val x:_3 = nullnull,类型为Succ[Succ[Succ[_0]]]),我们希望toInt(x)返回3

要实现toInt,我们要充分利用以下类:

class TypeToValue[T, VT](value : VT) { def getValue() = value } 

正如我们下面将看到的,将有来自TypeToValue类构造的每个Nat_0长达一个对象(例如)_3,并且每个将存储对应类型的值表示(即TypeToValue[_0, Int]将存储值0,TypeToValue[Succ[_0], Int]将存储值1等)。请注意,TypeToValue由两种类型参数化:TVTT对应于我们试图赋予值的类型(在本例中为Nat),而VT对应于我们分配给它的值的类型(在我们的示例中为Int)。

现在我们提出以下两个隐含的定义:

implicit val _0ToInt = new TypeToValue[_0, Int](0) 
implicit def succToInt[P <: Nat](implicit v : TypeToValue[P, Int]) = 
    new TypeToValue[Succ[P], Int](1 + v.getValue()) 

我们实现toInt如下:

def toInt[T <: Nat](v : T)(implicit ttv : TypeToValue[T, Int]) : Int = ttv.getValue() 

要了解toInt是如何工作的,让我们来看看它做什么一对夫妇的投入:

val z:_0 = null 
val y:Succ[_0] = null 

当我们拨打toInt(z),编译器查找TypeToValue[_0, Int]类型的隐式参数ttv(因为z的类型为_0)。它找到对象_0ToInt,它调用该对象的getValue方法并取回0。需要注意的重要一点是,我们没有向程序指定使用哪个对象,编译器隐式地发现它。

现在让我们考虑一下toInt(y)。这一次,编译器查找类型为TypeToValue[Succ[_0], Int]的隐式参数ttv(因为y的类型为Succ[_0])。它找到函数succToInt,它可以返回适当类型的对象(TypeToValue[Succ[_0], Int])并对其进行评估。该函数本身采用TypeToValue[_0, Int]类型的隐式参数(v)(即TypeToValue,其中第一个参数的类型为Succ[_])。编译器提供_0ToInt(如上面对toInt(z)的评估中所做的那样),并且succToInt构造了一个新的TypeToValue对象,其值为1。同样,重要的是要注意编译器隐式提供所有这些值,因为我们没有显式地访问它们。

检查你的工作

有几种方法来验证你的类型,级别的计算都在做你的期望。这里有几种方法。使两种类型AB,你想验证是相等的。然后,检查以下编译:

或者,您可以将类型转换为数值(如上所示)并对这些值进行运行时检查。例如。 assert(toInt(a) == toInt(b)),其中a是类型AbB类型。

其他资源

一套完整的可构建可在the scala reference manual (pdf)类型部分找到。

Adriaan Moors具有大约从阶类型构造和相关主题与实例几个学术论文:

Apocalisp是一个博客,有许多类型级专业版的例子在scala中编写。

ScalaZ是提供使用各种类型级编程功能延伸Scala的API功能一个非常活跃的项目。这是一个非常有趣的项目,有一个很大的关注。

MetaScala是Scala的一个类型级库,包括自然数的元类型,布尔值,单位,HList等。这是一个由Jesper Nordenberg (his blog)开发的项目。

Michid (blog)具有型电平编程的Scala中一些真棒的例子(从其他的答案):

Debasish Ghosh (blog)有一些相关的帖子,以及:

(我一直在做这方面的一些研究,这里是我学到的。我仍然对它不熟悉,所以请指出此答案中的任何不准确之处。)

4

Scalaz拥有源代码,wiki和示例。

+0

链接不会起作用 – iuriisusuk 2017-03-03 12:51:36

12
+0

只是想说感谢有趣的博客;我一直在关注它,特别是上面提到的最后一篇文章加深了我对面向对象语言的类型系统应该具有的重要属性的理解。那谢谢啦! – 2010-12-13 21:28:31