隐式转换vs.类型类 [英] Implicit conversion vs. type class
问题描述
在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>
具有在其本地环境中搜索默认类型类的能力。但是,我可以通过以下两种方法之一覆盖默认行为:
- 在Scope中创建/导入一个隐式类型实例-circuit implicit lookup
- 直接传递类型类
这里有一个例子:
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 toquantify
.Type classes
An alternative is to define a "witness"
Quantified[A]
that states, that some typeA
can be quantified.trait Quantified[A] { def quantify(a: A): Int }
We then provide instances of this type class for
String
andList
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 classQuantified[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:
- Creating/importing an implicit type class instance in Scope to short-circuit implicit lookup
- 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屋!