【发布时间】:2012-01-21 10:21:26
【问题描述】:
在 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]
隐式转换
- 因为我创建了一个新对象,我可以在那里缓存值或计算更好的表示;但是我应该避免这种情况吗,因为它可能会发生多次,并且可能只会调用一次显式转换?
我对答案的期望
介绍一个(或多个)用例,其中两个概念之间的差异很重要,并解释为什么我更喜欢其中一个。即使没有示例,也可以解释这两个概念的本质及其相互关系。
【问题讨论】:
-
尽管类型类使用上下文边界,但在类型类点中您提到“视图边界”时存在一些混淆。
-
+1 个很好的问题;我对这个问题的彻底回答非常感兴趣。
-
@Daniel 谢谢。我总是搞错。
-
您在一个地方弄错了:在第二个隐式转换示例中,您将列表的
size存储在一个值中,并说它避免了在后续调用 quantify 时对列表进行昂贵的遍历,但是在您每次调用quantify时,list2quantifiable都会再次被触发,从而重新实例化Quantifiable并重新计算quantify属性。我要说的是,实际上没有办法通过隐式转换来缓存结果。 -
@NikitaVolkov 您的观察是正确的。我在倒数第二段的问题中解决了这个问题。当转换后的对象在一个转换方法调用后使用更长时间(并且可能以其转换后的形式传递)时,缓存会起作用。虽然类型类在深入时可能会沿着未转换的对象链接。
标签: scala coding-style implicit-conversion