隐式转换与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 } 

然后我们为StringList提供这个类的实例。

 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参数,则上下文绑定语法不再起作用; 想象我不仅要用整数量化事物,还要用一些一般typesT值来量化事物。 我想创build一个typesQuantified[A,T]

隐式转换

  • 因为我创build一个新的对象,我可以caching值或计算更好的表示; 但我是否应该避免这种情况,因为它可能会发生多次,显式转换可能只会被调用一次?

我期待从答案

出现一个(或多个)用例,其中两个概念之间的区别很重要,并解释为什么我更喜欢一个。 同时解释这两个概念的本质和它们之间的关系也是很好的,即使没有例子。

虽然我不想从Scala深度复制我的材料,但我认为值得注意的是typestypes/types特性是无限灵活的。

 def foo[T: TypeClass](t: T) = ... 

有能力search其本地环境的默认types的类。 但是,我可以通过以下两种方法之一随时覆盖默认行为:

  1. 在范围中创build/导入隐式types实例以短路隐式查找
  2. 直接传递一个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 => Inttypes的函数,而后者的一个实例已经被应用到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)是一般情况。