Majid Azimi
Majid Azimi

Reputation: 5745

Conflicting method when paramter is limited to AnyRef and AnyVal in Scala

Scala compiler detects the following two map functions as duplicates conflicting with each other:

class ADT {
    def map[Output <: AnyVal](f: Int => Output): List[Output] = ???
    def map[Output >: Null <: AnyRef](f: Int => Output): List[Output] = ???
}

The class type of Output parameter is different. First one limits to AnyVal and second one limits to AnyRef. How can I differentiate them?

Upvotes: 1

Views: 144

Answers (3)

Dima
Dima

Reputation: 40510

The problem is not differentiating AnyVal from AnyRef so much as getting around the fact that both method signatures become the same after erasure.

Here is a neat trick to get around this kind of problem. It is similar to what @som-snytt did, but a bit more generic, as it works for other similar situations as well (e.g. def foo(f: Int => String): String = ??? ; def foo(f: String => Int): Int = ??? etc.):

class ADT {
  def map[Output <: AnyVal](f: Int => Output): List[Output] = ???
  def map[Output >: Null <: AnyRef](f: Int => Output)(implicit dummy: DummyImplicit): List[Output] = ???
}

The cutest thing is that this works "out of the box". Apparently, a DummyImplicit is a part of standard library, and you always have the thing in scope. You can have more than two overloads this way too by just adding more dummies to the list.

Upvotes: 4

mdm
mdm

Reputation: 3988

You could use a typeclass for that map method.

Using your exact example:

trait MyTC[Output]{
  def map(f: Int => Output): List[Output]
}

object MyTC{
  def apply[A](a : A)(implicit ev : MyTC[A]) : MyTC[A] = ev 

  implicit def anyRefMyTc[A <: AnyRef] : MyTC[A]  = new MyTC[A]{
    def map(f: Int => A): List[A] = { println("inside sub-AnyRef"); List.empty }
  }
  implicit def anyValMyTc[A <: AnyVal] : MyTC[A] = new MyTC[A]{
    def map(f: Int => A): List[A] = { println("inside sub-AnyVal"); List.empty }
  }
}

import MyTC._

val r1 = Option("Test1")
val r2 = List(5)
val v1 = true
val v2 = 6L

// The functions here are just to prove the point, and don't do anything.
MyTC(r1).map(_ => None)
MyTC(r2).map(_ => List.empty)
MyTC(v1).map(_ => false)
MyTC(v2).map(_ => 10L)

That would print:

inside sub-AnyRef
inside sub-AnyRef
inside sub-AnyVal
inside sub-AnyVal

The advantage of this approach is that, should you then choose to specialise the behaviour further for just some specific type (e.g. say you want to do something specific for Option[String]), you can do that easily:

// This is added to MyTC object 
implicit val optMyTc : MyTC[Option[String]] = new MyTC[Option[String]]{
    def map(f: Int => Option[String]): List[Option[String]] = { println("inside Option[String]"); List.empty }
  }

Then, re-running the code will print:

inside Option[String]
inside sub-AnyRef
inside sub-AnyVal
inside sub-AnyVal

Upvotes: 2

som-snytt
som-snytt

Reputation: 39587

scala 2.13.0-M5> :pa
// Entering paste mode (ctrl-D to finish)

object X {
    def map[Output <: AnyVal](f: Int => Output) = 1
    def map[O](f: Int => O)(implicit ev: O <:< AnyRef) = 2
}

// Exiting paste mode, now interpreting.

defined object X

scala 2.13.0-M5> X.map((x: Int) => x*2)
res0: Int = 1

scala 2.13.0-M5> X.map((x: Int) => "")
res1: Int = 2

Upvotes: 2

Related Questions