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
的eval
types,并调用结果上的参数T
apply
。 注意,S
保证有一个eval
types,因为参数指定它是Lambda
的子types。 类似地,eval
的结果必须具有apply
types,因为它被指定为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和例子。