Scalaz(2)- 基础篇:随意多态-typeclass, ad-hoc polymorphism

  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:

object overload {
 case class Color(descript: String)
 case class Person(name: String)

 def tell(c: Color) = "I am color "+ c.descript
 def tell(p: Person) = "I am "+ p.name
}

我们看到用重载的话,除了相同的函数名称tell之外这两个函数没有任何其它关系。我们必须对每一个不同的类型提供一个独立的tell函数。这种方式没什么用,我们需要的是一个函数施用在不同的类型上。

再试试用继承Inheritance:

trait Thing {
  def tell: String
}
class  Color(descript: String) extends Thing {
  override def tell: String = "I am color " + descript
}
class Person(name: String) extends Thing {
  override def tell: String = "I am " + name
}

new Color("RED").tell                             //> res0: String = I am color RED
new Person("John").tell                           //> res1: String = I am John

这种方式更糟糕,tell和类有着更强的耦合。用户必须拥有这些类的源代码才能实现tell。试想如果这个类型是标准的Int怎么办。

用模式匹配pattern-matching呢?

case class Color(descript: String)
case class Person(name: String)
def tell(x: Any): String = x match {
  case Color(descr) => "I am color " + descr
  case Person(name) => "I am " + name
  case i: Int => "I am Int "+i
  case _ => "unknown"
}                                                 //> tell: (x: Any)String

tell(23)                                          //> res0: String = I am Int 23
tell(Color("RED"))                                //> res1: String = I am color RED
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类型:

trait Tellable[T] {
  def tell(t: T): String
}
case class Color(descript: String)
case class Person(name: String)
object colorTeller extends Tellable[Color] {
    def tell(t: Color): String = "I am color "+t.descript
}

针对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]实例就行了吧:

val personTeller = new Tellable[Person] {
    def tell(t: Person): String = "I am "+ t.name
}                                                 //> personTeller  : scalaz.learn.demo.Tellable[scalaz.learn.demo.Person] = scala
                                                  //| z.learn.demo$$anonfun$main$1$$anon$1@13969fbe
tell(Person("John"))(personTeller)                //> res1: String = I am John
val intTeller = new Tellable[Int] {
    def tell(t: Int): String = "I am Int "+ t.toString
}                                                 //> intTeller  : scalaz.learn.demo.Tellable[Int] = scalaz.learn.demo$$anonfun$ma
                                                  //| in$1$$anon$2@6aaa5eb0
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:

implicit object colorTeller extends Tellable[Color] {
    def tell(t: Color): String = "I am color "+t.descript
}

tell(Color("RED"))                                //> res0: String = I am color RED

implicit val personTeller = new Tellable[Person] {
    def tell(t: Person): String = "I am "+ t.name
}                                                 //> personTeller  : scalaz.learn.demo.Tellable[scalaz.learn.demo.Person] = scala
                                                  //| z.learn.demo$$anonfun$main$1$$anon$1@3498ed
tell(Person("John"))                              //> res1: String = I am John

implicit val intTeller = new Tellable[Int] {
    def tell(t: Int): String = "I am Int "+ t.toString
}                                                 //> intTeller  : scalaz.learn.demo.Tellable[Int] = scalaz.learn.demo$$anonfun$ma
                                                  //| in$1$$anon$2@1a407d53
tell(43)                                          //> res2: String = I am Int 43

假如我忽然需要针对新的类型List[Color], 我肯定无须理会tell[T],只要调用它就行了:

implicit object listTeller extends Tellable[List[Color]] {
    def tell(t: List[Color]): String = {
        (t.map(c => c.descript)).mkString("I am list of color [",",","]")
    }
}

tell[List[Color]](List(Color("RED"),Color("BLACK"),Color("YELLOW"),Color("BLUE")))
                                                  //> res3: String = I am list of color [RED,BLACK,YELLOW,BLUE]

这才是真正的随意多态。

值得注意的是implicit是scala compiler的一项功能。在编译时compiler发现类型不对称就会进行隐式转换解析(implicit resolution)。如果解析失败则程序无法通过编译。如果我这样写: tell(4.5),compiler会提示语法错误。而上面其它多态方式则必须在运算时(runtime)才能发现错误。

时间: 2024-12-02 04:16:13

Scalaz(2)- 基础篇:随意多态-typeclass, ad-hoc polymorphism的相关文章

