接上一篇,现在来谈谈shapeless怎么实现的函数(值)的多态。参考shapeless作者原文:
先回顾一下scala里函数类型的定义(带有一个参数的):
trait Function1[-T, +R] {
def apply(t : T) : R
}
我们只要稍作修改就可以支持对入参类型支持多态,即把apply
方法声明为参数化的:
trait PolyFunction1[R] {
def apply[T](t : T) : R
}
测试一下:
scala> object test extends PolyFunction1[String] { def apply[T](p:T) = p.toString}
scala> test("hi")
res21: String = hi
scala> test(2)
res22: String = 2
现在还差一步,怎么让返回结果类型也支持多态?虽然我们也可以在函数结尾声明返回结果是T
,但那只是表示入参类型与出参类型一致的函数,并不能表达所有的函数类型。
为了达到效果,我们必须对出参类型再进行抽象!也就是说我们用高阶类型来描述(higher-kinded type,可以参考这篇:scala类型系统:24) 理解 higher-kinded-type)
对于特定类型R
,它们的高阶类型为C[_]
,比如:
List[Int] 对应的高阶类型,即类型构造器 List
Set[String] 对应的高阶类型,即类型构造器 Set
那对于 String
, Int
这样非参数化的类型,它们的高阶类型(构造器)又是什么?我们可以假定存在一个这样的类型构造器:
scala> type Id[T] = T
Id
这个类型构造器与任何类型参数结合,构造出来的结果类型与参数类型一致。
scala> val s:Id[String] = "hi"
s: Id[String] = hi
scala> val s:Id[Int] = 2
s: Id[Int] = 2
scala> def f(id:Id[_]) = print("ok")
f: (id: Id[_])Unit
scala> f(2)
ok
scala> f("hello")
ok
对于String
,Int
这样的类型,它们的高阶类型即类型构造器Id
.(其实可以把这背后看做是一个类型级别的函数,非参数化的类型其构造器都可以看做是Id这样的一个构造器类型)
现在我们重新定义多态函数PolyFunction1
,通过前边高阶类型的铺垫,我们现在把结果类型R
与参数类型T
之间看做是一种依赖关系,即R
取决于T
和构造器C[_]
(关于依赖类型,参考 scala类型系统:28) 依赖类型):
构造器 C[_] 与参数 T 产生结果类型 C[T] 即 R == C[T]
这样我们在定义PolyFunction1
时,只需要抽象出一个结果类型的类型构造器就可以了:
trait PolyFunction1[C[_]] {
def apply[T](t : T) : C[T]
}
现在构造一个多态函数的实例,先用一个常见的类型构造器 Set
做参数,结果类型是Set[T]
:
scala> object test extends PolyFunction1[Set] { def apply[T](p:T) = Set(p)}
defined module test
scala> test("hi")
res23: scala.collection.immutable.Set[String] = Set(hi)
scala> test(2)
res24: scala.collection.immutable.Set[Int] = Set(2)
现在实现了我们上一篇期望的 singletonFn
函数能够满足 T => Set[T]
的效果。
再测试使用Id
这个类型构造器(高阶类型)做类型参数,结果类型是Id[T]
,也就是T
scala> object test extends PolyFunction1[Id] { def apply[T](p:T) = p}
scala> test("hi")
res17: String = hi
scala> test(2)
res18: Int = 2
shapeless正是通过这种思路对函数实现的多态。