从年初开始进行此项工作,我和合作伙伴包亮付出了大量而艰辛的劳动。翻译中我们本着能够让所有人看明白、看懂的目的,反复修改高达五次。现基本翻译完毕,有出版意向,如果有意向欢迎联系,不甚感激!现在此文中展示其中的前言和第一章,欢迎各位博友对此翻译提出意见建议以及指导如何出版,在此谢过!
时间回到2011年初,我做了一些泛型编程(generic programming)的实验,最后这些实验形成了shapeless,我绝不会想到五年后它居然会成为一个被如此广泛使用的类库。非常感谢那些信任我并将shapeless用到项目中的人,这种信任对任何开源项目来说都是巨大的动力。同样感谢多年来对项目做出巨大贡献的人,截止写此书时共有81人,没有它们的帮助shapeless不可能这么有趣和有用。
尽管有这些积极因素,shapeless也经历了所有开源项目的通病:缺乏完善的、准确的、易懂的文档。尽管我知晓这一点,但还是没能抽出时间在这方面做些事情,责任全在我。Travis Brown在Stack Overflow的英勇的表现以及很多人的讨论和实践,从一定程度上弥补了这一缺陷(此处我要特别指出Sam Halliday的“Shapeless for Mortals”)。
但是Dave Gurnell改变了这一切:他为我们写了这本精彩的书,此书介绍了shapeless的最重要的应用:通过泛型编程实现类型类(type class)派生。为了写此书他搜集了大量的代码和文档,征求了我的意见,并将杂乱无章变的清晰、简明、实用。幸运的是他很好的实现了我的主张——shapeless是一个非常简单的类库,它体现了一系列简单的原则。 感谢Dave,你为我们做了一件伟大的事情。
Miles Sabin
shapeless作者
此书是关于如何使用shapeless的指导,shapeless是基于Scala语言的泛型编程库。由于shapeless包含的内容过多,所以此书只是专注于一些非常有意思的使用案例并用它们描绘出一个可用的工具和编程模式的画面。
在本章开始处,先来介绍一下什么是泛型编程以及是什么原因使得shapeless让Scala开发者如此兴奋。
具体的类型是有帮助的,它向我们展示不同的代码片段如何能够组合到一起、帮助我们消除bug以及当我们编写代码的时候引导我们找到解决方案。
然而有时类型又太具体,有些情形下我们想探索不同类型之间的相似性来去除重复编码工作。例如,考虑以下两个类型:
case class Employee(name: String, number: Int, manager: Boolean)
case class IceCream(name: String, numCherries: Int, inCone: Boolean)
这两个模式类代表不同的数据种类,但是它们又非常相似,每种都包含三个类型相同的字段。假设我们要实现一个对它们都通用的操作,例如将它们的实例序列化到CSV文件中,尽管这两类数据相似,但是我们不得不写两个不同的方法来处理。分别如下:
def employeeCsv(e: Employee): List[String] =
List(e.name, e.number.toString, e.manager.toString)
def iceCreamCsv(c: IceCream): List[String] =
List(c.name, c.numCherries.toString, c.inCone.toString)
泛型编程能够克服像上面这样由于不同数据类型带来的重复操作。shapeless很容易实现将具体的类型泛型化,这样就可以使用同一段代码来操作不同的类型。
比如,我们能用如下代码将employees和ice creams实例转换成同一类型。不用担心不理解以下代码,本书会在接下来的章节中详细介绍这些它们。
import shapeless._
val genericEmployee = Generic[Employee].to(Employee("Dave", 123, false))
// genericEmployee: shapeless.::[String,shapeless.::[Int,shapeless.::[
Boolean,shapeless.HNil]]] = Dave :: 123 :: false :: HNil
val genericIceCream = Generic[IceCream].to(IceCream("Sundae", 1, false))
// genericIceCream: shapeless.::[String,shapeless.::[Int,shapeless.::[
Boolean,shapeless.HNil]]] = Sundae :: 1 :: false :: HNil
现在两个值变成了相同类型,都是异构的列表(简称HList),它包含一个字符串(String)、一个整型(Int)和一个布尔(Boolean)对象。接下来我们将研究HList类型和它在shapeless中所扮演的重要角色。目前为止关键问题在于我们已经解决了用同一个函数来序列化上面两种经过类型变换后的值。代码如下:
def genericCsv(gen: String :: Int :: Boolean :: HNil): List[String] =
List(gen(0), gen(1).toString, gen(2).toString)
genericCsv(genericEmployee)
// res2: List[String] = List(Dave, 123, false)
genericCsv(genericIceCream)
// res3: List[String] = List(Sundae, 1, false)
这个简单的例子展示了泛型编程的精髓。重新探究这些问题之后,我们用泛型代码块解决了问题并写出了适用于多种类型的精简代码。使用shapeless进行泛型编程可以消除大量的冗余代码,使Scala应用程序更容易读、写和维护。
听上去是不是很有诱惑?想想这些,让我们一起入坑吧!
该书共分为两部分。
第一部分介绍类型类(type class)派生,这允许我们仅用一些泛型规则来为任何代数数据类型(algebraic data type,简称ADT)创建类型类实例。第一部分包含四章。
第二部分介绍在shapeless.ops包中提供的“ops类型类”,它来源于一个处理泛型表示工具的扩展库。在接下来的三章中仅为大家介绍入门理论,而不是介绍每一个操作(op)的细节。
此书是开源的,你可以在Github中找到其markdown格式。本书会持续更新,所以请检查上述Github仓库以获取最新版本。
书中的大多数例子已经实现,你可以在此仓库中找到它们。细节请参考README文件。
我们推荐shapeless2.3.2版以及Typelevel Scala2.11.8+版或者Lightbend Scala2.11.9+/2.12.1+版。
感谢Miles Sabin、Richard Dallaway、Noel Welsh、Travis Brown和我们的Github仓库中的贡献者,他们为这本指导提供了不可估量的贡献。
特别感谢Sam Halliday,他的优秀的Shapeless for Mortals库为我们提供了最初的灵感和骨架。
最后,感谢Rob Norris和他的Tut库的跟随者,他们对我们的例子做了精心的验证,确保其编译无误。