隐式转换vs.类型类 [英] Implicit conversion vs. type class

查看:143
本文介绍了隐式转换vs.类型类的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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



隐式转换



  {def quantify:Int} 

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

  implicit def string2quant(s:String)= new Quantizable {
def quantify = s.size
}
implicit def list2quantifiable [A](l:List [A])= new Quantizable {
val quantify = l.size
}

导入这些后,我们可以在字符串和列表上调用 quantify 注意,可量化列表存储它的长度,因此它避免了对 quantify 的后续调用的昂贵的遍历列表。

< h3>类型类

另一种方法是定义一个见证 Quantified [A] 类型 A 可以量化。

  quantify(a:A):Int} 

$ c> String 和列出某处。

  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



但何时使用哪种方法?



现在问题来了。如何在这两个概念之间做出选择?



到目前为止我注意到了什么。






  • 类型类允许使用nice上下文绑定语法

  • 不要在每次使用时创建新的包装器对象

  • 如果类型类有多个类型参数,上下文绑定语法将不再工作;想象我想量化东西不仅用整数,而是一些一般类型的值 T 。我想创建一个类型类 Quantified [A,T]



隐式转换




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



一个回答



呈现一个(或多个)用例,其中两个概念之间的区别很重要,并解释为什么我更喜欢一个。

解决方案

虽然我没有不想从 Scala In Depth 复制我的材料,我认为值得注意的是类型类/类型特性无限更灵活。 p>

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

具有在其本地环境中搜索默认类型类的能力。但是,我可以通过以下两种方法之一覆盖默认行为:


  1. 在Scope中创建/导入一个隐式类型实例-circuit implicit lookup

  2. 直接传递类型类

这里有一个例子:

  def myMethod():Unit = {
//覆盖默认隐式Int
隐含对象MyIntFoo extends Foo [ int] {...}
foo(5)
foo(6)//这些都使用我重写的类型类
foo(7)(new Foo [Int] {... })//这需要一个不同的配置
}

灵活。另一件事是类型classes / traits更好地支持隐式查找



在第一个例子中,如果使用隐式视图,进行隐式查找:

  Function1 [Int,?] 

这将查看Function1的伴随对象和Int companion对象。



注意Quantizable是无处隐藏查找。这意味着您必须在包对象中放置隐式视图将其导入范围。更多的工作要记住发生了什么。



另一方面,类型类是显式。你看到它在方法签名中寻找。您还有一个隐式查找

  Quantifiable [Int] 

它会显示在Quantizable的伴随对象 Int的伴随对象中。这意味着您可以提供默认的新类型(例如MyString类)可以在其伴随对象中提供默认值,并且将被隐式搜索。



一般来说,我使用类型类。它们对于初始示例无限更灵活。我使用隐式转换的唯一地方是在Scala包装程序和Java库之间缓动一个API层,如果你不小心,甚至这可能是危险的。


In Scala, we can use at least two methods to retrofit existing or new types. Suppose we want to express that something can be quantified using an Int. We can define the following trait.

Implicit conversion

trait Quantifiable{ def quantify: Int }

And then we can use implicit conversions to quantify e.g. Strings and Lists.

implicit def string2quant(s: String) = new Quantifiable{ 
  def quantify = s.size 
}
implicit def list2quantifiable[A](l: List[A]) = new Quantifiable{ 
  val quantify = l.size 
}

After importing these, we can call the method quantify on strings and lists. Note that the quantifiable list stores its length, so it avoids the expensive traversal of the list on subsequent calls to quantify.

Type classes

An alternative is to define a "witness" Quantified[A] that states, that some type A can be quantified.

trait Quantified[A] { def quantify(a: A): Int }

We then provide instances of this type class for String and List somewhere.

implicit val stringQuantifiable = new Quantified[String] {
  def quantify(s: String) = s.size 
}

And if we then write a method that needs to quantify its arguments, we write:

def sumQuantities[A](as: List[A])(implicit ev: Quantified[A]) = 
  as.map(ev.quantify).sum

Or using the context bound syntax:

def sumQuantities[A: Quantified](as: List[A]) = 
  as.map(implicitly[Quantified[A]].quantify).sum

But when to use which method?

Now comes the question. How can I decide between those two concepts?

What I have noticed so far.

type classes

  • type classes allow the nice context bound syntax
  • with type classes I don't create a new wrapper object on each use
  • the context bound syntax does not work anymore if the type class has multiple type parameters; imagine I want to quantify things not only with integers but with values of some general type T. I would want to create a type class Quantified[A,T]

implicit conversion

  • since I create a new object, I can cache values there or compute a better representation; but should I avoid this, since it might happen several times and an explicit conversion would probably be invoked only once?

What I expect from an answer

Present one (or more) use case(s) where the difference between both concepts matters and explain why I would prefer one over the other. Also explaining the essence of the two concepts and their relation to each other would be nice, even without example.

解决方案

While I don't want to duplicate my material from Scala In Depth, I think it's worth noting that type classes/ type traits are infinitely more flexible.

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

Has the ability to search its local environment for a default typeclass. However, I can override default behavior at any time by one of two ways:

  1. Creating/importing an implicit type class instance in Scope to short-circuit implicit lookup
  2. Directly passing a type class

Here's an example:

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
}

This makes type classes infinitely more flexible. Another thing is that type classes/traits support implicit lookup better.

In your first example, if you use an implicit view, the compiler will do an implicit lookup for:

Function1[Int, ?]

Which will look at Function1's companion object and the Int companion object.

Notice that Quantifiable is nowhere in the implicit lookup. This means you have to place the implicit view in a package object or import it into scope. It's more work to remember what's going on.

On the other hand, a type class is explicit. You see what it's looking for in the method signature. You also have an implicit lookup of

Quantifiable[Int]

which will look in Quantifiable's companion object and Int's companion object. Meaning that you can provide defaults and new types (like a MyString class) can provide a default in his companion object and it will be implicitly searched.

In general, I use type classes. They are infinitely more flexible for the initial example. The only place I use implicit conversions is when easing a API layer between a Scala wrapper and a Java library, and even this can be 'dangerous' if you're not careful.

这篇关于隐式转换vs.类型类的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