在Scala中,我们可以使用至少两种方法来适配现有或新类型。假设我们想要表达某个东西可以使用Int
进行量化。我们可以定义以下特质。
隐式转换
trait Quantifiable{ def quantify: Int }
然后我们可以使用隐式转换来量化例如字符串和列表。
implicit def string2quant(s: String) = new Quantifiable{
def quantify = s.size
}
implicit def list2quantifiable[A](l: List[A]) = new Quantifiable{
val quantify = l.size
}
导入这些后,我们可以在字符串和列表上调用方法quantify
。请注意,可量化列表存储其长度,因此避免了在后续对quantify
的调用中对列表进行昂贵的遍历。
类型类
另一种方法是定义一个“证人”Quantified[A]
,它表明某些类型A
可以被量化。
trait Quantified[A] { def quantify(a: A): Int }
然后我们在某个地方为String
和List
提供了这个类型类的实例。
implicit val stringQuantifiable = new Quantified[String] {
def quantify(s: String) = s.size
}
如果我们想要编写一个需要量化其参数的方法,我们会这样写:
def sumQuantities[A](as: List[A])(implicit ev: Quantified[A]) =
as.map(ev.quantify).sum
或者使用上下文绑定语法:
def sumQuantities[A: Quantified](as: List[A]) =
as.map(implicitly[Quantified[A]].quantify).sum
但是什么时候使用哪种方法呢?
现在问题来了,我该如何决定这两个概念之间的区别?
我目前所注意到的是:
类型类
- 类型类允许使用美好的上下文限定语法
- 使用类型类时,我不会在每次使用时创建新的包装对象
- 如果类型类具有多个类型参数,则上下文绑定语法将不再起作用;想象一下,我不仅想量化整数,还想量化某些通用类型
T
的值。我希望创建一个类型类Quantified[A,T]
隐式转换
- 由于我创建了一个新对象,因此我可以在其中缓存值或计算更好的表示形式;但是是否应该避免这样做,因为它可能会发生多次,并且显式转换可能只会被调用一次?
我期望得到的答案
提供一个(或多个)使用案例,说明两个概念之间的区别很重要,并解释为什么我更喜欢其中的一个。即使没有示例,也要解释这两个概念的本质及其相互关系。
size
存储在一个值中,并说它避免了在后续调用quantify
时昂贵的列表遍历,但是每次调用quantify
时,list2quantifiable
都会再次触发,从而重新实例化Quantifiable
并重新计算quantify
属性。我的意思是,实际上没有办法使用隐式转换缓存结果。 - Nikita Volkov