scalaz功能基本上由以下三部分组成:
1、新的数据类型,如:Validation, NonEmptyList ...
2、标准scala类型的延伸类型,如:OptionOps, ListOps ...
3、通过typeclass的随意多态(ad-hoc polymorphism)编程模式实现的大量概括性函数组件库
我们在这篇重点讨论多态(polymorphism),特别是随意多态(ad-hoc polymorphism)。
多态,简单来讲就是一项操作(operation)可以对任意类型施用。在OOP的世界里,我们可以通过以下各种方式来实现多态:
1、重载 Overloading
2、继承 Inheritance
3、模式匹配 Pattern-matching
4、特性 Traits/interfaces
5、类型参数 Type parameters
作为一种通用的组件库,scalaz是通过任意多态typeclass模式来实现软件模块之间的松散耦合(decoupling).这样scalaz的用户就可以在不需要重新编译scalaz源代码的情况下对任何类型施用scalaz提供的函数功能了。
我们来分析一下各种实现多态的方式:
假如我们设计一个描述输入参数的函数:tell(t: some type): String
如:tell(c: Color) >>> "I am color Red"
tell(i: Int) >>> "I am Int 3"
tell(p: Person) >>> "I am Peter"
如果用Overloading:
1 object overload { 2 case class Color(descript: String) 3 case class Person(name: String) 4 5 def tell(c: Color) = "I am color "+ c.descript 6 def tell(p: Person) = "I am "+ p.name 7 }
我们看到用重载的话,除了相同的函数名称tell之外这两个函数没有任何其它关系。我们必须对每一个不同的类型提供一个独立的tell函数。这种方式没什么用,我们需要的是一个函数施用在不同的类型上。
再试试用继承Inheritance:
1 trait Thing { 2 def tell: String 3 } 4 class Color(descript: String) extends Thing { 5 override def tell: String = "I am color " + descript 6 } 7 class Person(name: String) extends Thing { 8 override def tell: String = "I am " + name 9 } 10 11 new Color("RED").tell //> res0: String = I am color RED 12 new Person("John").tell //> res1: String = I am John
这种方式更糟糕,tell和类有着更强的耦合。用户必须拥有这些类的源代码才能实现tell。试想如果这个类型是标准的Int怎么办。
用模式匹配pattern-matching呢?
1 case class Color(descript: String) 2 case class Person(name: String) 3 def tell(x: Any): String = x match { 4 case Color(descr) => "I am color " + descr 5 case Person(name) => "I am " + name 6 case i: Int => "I am Int "+i 7 case _ => "unknown" 8 } //> tell: (x: Any)String 9 10 tell(23) //> res0: String = I am Int 23 11 tell(Color("RED")) //> res1: String = I am color RED 12 tell(Person("Peter")) //> res2: String = I am Peter
Pattern-matching倒是可以把tell和类型分开。但是必须在tell里增加新的类型匹配,也就是说必须能控制tell的源代码。
现在再尝试用typeclass模式:typeclass模式是由trait加implicit组成。先看看trait:
1 trait Tellable[T] { 2 def tell(t: T): String 3 }
这个trait Tellable代表的意思是把tell功能附加到任意类型T,但还未定义tell的具体功能。
如果用户想把tell附加给Color类型:
1 trait Tellable[T] { 2 def tell(t: T): String 3 } 4 case class Color(descript: String) 5 case class Person(name: String) 6 object colorTeller extends Tellable[Color] { 7 def tell(t: Color): String = "I am color "+t.descript 8 }
针对Color我们在object colorTeller里实现了tell。现在更概括的tell变成这样:
1 def tell[T](t: T)(M: Tellable[T]) = { 2 M.tell(t) 3 } //> tell: [T](t: T)(M: scalaz.learn.demo.Tellable[T])String 4 tell(Color("RED"))(colorTeller) //> res0: String = I am color RED
这个版本的tell增加了类型变量T、输入参数M,意思是对任何类型T,因为M可以对任何类型T施用tell,所以这个版本的tell可以在任何类型上施用。上面的例子调用了针对Color类型的tell。那么针对Person的tell我们再实现一个Tellable[Person]实例就行了吧:
1 val personTeller = new Tellable[Person] { 2 def tell(t: Person): String = "I am "+ t.name 3 } //> personTeller : scalaz.learn.demo.Tellable[scalaz.learn.demo.Person] = scala 4 //| z.learn.demo$$anonfun$main$1$$anon$1@13969fbe 5 tell(Person("John"))(personTeller) //> res1: String = I am John 6 val intTeller = new Tellable[Int] { 7 def tell(t: Int): String = "I am Int "+ t.toString 8 } //> intTeller : scalaz.learn.demo.Tellable[Int] = scalaz.learn.demo$$anonfun$ma 9 //| in$1$$anon$2@6aaa5eb0 10 tell(43)(intTeller) //> res2: String = I am Int 43
如上,即使针对Int类型我们一样可以调用这个tell[T]。也既是说如果这个概括性的tell[T]是由其他人开发的某些组件库提供的,那么用户只要针对他所需要处理的类型提供一个tell实现实例,然后调用这个共享的tell[T],就可以得到随意多态效果了。至于这个类型的实现细节或者源代码则不在考虑之列。
好了,现在我们可以用implicit来精简tell[T]的表达形式:
1 def tell[T](t: T)(implicit M: Tellable[T]) = { 2 M.tell(t) 3 } //> tell: [T](t: T)(implicit M: scalaz.learn.demo.Tellable[T])String
也可以这样写:
1 def tell[T : Tellable](t: T) = { 2 implicitly[Tellable[T]].tell(t) 3 } //> tell: [T](t: T)(implicit evidence$1: scalaz.learn.demo.Tellable[T])String
现在看看如何调用tell:
1 implicit object colorTeller extends Tellable[Color] { 2 def tell(t: Color): String = "I am color "+t.descript 3 } 4 5 tell(Color("RED")) //> res0: String = I am color RED 6 7 implicit val personTeller = new Tellable[Person] { 8 def tell(t: Person): String = "I am "+ t.name 9 } //> personTeller : scalaz.learn.demo.Tellable[scalaz.learn.demo.Person] = scala 10 //| z.learn.demo$$anonfun$main$1$$anon$1@3498ed 11 tell(Person("John")) //> res1: String = I am John 12 13 implicit val intTeller = new Tellable[Int] { 14 def tell(t: Int): String = "I am Int "+ t.toString 15 } //> intTeller : scalaz.learn.demo.Tellable[Int] = scalaz.learn.demo$$anonfun$ma 16 //| in$1$$anon$2@1a407d53 17 tell(43) //> res2: String = I am Int 43
假如我忽然需要针对新的类型List[Color], 我肯定无须理会tell[T],只要调用它就行了:
1 implicit object listTeller extends Tellable[List[Color]] { 2 def tell(t: List[Color]): String = { 3 (t.map(c => c.descript)).mkString("I am list of color [",",","]") 4 } 5 } 6 7 tell[List[Color]](List(Color("RED"),Color("BLACK"),Color("YELLOW"),Color("BLUE"))) 8 //> res3: String = I am list of color [RED,BLACK,YELLOW,BLUE]
这才是真正的随意多态。
值得注意的是implicit是scala compiler的一项功能。在编译时compiler发现类型不对称就会进行隐式转换解析(implicit resolution)。如果解析失败则程序无法通过编译。如果我这样写: tell(4.5),compiler会提示语法错误。而上面其它多态方式则必须在运算时(runtime)才能发现错误。