Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Implicit class for subtypes of a generic class

Tags:

scala

implicit

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?

like image 285
Adracus Avatar asked Aug 25 '16 15:08

Adracus


2 Answers

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
like image 153
Michael Zajac Avatar answered Sep 30 '22 19:09

Michael Zajac


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.

like image 23
Alvaro Carrasco Avatar answered Sep 30 '22 19:09

Alvaro Carrasco