Adracus
Adracus

Reputation: 1009

Implicit class for subtypes of a generic class

I want to enhance all Iterables with some custom code. For this I wrote the following:

implicit class RichIterable[A, B <: Iterable[A]](b: B) {
  def nonEmptyOpt: Option[B] = if (b.nonEmpty) Some(b) else None
}

Now, when I want to use this method on a List that definitely is a subclass of Iterable like so

List(1, 2, 3).nonEmptyOpt

I get

value nonEmptyOpt is not a member of List[Int]

How can I resolve this?

Upvotes: 6

Views: 1298

Answers (3)

pedrofurla
pedrofurla

Reputation: 12783

A simpler solution would be:

implicit class RichIterable[A](b: Iterable[A]) {
  def nonEmptyOpt: Option[Iterable[A]] = if (b.nonEmpty) Some(b) else None
}

scala> List(1,2,3).nonEmptyOpt
res0: Option[Iterable[Int]] = Some(List(1, 2, 3))

Upvotes: 0

Michael Zajac
Michael Zajac

Reputation: 55569

Given a parameter with only type B <: Iterable[A], the compiler doesn't know how to easily figure out what A is, because it is not necessarily easily computed from B (needing to search for least upper-bounds).

Instead you can do this by redefining the type constraints, without using tricks. Essentially, B should really be a type constructor that is bounded above by Iterable. Then, your implicit class is a conversion from some B[A] to your enriched class. Having a parameter of B[A] helps the compiler compute A, because it expects it to be the argument of the type constructor B.

implicit class RichIterable[A, B[X] <: Iterable[X]](b: B[A]) {
  def nonEmptyOpt: Option[B[A]] = if (b.nonEmpty) Some(b) else None
}

scala> List(1, 2, 3).nonEmptyOpt
res0: Option[List[Int]] = Some(List(1, 2, 3))

scala> List.empty[Int].nonEmptyOpt
res1: Option[List[Int]] = None

Upvotes: 9

Alvaro Carrasco
Alvaro Carrasco

Reputation: 6172

Little trick I stumbled upon once:

scala> implicit class RichIterable[A, B <: Iterable[A]](b: B with Iterable[A]) {
 |   def nonEmptyOpt: Option[B] = if (b.nonEmpty) Some(b) else None
 | }
defined class RichIterable

scala> List(1,2,3).nonEmptyOpt
res3: Option[List[Int]] = Some(List(1, 2, 3))

Note the B with Iterable[A] on the parameter.

By the way, when debugging implicits, it helps sometimes to try to apply them explicitly (before change):

scala> new RichIterable(List(1,2,3)).nonEmptyOpt
<console>:15: error: inferred type arguments [Nothing,List[Int]] do not conform to class RichIterable's type parameter bounds [A,B <: Iterable[A]]
          new RichIterable(List(1,2,3)).nonEmptyOpt

So, the compiler is having a hard time figuring out the type of A. The type refinement apparently helps it along.

Upvotes: 7

Related Questions