Scalatypes的编程资源

根据这个问题 ,Scala的types系统是图灵完整的 。 有哪些资源可以使新手充分利用types编程的力量?

以下是我迄今为止发现的资源:

  • 丹尼尔Spiewak 在斯卡拉土地高巫术
  • Apocalisp 在Scala中types级编程
  • Jesper的HList

这些资源非常棒,但我觉得我错过了基础知识,所以没有坚实的基础来构build。 例如,哪里有对types定义的介绍? 我可以在types上执行哪些操作?

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

概观

types级编程与传统的价值级编程有许多相似之处。 但是,与运行时发生计算的值级编程不同,在types级编程中,计算是在编译时进行的。 我将尝试从价值层面上的编程和types层面的编程中得出相似之处。

范式

在types编程中有两个主要的范式:“面向对象”和“function”。 从这里链接到的大多数例子遵循面向对象的范例。

lambda演算的 apocalisp 实现中可以find一个很好的,相当简单的面向对象范型中的types级编程的例子,

// 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 } 

从示例中可以看出,types级编程的面向对象的范例如下进行:

  • 首先:用各种抽象types字段定义一个抽象特征(请参阅下面的抽象字段是什么)。 这是一个模板,用于保证所有实现中都存在某些types的字段而不强制执行。 在lambda微积分示例中,这对应于保证存在以下types的trait Lambdasubstapplyeval
  • 下一步:定义扩展抽象特征并实现各种抽象types字段的相减
    • 通常,这些减法将用参数进行参数化。 在lambda微积分示例中,子types是trait App extends Lambda ,它是用两种types( ST ,都必须是Lambda子types)参数化的, trait Lam extends Lambda用一个types( T )参数化的trait Lam extends Lambdatrait X extends Lambda这是没有参数化)。
    • types字段通常通过引用子types的参数来实现,有时通过哈希运算符引用它们的types字段: # (这与点运算符非常类似。 在lambda微积分示例的trait App中,typeseval实现如下: type eval = S#eval#apply[T] 。 这本质上是调用特征参数Sevaltypes,并调用结果上的参数T apply 。 注意, S保证有一个evaltypes,因为参数指定它是Lambda的子types。 类似地, eval的结果必须具有applytypes,因为它被指定为Lambda的子types,如抽象特征Lambda所指定的。

function范式由定义大量参数化types的构造函数组成,这些构造函数在特征中没有组合在一起。

价值层次编程和types层次编程之间的比较

  • 抽象类
    • 值级别: abstract class C { val x }
    • types级别: trait C { type X }
  • path依赖types
    • Cx (在对象C中引用字段值/函数x)
    • C#x (在特征C中引用字段typesx)
  • 函数签名(不执行)
    • 值级别: def f(x:X) : Y
    • type-level: type f[x <: X] <: Y (这被称为“types构造函数”,通常发生在抽象trait中)
  • function实现
    • 值级别: def f(x:X) : Y = x
    • type-level: type f[x <: X] = x
  • 条件语句
    • 看这里
  • 检查平等
    • 值级: a:A == b:B
    • types级别: implicitly[A =:= B]
    • 值级:通过运行时的unit testing(即没有运行时错误)在JVM中发生:
      • 在本质上是一个断言: assert(a == b)
    • types级别:通过types检查(即没有编译器错误)在编译器中发生:
      • 实质上是一种types比较:例如implicitly[A =:= B]
      • A <:< B ,仅当AB的子types时才编译
      • A =:= B ,仅在AB的子types时才编译, BA的子types
      • A <%< B ,(“可见”)只有在A可见的时候才会编译为B (即有一个从AB的子types的隐式转换)
      • 一个例子
      • 比较运算符更多

在types和值之间转换

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

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

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

考虑这个例子( 取自metascala和apocalisp ):

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

在这里你有自然数的Peano编码。 也就是说,每个非负整数都有一个types:一个0的特殊types,即_0 ; 并且每个大于零的整数具有formsSucc[A]的types,其中A是代表较小整数的types。 例如,代表2的types将是: Succ[Succ[_0]] (后继对表示零的types应用了两次)。

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

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

