Mark Pierotti
Mark Pierotti

Reputation: 151

Implicit class vs Implicit conversion to trait

I'm trying to add new functions to existing types (so I can have the IDE auto suggest relevant functions for types I don't have control over, eg Future[Option[A]]). I've explored both implicit classes and implicit conversions to accomplish this and they both seem to offer the same behavior.

Is there any effective difference between using an implicit class:

case class Foo(a: Int)
implicit class EnrichedFoo(foo: Foo) {
  def beep = "boop"
}
Foo(1).beep // "boop"

And using an implicit conversion:

case class Foo(a: Int)
trait Enriched {
  def beep: String
}
implicit def fooToEnriched(foo: Foo) = new Enriched {
  def beep = "boop"
}
Foo(1).beep // "boop"

I suppose one difference here might be that the first example creates a one-off class instead of a trait, but I could easily adapt the implicit class to extend an abstract trait, eg:

case class Foo(a: Int)
trait Enriched {
  def beep: String
}
implicit class EnrichedFoo(foo: Foo) extends Enriched {
  def beep = "boop"
}
Foo(1).beep // "boop"

Upvotes: 10

Views: 3268

Answers (3)

AyJayKay
AyJayKay

Reputation: 103

To add on Luka Jacobowitz answer: Implicit classes are basically extensions. Implicit conversion is used to tell the compiler that it may be treated as something with extension.

Sounds nearly the same. Two things of interest from implicit conversion to have some difference:

First: You may need to activate the language feature for disabling warnings when using implicit conversion.

Second: The term of "converting" the type may be confusing:

Implicit conversions are applied in two situations: If an expression e is of type S, and S does not conform to the expression’s expected type T. [Or:] In a selection e.m with e of type S, if the selector m does not denote a member of S.

case class Foo(a: Int)
trait Enriched {
  def beep: String
}
implicit def fooToEnriched(foo: Foo) = new Enriched {
  def beep = "boop"
}

Foo(1) match {
  case _:Enriched => println("is an Enriched")
  case _:Foo => println("no, was a Foo")
}
// no, was a Foo

but it may be treated as an Enriched...

val enriched: Enriched = Foo(2)
enriched match {
  case _:Enriched => println("is an Enriched")
  case _:Foo => println("no, was a Foo")
}
// is an Enriched
// plus unreachable code warning: case _:Foo => println("no, was a Foo")

Upvotes: 0

Luka Jacobowitz
Luka Jacobowitz

Reputation: 23502

As far as I'd know, they're pretty much exactly the same. The scoping rules also equally apply to both.

In my opinion, I'd use the implicit classes for your kind of situation. They were probably created exactly for something like that.

Implicit conversions, to me, are more appropriate when you already actually have two different kind of classes and want to convert between the two.

You can check out the initial proposal for implicit classes right here. There it says:

A new language construct is proposed to simplify the creation of classes which provide extension methods to another type.

You can even see how it desugars implicit classes. The following:

implicit class RichInt(n: Int) extends Ordered[Int] {
   def min(m: Int): Int = if (n <= m) n else m
   ...
}

will desugar into:

class RichInt(n: Int) extends Ordered[Int] {
  def min(m: Int): Int = if (n <= m) n else m
  ...
}
implicit final def RichInt(n: Int): RichInt = new RichInt(n)

Upvotes: 6

Som Bhattacharyya
Som Bhattacharyya

Reputation: 4112

Well to me its a matter of preference. Actually the implicit classes came into being to ease the creation of classes which provide extension methods to another type. Implicit classes add a lot of value to value classes though.

Upvotes: 1

Related Questions