真的学不动了: 除了 class , 也该了解 Type classes 了

前言

做为一个 Java 开发者, class 的概念确定是耳熟能详了,但是在山的另外一边还有拥有别样风情的 type classes,但不翻过 Java 这座山,它就始终隔着一层纱。html

一个经典的问题

在编程中,常常须要判断两个值是否相等,而在很长的一段时间内这个问题都没有一个标准的解决方案,这就是经典的判等问题。java

我这里统一使用 “值” 来代替对象、基本类型等等概念,以便于简化沟通编程

在 Java 中,咱们能够用 == ,也能够用 equals 来判断值是否相等markdown

public void test() {
    boolean res = "hello" == "world";
  
    boolean res2 = "hello".equals("hello");

    boolean res3 = 3 == 3;

    boolean res4 = 5 == 9;
}
复制代码

熟悉 Java 的同窗都知道对于非基础类型, equals 方法的默认实现其实就是调用 == 操做符,而 == 操做比较的是对象的引用地址app

public class Object {
  // ......
  
  public boolean equals(Object obj) {
  	return (this == obj);
  }
  
  // ......
}
复制代码

全部类都会有 equals 方法,这是由于在 Java 中默认全部类型都是 Object 的子类。框架

其实这也是 Java 语言处理判等问题的解决方案,即统一从 Object 中继承判等方法。less

image-20200715144724027

但是对于纯函数式的语言,好比 Haskell 来讲,它没有 OOP 中的继承、类等概念,它又该如何优雅的解决判等的问题呢?ide

若是你以为 Haskell 比较陌生,咱们就换一种提问的方式:还有其它通用的设计方案能够解决这类判等问题吗函数

固然有,而 Type classes 就是这个领域内最靓的那个仔,要了解 Type classes, 还得先从多态开始。oop

Type classes 与多态

Type classes 结合了 ad-hoc polymorphism(特设多态)和 Parametric polymorphism (参数化多态),实现了一种更通用的重载。

问题来了,什么是特设多态、参数化多态呢?

关于多态的更多内容 ,还能够参考个人前一篇文章《多态都不知道,谈什么对象》

  • ad-hoc polymorphism (特设多态) 指的是函数应用不一样类型的参数时,会有不一样的行为(或者说实现)

    最典型的就是算术重载

    3 * 3  // 表明两个整形的乘法
    
    3.14 * 3.14 // 表明两个浮点数的乘法
    复制代码
  • Parametric polymorphism (参数化多态) 指的是函数被定义在某一些类型之上,对于这些类型来讲函数的实现都是同样的。

    好比 List[T] 的 size() 函数,不管 T 的类型是 String、仍是 Int, size() 的实现都同样

    List[String].size()
    List[Int].size()
    复制代码

虽然 Type classes 结合了两种多态类型,但它自己却被归到特设多态(ad-hoc polymorphism)这一分类下。

若是你想了解更多 type classes 的思想,很是推荐阅读 《How to make ad-hoc polymorphism less ad hoc》 这篇论文,它也算是 Type classes 的开篇做。

Haskell 与 Type classes

Type classes 通常译做类型类,最开始是由 haskell 引入并实现,因此咱们颇有必要先了解一下 haskell 中的 Type classes。

以最开始提到的判等问题为例,来看看在 Haskell 中怎么用 Type classes 去解决。

首先咱们得用关键字 class 定义一个 Type class,千万不要和 Java 的 class 混为一谈。

class Eq a where
	(==) :: a -> a -> Bool
	(/=) :: a -> a -> Bool
复制代码

/= 其实就是 !=

haskell 的 Type class 与 Java 的 Interface 相似,上面的 Eq 类型类就定义了 ==/= 两个抽象函数,其中的 a 就是类型变量,与 Java 中的泛型相似。

由此看来,Type classes 只是抽象了一些共同的行为,而这些行为的具体实现会根据类型的不一样而不一样,具体的实现会由类型类实例来定义。

经过 instance 关键字能够建立类型类实例,下面展现了针对于于 Float 和 Int 的 Eq 类型类实例

instance Eq Int where
	(==) = eqInt
	(/=) = neInt
	
instance Eq Float where
	(==) = eqFloat
	(/=) = neFloat

复制代码

咱们假设 eqInt、neInt、eqFloat、neFloat 都已经由标准库实现了

这样就能够直接用 ==/= 函数对 Int 和 Float 进行判等了

-- 判断 Int 的相等性
== 1 2
/= 2 4

-- 判断 Float 的相等性
== 1.2 1.2
/= 2.4 2.1
复制代码

在调用 ==/= 函数时,编译器会根据参数类型自动找到类型类实例,而后调用类型类实例的函数执行调用。

若是用户须要自定义判等函数,只须要实现本身的类型类实例便可。

此时你可能会不自觉的和最开始提到的继承方案作一个对比,我画了两个图,能够参考一下

  • 继承方案的类型结构是一个层次型的

  • Type classes 方案的类型结构是线性的

若是仅仅从结构上来看的话,它们之间的差异就像 ComparableComparator 同样。

Scala 与 Type classes Pattern

目前的 Java 是没法实现 Type classes 的,但同为 JVM 的语言,多范式的 Scala 却能够实现。

与 Haskell 不同, Type classes 在 Scala 中并非一等公民,也就是没有直接的语法支持,但借助于强大的隐式系统咱们也能实现 Type classes,因为实现的步骤比较公式化,也就被称之为 Type classes Pattern (类型类模式)。

