在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]
隐式转化
提出一个(或多个)用例,其中两个概念之间的差异很重要,并解释为什么我更喜欢一个概念。同样解释这两个概念的本质及其相互之间的关系也很好,即使没有例子。
答案 0 :(得分:39)
虽然我不想从Scala In Depth复制我的材料,但我认为值得注意的是类型类/类型特征可以更灵活。
def foo[T: TypeClass](t: T) = ...
能够在其本地环境中搜索默认类型类。但是,我可以通过以下两种方式之一随时覆盖默认行为:
以下是一个例子:
def myMethod(): Unit = {
// overrides default implicit for Int
implicit object MyIntFoo extends Foo[Int] { ... }
foo(5)
foo(6) // These all use my overridden type class
foo(7)(new Foo[Int] { ... }) // This one needs a different configuration
}
这使得类型类更加灵活。另一件事是类型类/特征更好地支持隐式查找。
在第一个示例中,如果使用隐式视图,编译器将对以下内容执行隐式查找:
Function1[Int, ?]
查看Function1
的伴随对象和Int
随播广告对象。
请注意,隐式查找中Quantifiable
无处。这意味着您必须将隐式视图放在包对象或中将其导入范围。记住正在发生的事情还需要做更多工作。
另一方面,类型类是显式。你可以在方法签名中看到它正在寻找什么。您还隐式查找
Quantifiable[Int]
将查看Quantifiable
的伴随对象和 Int
的伴随对象。这意味着您可以提供默认值和新类型(如MyString
类)可以在其伴随对象中提供默认值,并且将隐式搜索它。
通常,我使用类型类。对于最初的例子,它们更加灵活。我使用隐式转换的唯一地方是在Scala包装器和Java库之间使用API层时,如果你不小心,这甚至可能是“危险的”。
答案 1 :(得分:20)
可以发挥作用的一个标准是您希望新功能如何感受"感觉"喜欢;使用隐式转换,您可以使它看起来只是另一种方法:
"my string".newFeature
...在使用类型类时,它总是看起来像是在调用外部函数:
newFeature("my string")
使用类型类而不是隐式转换可以实现的一件事是向类型添加属性,而不是向类型的实例添加属性。然后,即使您没有可用类型的实例,也可以访问这些属性。一个典型的例子是:
trait Default[T] { def value : T }
implicit object DefaultInt extends Default[Int] {
def value = 42
}
implicit def listsHaveDefault[T : Default] = new Default[List[T]] {
def value = implicitly[Default[T]].value :: Nil
}
def default[T : Default] = implicitly[Default[T]].value
scala> default[List[List[Int]]]
resN: List[List[Int]] = List(List(42))
这个例子还说明了概念是如何紧密相关的:如果没有机制来生成无限多个实例,那么类型类就不会那么有用了。如果没有implicit
方法(不是转换),我只能拥有有限多种类型的Default
属性。
答案 2 :(得分:13)
您可以通过类比功能应用程序来考虑这两种技术之间的区别,只需使用命名包装器即可。例如:
trait Foo1[A] { def foo(a: A): Int } // analogous to A => Int
trait Foo0 { def foo: Int } // analogous to Int
前者的实例封装了类型A => Int
的函数,而后者的实例已经应用于A
。你可以继续这种模式......
trait Foo2[A, B] { def foo(a: A, b: B): Int } // sort of like A => B => Int
因此您可以认为Foo1[B]
有点像Foo2[A, B]
部分应用于某个A
实例。 Miles Sabin将"Functional Dependencies in Scala"写成了一个很好的例子。
所以我的意思是,原则上: