前言
本篇内容主要来学习Cats中另一个基础的type class:Eq。
学习程度:需要完全掌握
1.5 Eq:一个类型安全的判等Type class
本章节我们继续来学习一个非常实用的type class:cats.Eq。Eq主要是为了类型安全的判等设计的,因为Scala内置的 ==操作符有时会给我们带来困扰。
大多数Scala程序员都应该写过类似下面的代码:
List(1, 2, 3).map(Option(_)).filter(item => item == 1)
// res0: List[Option[Int]] = List()
可能很多人都不会犯这么简单的错误,但是这是可能存在的,filter里面的判断逻辑会一直返回false,因为Int和Option[Int]是不可能相等的。
这是开发者的错,我们应该用Some(1)去比较而不是1。然而这在技术上来说并不能说它是错的,因为==可以作用于任意的两个对象,不用关心具体的类型。Eq的设计,解决了这个问题,因为它是类型安全的。
1.5.1 Equality, Liberty and Fraternity
我们可以使用Eq对任意给定类型的对象进行类型安全的判等:
package cats
trait Eq[A] {
def eqv(a: A, b: A): Boolean
// other concrete methods based on eqv...
}
与Show类似,Eq对应的interface syntax,声明在cats.syntax.eq这个包中,它提供了两个执行判等的方法,你可以直接使用,当然前提是在implicit scope中导入了对应的instance:
- === 比较两个对象相等
- =!= 比较两个对象不相等
1.5.2 比较Int类型
让我们来看些例子,首先我们需要先导入Eq的type class:
import cats.Eq
接着,我们来获取一个Int的instance:
import cats.instances.int._ // for Eq
val eqInt = Eq[Int]
我们可以直接使用eqInt来进行判等:
eqInt.eqv(123, 123)
// res2: Boolean = true
eqInt.eqv(123, 234)
// res3: Boolean = false
不同于Scala的==操作符,假如你试图用eqv去比较两个不同类型的对象时,编译将会报错:
eqInt.eqv(123, "234")
// <console>:18: error: type mismatch; // found : String("234")
// required: Int
// eqInt.eqv(123, "234")
// ^
我们同样可以使用interface syntax语法,但需要先导入cats.syntax.eq,然后我们就可以直接使用 === 和 =!=方法:
import cats.syntax.eq._ // for === and =!=
123 === 123
// res5: Boolean = true
123 =!= 234
// res6: Boolean = true
同样,我们尝试去比较两个不同类型的对象时也会编译报错:
123 === "123"
// <console>:20: error: type mismatch;
// found : String("123")
// required: Int
// 123 === "123"
//
1.5.3 比较Option类型
接下来我们来看一个更有趣的例子—Option[Int]。如果要比较Option[Int]类型的值,我们需要先导入Option以及Int对应的instances:
import cats.instances.int._ // for Eq
import cats.instances.option._ // for Eq
现在我们来尝试进行一些比较:
Some(1) === None
// <console>:26: error: value === is not a member of Some[Int] // Some(1) === None
//
编译发现了一个错误,原因是类型没匹配上,我们导入的是Int以及Option[Int]对应Eq的instances,所以Some[Int]是无法比较的,要解决这个问题我们需要将值的类型指定为Option[Int]:
(Some(1) : Option[Int]) === (None : Option[Int])
// res9: Boolean = false
更友好的方式是利用标准库中的Option.apply和Option.empty方法:
Option(1) === Option.empty[Int]
// res10: Boolean = false
或者使用cats.syntax.option中特殊语法:
import cats.syntax.option._ // for some and none
1.some === none[Int]
// res11: Boolean = false
1.some =!= none[Int]
// res12: Boolean = true
1.5.4 比较自定义类型
我们可以为自定义的类型创建一个关于Eq的instance,它接收一个(A, A) => Boolean 的方法返回一个Eq[A]:
import java.util.Date
import cats.instances.long._ // for Eq
implicit val dateEq: Eq[Date] =
Eq.instance[Date] { (date1, date2) =>
date1.getTime === date2.getTime
}
val x = new Date() // now
val y = new Date() // a bit later than now
x === x
// res13: Boolean = true
x === y
// res14: Boolean = false
1.5.5 练习: 实现Cat类型的判等
实现一个Cat类型关于Eq的instance:
final case class Cat(name: String, age: Int, color: String)
并对下面这些对象进行判等操作:
val cat1 = Cat("Garfield", 38, "orange and black")
val cat2 = Cat("Heathcliff", 33, "orange and black")
val optionCat1 = Option(cat1)
val optionCat2 = Option.empty[Cat]
代码见示例