Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
698 views
in Technique[技术] by (71.8m points)

scala - Implicit conversion vs. type class

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.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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 type class. 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 their 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 using an API layer between a Scala wrapper and a Java library, and even this can be 'dangerous' if you're not careful.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...