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 Lambda:subst,apply和eval。 - 下一步:定义扩展抽象特征并实现各种抽象types字段的相减
- 通常,这些减法将用参数进行参数化。 在lambda微积分示例中,子types是
trait App extends Lambda,它是用两种types(S和T,都必须是Lambda子types)参数化的,trait Lam extends Lambda用一个types(T)参数化的trait Lam extends Lambda,trait X extends Lambda这是没有参数化)。 - types字段通常通过引用子types的参数来实现,有时通过哈希运算符引用它们的types字段:
#(这与点运算符非常类似。 在lambda微积分示例的traitApp中,typeseval实现如下:type eval = S#eval#apply[T]。 这本质上是调用特征参数S的evaltypes,并调用结果上的参数Tapply。 注意,S保证有一个evaltypes,因为参数指定它是Lambda的子types。 类似地,eval的结果必须具有applytypes,因为它被指定为Lambda的子types,如抽象特征Lambda所指定的。
- 通常,这些减法将用参数进行参数化。 在lambda微积分示例中,子types是
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,仅当A是B的子types时才编译 -
A =:= B,仅在A是B的子types时才编译,B是A的子types -
A <%< B,(“可见”)只有在A可见的时候才会编译为B(即有一个从A到B的子types的隐式转换) - 一个例子
- 比较运算符更多
- 实质上是一种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]将存储值0 , TypeToValue[Succ[_0], Int]将存储值1等)。 请注意, TypeToValue被两种types参数化: T和VT 。 T对应于我们要赋值的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级别的计算正在做你期望的。 以下是一些方法。 使两种typesA和B ,你想validation是相等的。 然后检查下面的编译:
-
Equal[A, B]- 具有:性状
Equal[T1 >: T2 <: T2, T2]( 取自apocolisp )
- 具有:性状
-
implicitly[A =:= B]
或者,您可以将types转换为值(如上所示)并对值进行运行时检查。 例如assert(toInt(a) == toInt(b)) ,其中a的types为A , b的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和例子。