Shapeless 库主要思想参考了Haskell的HList
,以及自身type-class的扩展。它主要用于编译期实现,所以叫做"shapeless",无固定类型、无固定结构化、函数和对象多态等特点。因为它主要是在编译期实现,所以语法上接近于“表述式”。
什么是shapeless
Shapeless is a type class and dependent type based generic programming library for Scala.
因为Shapeless是基于类型,因此对于Scala的类型系统如虎添翼,只要提供少量的样板代码(boilerplate),就可以获得额外的类型安全机制。
因为shapeless的内容比较多,我们从www.scala-exercises.org开始介绍一些主要特性。
Polymorphic functions
多态函数,先看下面的例子:
1
| val demo = 42 :: "Hello" :: User("Julien") :: HNil
|
因为这个HList
结构不是固定类型的,如果我们要进行map
映射操作,
1
| def map[A, B, C](h: Int :: String :: User :: HNil)(f0: Int => A, f1: String => B, f3: User => C)
|
但是,过多这样的f0
,f1
,f2
参数函数是不切实际的,我们希望只用一个f
参数表示,
1
| def map[A, B, C](h: Int :: String :: User :: HNil)(f: (Int => A) & (String => B) & (User => C))
|
可惜Scala里面没有 &
这样的用法。记住一点,只要遇到非结构固定类型的地方,基本可以考虑用shapeless来实现。
1 2 3 4 5 6 7 8 9 10
| import shapeless._
object plusOne extends Poly1 { implicit def caseInt = at[Int](_ + 1) implicit def caseString = at[String](_ + 1) implicit def caseUser = at[User](case User(name) => User(name + 1)) }
demo.map(plusOne)
|
Generic
Generic
,顾名思义,类似于Java的泛型,它主要用于case class/product types 到 HList
之间的转换,
1 2 3 4 5 6 7 8
| import shapeless.Generic
case class UserWithAge(name: String, age: Int) val gen = Generic[UserWithAge] val u = UserWithAge("Julien", 30)
val h = gen.to(u) gen.from(h)
|
还是那句话,case class也是非结构固定类型的,因此可以扩展,
1 2 3 4 5 6 7 8 9 10
| case class Book(author: String, title: String, id: Int, price: Double) val bookGen = LabelledGeneric[Book] val tapl = Book("Benjamin Pierce", "Types and Programming Languages", 262162091, 44.11) val rec = bookGen.to(tapl) rec('price) should be(44.11)
case class ExtendedBook(author: String, title: String, id: Int, price: Double, inPrint: Boolean) val bookExtGen = LabelledGeneric[ExtendedBook] val extendedBook = bookExtGen.from(rec + ('inPrint ->> true)) extendedBook.inPrint should be(true)
|
Tuples
Shapleless对Tuplex提供了语法,因此可以在tuples上使用HList
的方法,
1 2 3 4 5 6
| (1, "foo", 12.3).tail
import shapeless.syntax.std.tuple._ (1, "foo", 12.3).tail
|
Lenses
Lenses,透镜。从名字可以看出,可以通过“透镜”直接操作,
1 2 3 4 5 6 7 8 9 10 11 12 13
| case class Address(street: String, city: String, postcode: String) case class Person(name: String, age: Int, address: Address)
val nameLens = lens[Person] >> 'name val ageLens = lens[Person] >> 'age val addressLens = lens[Person] >> 'address val streetLens = lens[Person] >> 'address >> 'street val cityLens = lens[Person] >> 'address >> 'city val postcodeLens = lens[Person] >> 'address >> 'postcode
val person = Person("Joe Grey", 37, Address("Southover Street", "Brighton", "BN2 9UA"))
|
1 2 3 4 5 6 7
| ageLens.get(person) ageLens.set(person)(38)
streetLens.get(person) streetLens.set(person)("Montpelier Road")
|
Abstracting over arity
并不是一个具体的特性,它只是HList
和 Generic
的一个运用。
1 2 3 4 5
| import syntax.std.function._ import ops.function._
def applyProduct[P <: Product, F, L <: HList, R](p: P)(f: F)(implicit gen: Generic.Aux[P, L], fp: FnToProduct.Aux[F, L => R]) = f.toProduct(gen.to(p))
|
1 2
| applyProduct(1, 2)((_: Int) + (_: Int)) should be(3) applyProduct(1, 2, 3)((_: Int) * (_: Int) * (_: Int)) should be(6)
|