在 Scala 中实现 Type classes Pattern 大体分为 3 个步骤

  1. 定义 Type class
  2. 实现 Type class 实例
  3. 定义包含隐式参数的函数

仍是之前面提到的判等问题为需求,按照前面总结的模式步骤来实现一个 Scala 版的 Type classes 解决方案。

第一步定义 Type class,实际就是定义一个带泛型参数的 trait

trait 也相似于 Java 的 interface,不过更增强大

trait Eq[T] {
  def eq(a: T, b: T): Boolean
}
复制代码

接着咱们针对 String、Int 来实现两个类型类实例

object EqInstances {

  implicit val intEq = new Eq[Int] {
    override def eq(a: Int, b: Int) = a == b
  }

  implicit val stringEq = instance[String]((a, b) => a.equals(b))

  def instance[T](func: (T, T) => Boolean): Eq[T] = new Eq[T] {
    override def eq(a: T, b: T): Boolean = func(a, b)
  }
}
复制代码

stringEq 和 intEq 采用了不一样的构造方式

  • stringEq 实例我采用的是相似于 Java 的匿名类进行构造
  • intEq 实例则采用了高阶函数来实现

两个实例都被 implicit 关键字修饰,通常称之为隐式值,做用会在后面讲到。

最后一步,来实现一个带隐式参数的 same 函数, 其实调用类型类实例来判断两个值是否相等

object Same {
  def same[T](a: T, b: T)(implicit eq: Eq[T]): Boolean = eq.eq(a, b)
}

复制代码
  • implicit eq: Eq[T] 就是隐式参数, 调用方能够不用主动传入,编译器会在做用域内查找匹配的隐式值传入(这就是为何前面的实例须要被 implicit 修饰)

最后来进行调用验证一下,在调用时咱们须要先在当前做用域内经过 import 关键字导入类型类实例(主要是为了让编译器能找到这些实例)

import EqInstances._

Same.same(1, 2)

Same.same("ok", "ok")

// 编译错误:no implicits found for parameter eq: Eq[Float]
Same.same(1.0F, 2.4F)
复制代码

能够看见,针对 Int 和 String 类型的 same 函数调用能经过编译, 而当参数是 Float 时调用就会提示编译错误,这就是由于编译器在做用域内没有找到能够处理 Float 类型的 Eq 实例。

关于 Scala 隐式查找的更多规则能够查看 docs.scala-lang.org/tutorials/F…

到这儿其实就差很少了,可是这样的写法在 Scala 里其实不是很优雅,咱们能够再经过一些小技巧优化一下

  • same 函数改成 apply 函数,能够简化调用

  • 使用 context bound 优化隐式参数,别慌,context bound 实际就是个语法糖而已

object Same {
  def apply[T: Eq](a: T, b: T): Boolean = implicitly[Eq[T]].eq(a, b)
}

// 使用 apply 做为函数, 调用时能够不用写函数名
Same(1, 1)
Same("hello", "world")
复制代码

简单说一下 context bund,首先泛型的定义 由 T 变成了 [T: Eq],这样就能够用 implicitly[Eq[T]] 让编译器在做用域内找到一个 Eq[T] 的隐式实例,context bound 可让函数的签名更加简洁。

在 Scala 中,类型类的设计其实随处可见,典型的就有 Ordered

回望 Java

以判等问题引出 Type classes 有一些不足,咱们只意识到了与 OOP 的继承是一个不同的判等解决方案,不妨再回到 Java 作一些其余的比较。

Comparator[T] 接口为例,在 Java 中咱们常常在集合框架中这样使用

List<Integer> list = new ArrayList<>();
list.sort(Comparator.naturalOrder())
复制代码

若是将其改形成为 Type classes 的话

trait Comparator[T] {
  def compare(o1: T, o2: T): Int
}

object Instances { 
  implicit val intComprator = new Comparator[T] {
    def compare(o1: Int, o2: Int) = o1.compareTo(o2)
  }
  
  //... other instances
}
复制代码

List 的 sort 方法也须要改成带隐式参数的方法前面,这样咱们就不须要显示的传 Compartor 实例了

// 编译期会自动找到 Comparator[Integer] 实例
List[Integer] list = new ArrayList<>();
list.sort()
复制代码

能够认为上面的 Type classes 是基于 Scala 语法的伪代码

相信你也看出来了,与 Type classes 方案相比,最大的差异就是 Java 须要手动传入 Comparator 实例,也许你会疑惑:就这?

不要小看这二者的区别,这二者的区别就像用 var 定义类型同样

// Java8
Map<String, String> map2 = new HashMap<>();

// Java10
var map = new HashMap<String, String>();
复制代码

若是类型系统能帮你完成的事情,就让它帮你作吧!

总结一下

看了 Haskell 和 Scala 的例子,最后仍是得总结一下:

Type classes 就是抽象了某一些类型的共同行为,当某个类型须要用到这些行为时,由类型系统去找到这些行为的具体实现。

未完待续

最后仍是得再安利一下 Scala3,在 Scala3 中, Type classes 获得了足够的重视,直接提供了语法层面的支持,不再用写一大堆的模板代码, 今后能够叫作 Type classes without Pattern

不过为了不“长篇大论”,相关的内容就留给下一篇文章了(点赞点赞点赞)。

弱弱的皮一下,还学得动吗?

参考

  1. 《How to make ad-hoc polymorphism less ad hoc》
  2. Of Scala Type Classes
  3. Where does Scala look for implicits?
  4. Scala 隐式参数
  5. Type classes for the Java Engineer
  6. OOP vs type classes
  7. Cats: Type classes
相关文章
相关标签/搜索