(这很像定义一个val作为函数的结果。)

现在,假设我们要定义一个值为def toInt[T <: Nat](v : T)函数def toInt[T <: Nat](v : T) ,它接受一个符合Nat的参数值v ,并返回一个整数,代表在v '中编码的自然数, stypes。 例如,如果我们有值val x:_3 = null (types为Succ[Succ[Succ[_0]]] null ),我们希望toInt(x)返回3

为了实现toInt ,我们将使用以下类:

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

正如我们将在下面看到的,对于从_0直到(eg) _3每个Nat ,将会有一个从TypeToValue类构造的对象,每个将存储对应types的值表示(即TypeToValue[_0, Int]将存储值0TypeToValue[Succ[_0], Int]将存储值1等)。 请注意, TypeToValue被两种types参数化: TVTT对应于我们要赋值的types(在我们的例子中, Nat ), VT对应于我们赋值的types(在我们的例子中为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() 

要理解如何工作,让我们考虑一下它在几个input上的作用:

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

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

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

检查你的工作

有几种方法来validation您的types级别的计算正在做你期望的。 以下是一些方法。 使两种typesAB ,你想validation是相等的。 然后检查下面的编译:

  • Equal[A, B]
    • 具有:性状Equal[T1 >: T2 <: T2, T2] ( 取自apocolisp )
  • implicitly[A =:= B]

或者,您可以将types转换为值(如上所示)并对值进行运行时检查。 例如assert(toInt(a) == toInt(b)) ,其中a的types为Ab的types为B

其他资源

完整的可用构造集可以在scala参考手册(pdf)的types部分find。

Adriaan Moors有几篇有关types构造函数和相关主题的学术论文,其中有来自scala的示例:

  • 更高types的generics(pdf)
  • 斯卡拉的types构造子多态性:理论与实践(pdf) (博士论文,其中包括摩尔斯以前的论文)
  • types构造器多态性推断

Apocalisp是一个博客,其中有很多scalatypes编程的例子。

  • 在Scala中进行types级编程是一个很棒的指导,包括布尔types,自然数(如上),二进制数,异类列表等等。
  • 更多Scala Typehackery是上面的lambda演算实现。

ScalaZ是一个非常活跃的项目,它提供了使用各种types级编程function来扩展Scala API的function。 这是一个非常有趣的项目,有一个很大的关注。

MetaScala是Scala的一个types级库,包括自然数的元types,布尔值,单位,HList等。这是Jesper Nordenberg(他的博客)的一个项目。

Michid(博客)在Scala中有一些很好的types级编程示例(来自其他答案):

  • Scala元编程第一部分:增加
  • 用Scala进行元编程第二部分:乘法
  • 用Scala进行元编程第三部分:部分函数应用
  • 使用Scala进行元编程:条件编译和循环展开
  • SKI演算的Scalatypes等级编码

Debasish Ghosh(博客)也有一些相关的post:

  • 更高阶抽象的斯卡拉
  • 静态打字可以让您领先一步
  • 斯卡拉暗示types类,我来了
  • 重构成Scalatypes的类
  • 使用广义types约束
  • 斯卡拉如何为你input系统词
  • 在抽象types成员之间进行select

(我一直在研究这个问题,这是我学到的东西,我还是一个新手,所以请在这个答案中指出任何不准确的地方。)

除了这里的其他链接之外,我还有一些关于Scala中types级元编程的博客文章:

  • Scala元编程第一部分:增加
  • 用Scala进行元编程第二部分:乘法
  • 用Scala进行元编程第三部分:部分函数应用
  • 使用Scala进行元编程:条件编译和循环展开
  • SKI演算的Scalatypes等级编码

正如Twitter上所build议的: 无形:通过Miles Sabin 在Scala中探索generics/多种编程 。

  • Sing ,Scala中的一个types级元编程库。
  • 在Scala中进行types编程的开始

Scalaz有源代码,一个wiki和例子。