Scalaz(3)- 基础篇:函数概括化-Generalizing Functions

  Scalaz是个通用的函数式编程组件库.它提供的类型.函数组件都必须具有高度的概括性才能同时支持不同数据类型的操作.可以说,scalaz提供了一整套所有编程人员都需要的具有高度概括性的通用函数,它是通过随意多态(ad-hoc polymorphism)来帮助用户使用这些函数的.随意多态就是trait+implicit parameters+implicit conversions.简单的说就是scalaz提供一个概括化的函数,用户可以在各种类型上施用这个同一函数.概括化(generalizi

JAVA程序员必读:基础篇(6)

程序|程序员 JAVA程序员必读:基础篇时间:2001/09/13 13:31作者:ZSC 太平洋网络学院 2.4.2初始化实例和类成员 下面讲讲初始化实例和类成员: 你可以在类中定义它们的时候,使用static初始化程序和实例初始化程序来为类和实例成员提供初始化数值: class BedAndBreakfast { static final int MAX_CAPACITY = 10; boolean full = false; } 这个对于原始数据类型是没有问题的.有时候,它可以用在创建数组

2000条你应知的WPF小姿势 基础篇<78-81 Dialog/Location/WPF设备无关性>

原文:2000条你应知的WPF小姿势 基础篇<78-81 Dialog/Location/WPF设备无关性> 在正文开始之前需要介绍一个人:Sean Sexton. 来自明尼苏达双城的软件工程师.最为出色的是他维护了两个博客:2,000Things You Should Know About C#  和 2,000 Things You Should Know About WPF .他以类似微博式的150字简短语言来每天更新一条WPF和C#重要又容易被遗忘的知识.很希望能够分享给大家. 本系列

JAVA程序员必读:基础篇(8)

程序|程序员 JAVA程序员必读:基础篇时间:2001/09/13 13:31 作者:ZSC 太平洋网络学院 2.6什么是接口 接口是一个收集方法和常数表单的契约.当类执行一个接口,它就许诺声明在那个接口中执行所有的方法. 接口是一个设备或者一个系统,它是用于交互的无关的实体.根据这个定义,远程控制是一个在你和电视的接口:而英语是两个人之间的接口:强制在军事中的行为协议是不同等价人之间的接口.在JAVA语言中,接口是一个设备,它是用来与其它对象交互的设备.一个接口可能对一个协议是类似的.实际上,

JAVA程序员必读:基础篇(9)

程序|程序员 JAVA程序员必读:基础篇时间:2001/09/13 13:31作者:ZSC 太平洋网络学院 2.8 面向对象概念的问题和练习 本节教程测试一下你对对象.类.消息等等的理解,我们是通过做一些练习以及回答一些问题来进行的. 2.8.1 问题 你可以使用API文档来回答这些问题: ClickMe applet使用Color.red来设置画图颜色为红色.其它有什么颜色可以象这样来使用? 怎样设置颜色为紫色(purple)? 2.8.2 练习 现在,利用你从API文档中学到的知识来修改Cl

JAVA程序员必读:基础篇(7)

程序|程序员 JAVA程序员必读:基础篇时间:2001/09/13 13:31作者:ZSC 太平洋网络学院 2.5什么是继承 一个类可以从它的父类继承状态和行为.继承为组织和构造软件程序提供了一个强大的和自然的机理. 总得说来,对象是以类得形式来定义得.你可能现在已经可以从它类知道许多对象了.即使你如知道,如果我告诉你它是一辆自行车,你就会知道它有两个轮子和脚踏板等等.面向对象系统就更深入一些了,它允许类在其它类中定义.比如,山地自行车.赛车以及串座双人自行车都是各种各样的自行车.在面向对象技术

JAVA程序员必读:基础篇(5)

程序|程序员 JAVA程序员必读:基础篇时间:2001/09/13 13:31作者:ZSC 太平洋网络学院 2.4实例和类成员 2.4.1理解实例和类成员 下面详细讨论一下实例和类成员,具体涉及变量和方法以及类变量和方法: 你这样声明一个成员变量,比如在类Myclass中有一个float型的aFloat: class MyClass { float aFloat; } 这样你就声明一个实例变量.每次你创建一个类的实例的时候,系统就为实例创建了类的每一个实例变量的副本.你可以从对象中访问对象的实例

JAVA程序员必读:基础篇(4)

程序|程序员 JAVA程序员必读:基础篇时间:2001/09/13 13:31作者:ZSC 太平洋网络学院 2.3什么是类 类实际上是对某种类型的对象定义变量和方法的原型. 在现实世界中,你经常看到相同类型的许多对象.比如 ,你的自行车只是现实世界中许多自行车的其中一辆.使用面向对象技术,我们可以说你的自行车是自行车对象类的一个实例.通常,自行车有一些状态(当前档位.两个轮子等等)以及行为(改变档位.刹车等等).但是,每辆自行车的状态都是独立的并且跟其它自行车不同. 当厂家制造自行车的时候,厂商

JAVA程序员必读:基础篇(3)

程序|程序员 JAVA程序员必读:基础篇时间:2001/09/13 13:31作者:ZSC 太平洋网络学院 2.2什么是消息 软件对象之间进行交互作用和通讯是利用消息的. 单一的一个对象通常不是很有用的.相反,一个对象通常是一个包含了许多其它对象的更大的程序或者应用程序.通过这些对象的交互作用,程序员可以获得高阶的功能以及更为复杂的行为.你的自行车如果不使用它的时候,它就是一堆铝合金和橡胶,它没有任何的活动.而只有当有其它的对象来和它交互的时候才是有用的. 软件对象与其它对象进行交互与通讯是利用