隐式转换与types类
在Scala中,我们可以使用至less两种方法来改造现有的或新的types。 假设我们想expression一些东西可以用Int
来量化。 我们可以定义以下特征。
隐式转换
trait Quantifiable{ def quantify: Int }
然后我们可以使用隐式转换来量化string和列表。
implicit def string2quant(s: String) = new Quantifiable{ def quantify = s.size } implicit def list2quantifiable[A](l: List[A]) = new Quantifiable{ val quantify = l.size }
导入这些后,我们可以调用string和列表的方法quantify
。 请注意,可量化列表存储其长度,所以它避免了在后续调用中进行昂贵的遍历。
types类
另一种方法是定义一个“证人” 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
但是何时使用哪种方法?
现在来了这个问题。 我怎么能在这两个概念之间做出决定?
到目前为止我已经注意到了。
types的类
- types的类允许很好的上下文绑定语法
- 与types类我不会在每次使用时创build一个新的包装对象
- 如果types类具有多个types参数,则上下文绑定语法不再起作用; 想象我不仅要用整数量化事物,还要用一些一般types
T
值来量化事物。 我想创build一个typesQuantified[A,T]
隐式转换
- 因为我创build一个新的对象,我可以caching值或计算更好的表示; 但我是否应该避免这种情况,因为它可能会发生多次,显式转换可能只会被调用一次?
我期待从答案
出现一个(或多个)用例,其中两个概念之间的区别很重要,并解释为什么我更喜欢一个。 同时解释这两个概念的本质和它们之间的关系也是很好的,即使没有例子。
虽然我不想从Scala深度复制我的材料,但我认为值得注意的是typestypes/types特性是无限灵活的。
def foo[T: TypeClass](t: T) = ...
有能力search其本地环境的默认types的类。 但是,我可以通过以下两种方法之一随时覆盖默认行为:
- 在范围中创build/导入隐式types实例以短路隐式查找
- 直接传递一个types的类
这是一个例子:
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 }
这使得types无限更加灵活。 另一件事是types类/特性支持隐式查找更好。
在第一个示例中,如果使用隐式视图,编译器将为以下内容执行隐式查找:
Function1[Int, ?]
这将看Function1
的伴侣对象和Int
伴侣对象。
请注意,在隐式查找中, Quantifiable
是无处可查的。 这意味着您必须将隐式视图放置在包对象中或将其导入范围。 记住发生了什么是更多的工作。
另一方面,types类是明确的 。 您可以在方法签名中看到要查找的内容。 你也有一个隐式的查找
Quantifiable[Int]
它将在Quantifiable
的伴侣对象和 Int
的伴侣对象中看待。 这意味着你可以提供默认值和新types(比如一个MyString
类)可以在他们的伴侣对象中提供一个默认值,并且会被隐式search。
一般来说,我使用types类。 它们对于最初的例子来说是非常灵活的。 我使用隐式转换的唯一地方是在Scala包装器和Java库之间使用API层,如果不小心的话,这可能是“危险的”。
可以发挥作用的一个标准是如何让新function“感觉”喜欢; 使用隐式转换,你可以使它看起来只是另一种方法:
"my string".newFeature
…使用types类时,它总是看起来像你正在调用一个外部函数:
newFeature("my string")
你可以通过types类而不是隐式转换来实现的一件事就是将属性添加到types中 ,而不是types的实例。 即使没有可用types的实例,也可以访问这些属性。 典型的例子是:
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))
这个例子也显示了这些概念是如何紧密相关的:如果没有机制来产生无数的实例,那么types类就不是那么有用; 没有implicit
方法(不是一个转换,诚然),我只能有有限的许多types有Default
属性。
您可以通过类比function应用程序来思考两种技术之间的区别,只需要一个命名的包装器。 例如:
trait Foo1[A] { def foo(a: A): Int } // analogous to A => Int trait Foo0 { def foo: Int } // analogous to Int
前者的一个实例封装了A => Int
types的函数,而后者的一个实例已经被应用到A
。 你可以继续这个模式
trait Foo2[A, B] { def foo(a: A, b: B): Int } // sort of like A => B => Int
因此您可以将Foo1[B]
想象成Foo1[B]
Foo2[A, B]
部分应用于某个A
实例。 Miles Sabin写的一个很好的例子就是“Scala中的函数依赖” 。
所以我的观点原则上是这样的:
- “拉皮”一个类(通过隐式转换)是“零阶”情况…
- 声明一个types类是“一阶”的情况下…
- 具有fundeps的多参数types类(或类似fundeps)是一般情况。