首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >隐式转换与类型类

隐式转换与类型类
EN

Stack Overflow用户
提问于 2011-12-15 18:30:51
回答 3查看 8.1K关注 0票数 96

在Scala中,我们至少可以使用两种方法来改造现有的或新的类型。假设我们想用Int来表示某种东西可以量化。我们可以定义以下特征。

隐式转换

代码语言:javascript
复制
trait Quantifiable{ def quantify: Int }

然后我们可以使用隐式转换来量化例如String和List。

代码语言:javascript
复制
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可以量化。

代码语言:javascript
复制
trait Quantified[A] { def quantify(a: A): Int }

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

代码语言:javascript
复制
implicit val stringQuantifiable = new Quantified[String] {
  def quantify(s: String) = s.size 
}

如果我们写了一个需要量化它的参数的方法,我们会写:

代码语言:javascript
复制
def sumQuantities[A](as: List[A])(implicit ev: Quantified[A]) = 
  as.map(ev.quantify).sum

或使用上下文绑定语法:

代码语言:javascript
复制
def sumQuantities[A: Quantified](as: List[A]) = 
  as.map(implicitly[Quantified[A]].quantify).sum

但是什么时候使用哪种方法呢?

现在问题来了。我怎样才能在这两个概念之间作出决定?

到目前为止我已经注意到了。

类型类

  • 类型类允许良好的上下文绑定语法。
  • 使用类型类,我不会在每次使用时创建一个新的包装器对象
  • 如果类型类有多个类型参数,那么上下文绑定语法就不再起作用了;想象一下,我不仅想用整数量化东西,而且要用一些通用类型T的值来量化。我想要创建一个类型类Quantified[A,T]

隐式转换

  • 因为我创建了一个新对象,所以我可以在那里缓存值或计算一个更好的表示;但是我是否应该避免这种情况,因为它可能会发生几次,并且可能只调用一次显式转换?

我希望从答案中得到什么

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

EN

回答 3

Stack Overflow用户

发布于 2011-12-16 13:49:37

虽然我不想从斯卡拉深度复制我的材料,但我认为值得注意的是,类型类/类型特性具有无限的灵活性。

代码语言:javascript
复制
def foo[T: TypeClass](t: T) = ...

具有搜索其本地环境以查找默认类型类的能力。但是,我可以通过以下两种方式之一在任何时候重写默认行为:

  1. 在作用域中创建/导入隐式类型类实例以进行短路隐式查找。
  2. 直接传递类型类

下面是一个例子:

代码语言:javascript
复制
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
}

这使得类型类更加灵活。另一件事是类型类/特性更好地支持隐式查找。

在第一个示例中,如果使用隐式视图,编译器将执行隐式查找:

代码语言:javascript
复制
Function1[Int, ?]

它将查看Function1的同伴对象和Int同伴对象。

注意,在隐式查找中Quantifiable是不存在的。这意味着您必须将隐式视图放置在包对象中,或者将其导入范围。更多的工作是记住发生了什么。

另一方面,类型类是显式的。您可以在方法签名中看到它正在寻找的内容。您还可以隐式查找

代码语言:javascript
复制
Quantifiable[Int]

它将查看Quantifiable的伙伴对象和Int的伙伴对象。这意味着您可以提供默认值,新类型(如MyString类)可以在其同伴对象中提供默认设置,并将被隐式搜索。

通常,我使用类型类。对于最初的例子,它们是无限灵活的。我使用隐式转换的唯一地方是在Scala包装器和Java库之间使用API层时,如果您不小心的话,这甚至可能是“危险的”。

票数 42
EN

Stack Overflow用户

发布于 2011-12-15 20:55:18

一个可以发挥作用的标准是,您希望新特性“感觉”是什么样的;使用隐式转换,您可以使它看起来像是另一种方法:

代码语言:javascript
复制
"my string".newFeature

使用类型类的...while,它总是看起来像在调用外部函数:

代码语言:javascript
复制
newFeature("my string")

使用类型类而不是隐式转换可以实现的一件事是向类型添加属性,而不是向类型的实例添加属性。然后,即使没有可用类型的实例,也可以访问这些属性。一个典型的例子是:

代码语言:javascript
复制
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属性。

票数 20
EN

Stack Overflow用户

发布于 2011-12-15 23:37:18

您可以通过类比函数应用程序来考虑这两种技术之间的区别,只需使用一个命名的包装器即可。例如:

代码语言:javascript
复制
trait Foo1[A] { def foo(a: A): Int }  // analogous to A => Int
trait Foo0    { def foo: Int }        // analogous to Int

前者的实例封装了A => Int类型的函数,而后者的实例已经应用于A。你可以继续这种模式..。

代码语言:javascript
复制
trait Foo2[A, B] { def foo(a: A, b: B): Int } // sort of like A => B => Int

因此,您可以将Foo1[B]看作是Foo2[A, B]在某些A实例中的部分应用程序。这方面的一个很好的例子是由Miles写成的“Scala中的函数依赖项”

所以,我的观点是,原则上:

  • “拉皮条”类(通过隐式转换)是“零阶”情况.
  • 宣布一个类型是“第一命令”的情况..。
  • 具有fundeps (或类似fundeps)的多参数类型类是一般情况。
票数 13
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/8524878

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档