在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
的list2quantifiable
被触发从而重新实例化Quantifiable
并重新计算该quantify
属性。我的意思是实际上没有办法用隐式转换来缓存结果。