Open kun-song opened 6 years ago
implicit
作用域Scala 编译器从 implicit
作用中查找需要的 implicit
值,而 implicit
作用域由多部分组成,不同部分优先级也不同:
import
引入的标识符等sorted[B >: A](implicit ord: math.Ordering[B]): List[A]
Scala 编译器会在如下伴生对象中查找 Ordering
实例:
List
伴生对象,因为 sorted
定义在 List
上Ordering
的伴生对象B
的伴生对象前面我们为 Rational
创建了 Ordering
对象,借助伴生对象,可以提升其可读性,首先,最直接的版本是将 Ordering[Rational]
定义在局部作用域中:
final case class Rational(numerator: Int, denominator: Int)
object Demo {
def demo = {
implicit val rationalOrdering: Ordering[Rational] = Ordering.fromLessThan[Rational]{
(a, b) ⇒ a.numerator * b.denominator < b.numerator * a.denominator
}
assert(List(Rational(1, 2), Rational(3, 4), Rational(1, 3)).sorted ==
List(Rational(1, 3), Rational(1, 2), Rational(3, 4)))
}
}
这当然没有问题,完全可以,但我们不想把 implicit
值放在局部作用域中,我们想要把它移出 Demo
类,由于 implicit
值只能位于 class object trait 等内部,所以可以将其移动到另外一个 object 内部:
final case class Rational(numerator: Int, denominator: Int)
object Instance {
implicit val rationalOrdering: Ordering[Rational] = Ordering.fromLessThan[Rational]{
(a, b) ⇒ a.numerator * b.denominator < b.numerator * a.denominator
}
}
object Demo extends App {
def demo = {
assert(List(Rational(1, 2), Rational(3, 4), Rational(1, 3)).sorted ==
List(Rational(1, 3), Rational(1, 2), Rational(3, 4)))
}
demo
}
但运行该例子,将会报如下错误:
Error:(14, 65) No implicit Ordering defined for com.huawei.archive.demos.Rational.
assert(List(Rational(1, 2), Rational(3, 4), Rational(1, 3)).sorted ==
此时,编译器无法为 sorted
方法找到合适的 Ordering
对象吗,换句话说,定义在 Instance
中的 rationalOrdering
对 sorted
不可见,将 rationalOrdering
放到 Rational
的伴生对象中即可解决该问题:
final case class Rational(numerator: Int, denominator: Int)
object Rational {
implicit val rationalOrdering: Ordering[Rational] = Ordering.fromLessThan[Rational]{
(a, b) ⇒ a.numerator * b.denominator < b.numerator * a.denominator
}
}
object Demo extends App {
def demo = {
assert(List(Rational(1, 2), Rational(3, 4), Rational(1, 3)).sorted ==
List(Rational(1, 3), Rational(1, 2), Rational(3, 4)))
}
demo
}
这里引出打包 type class instance 的第一条原则:
When defining a type class instance, if
- there is a single instance for the type; and
- you can edit the code for the type that you are defining the instance for
then define the type class instance in the companion object of the type.
打开 Ordering
伴生对象的源码,可以看到里面已经预定义了很多 type class instance,例如 Ordering[Int]
,而前面我们也定义了自己的 Ordering[Int]
,而且并没有引起编译错误,这说明不同 implicit
作用域之间有优先级。
完整的 implicit
优先级非常复杂,但有一条非常符合直觉的规则:local scope
优先于 companion object scope
,即用户在局部作用域中:
import
语句引入的 type class instance优先级都高于伴生对象中的 type class instance。
这引出了打包 type class instance 的第二个原则:
When defining a type class instance, if
- there is a single good default instance for the type; and
- you can edit the code for the type that you are defining the instance for
then define the type class instance in the companion object of the type. This allows users to override the instance by defining one in the local scope whilst still providing sensible default behaviour.
如果 Rational
类型没有一个合适的默认 object Rational
,或者可能有多个时,把 implicit
放到 Rational
的对生对象中合适吗?当然不合适!
此时可以将每个 type class instance 都放在自己单独的对象中,例如:
final case class Rational(numerator: Int, denominator: Int)
object RationalLessThanOrdering {
implicit val ordering = Ordering.fromLessThan[Rational]((x, y) =>
(x.numerator.toDouble / x.denominator.toDouble) <
(y.numerator.toDouble / y.denominator.toDouble)
)
}
object RationalGreaterThanOrdering {
implicit val ordering = Ordering.fromLessThan[Rational]((x, y) =>
(x.numerator.toDouble / x.denominator.toDouble) >
(y.numerator.toDouble / y.denominator.toDouble)
)
}
用户使用时,手动 import
想要用的 type class instance 即可。
使用 Order
模拟订单,其中 units
为数量,unitPrice
为单价:
final case class Order(units: Int, unitPrice: Double) {
val total: Double = unitPrice * units
}
分别为 Order
实现按照 total
units
unitPrice
三种方式排序:
object Order {
implicit val totalOrdering: Ordering[Order] = Ordering.fromLessThan(_.total < _.total)
}
object UnitsOrder {
implicit val unitsOrdering: Ordering[Order] = Ordering.fromLessThan(_.units < _.units)
}
object PriceOrder {
implicit val priceOrdering: Ordering[Order] = Ordering.fromLessThan(_.unitPrice < _.unitPrice)
}
object Demo extends App {
import PriceOrder.priceOrdering
val xs = List(Order(1, 1.0), Order(1, 2.0), Order(1, 1.5), Order(2, 0.2))
val r1 = xs.sorted
println(r1)
}
基础
在 Scala 中,type class 即为
trait
,使用 type class 一般需要:trait
的实现,称之为type class instance
type class instance
标注为implicit
值Scala 中的
Ordering
特质即为 type class,其使用方式如下:为了避免每次都要手动给
sorted
函数传递参数,可以将常用的Ordering
实例声明为implicit
:但同一作用域内,同一类型只能有一个
implicit
实例,否则 Scala 编译器无法选择:上面的例子,将报如下错误:
可以实现绝对值排序:
也可以为自定义类型实现
Ordering
实例:可以看到
Ordering
特质实现了排序函数,但未指定具体类型应该如何排序,实际使用时,需要程序员自己去定义具体类型的排序规则,排序规则定义好后,即可享受Ordering
特质支持的其他功能,比如例子中的sorted
方法,所以 type class 是一种高度抽象。