Search code examples
scalaimplicittype-erasure

Problems in using `DummyImplicit` (Scala)


Consider the following program:

  class myClass {
    def property[T](name: Symbol)(f: T => Boolean): Unit = {
      // do something
    }

    def property1[T](name: Symbol)(f: T => List[Int]): Unit = {
      // do something
    }
  }

  object spamDataModel extends myClass {
    property[Int]('myBoolean) {
      x:Int=> true
    }
  }

Suppose I want to change the name of the method property1 to property whole keeping both of them. Due to type erasure this is not possible, unless I do some tricks.

Wish suggestions from here I tried adding DummyImplicits:

class myClass {
    def property[T](name: Symbol)(f: T => Boolean)(): Unit = {
      // do something
    }

    def property[T](name: Symbol)(f: T => List[Int])(implicit d: DummyImplicit): Unit = {
      // do something
    }
  }

  object spamDataModel extends myClass {
    property[Int]('myBoolean) {
      x:Int=> true
    }
  }

But the moment that I add DummyImplcits, it starts to have errors (the second program):

[error] /Users/i-danielk/ideaProjects/saul/saul-core/src/main/scala/edu/illinois/cs/cogcomp/saul/datamodel/DataModel.scala:261: ambiguous reference to overloaded definition,
[error] both method property in class myClass of type (name: Symbol)(f: Int => List[Int])Unit
[error] and  method property in class myClass of type (name: Symbol)(f: Int => Boolean)(implicit d1: DummyImplicit)Unit
[error] match argument types (Symbol)
[error]     property[Int]('myBoolean) {
[error]             ^
[warn] two warnings found
[error] one error found

Solution

  • By using a DummyImplicit you have only solved the erasure problem, but now you have another one: both overloads have the exact same (first) parameter list. Inference works one parameter list a t a time, so if two overloads have the same first parameter list there is no way for the compiler to decide which one you meant to call.

    One way to solve this would be to use the Magnet Pattern.

    But you can also solve it in a somewhat simpler way, by having a single property method (like you'd do with the magnet pattern) and simply have it return a class that itself has two apply methods:

    class myClass {
      class PropertyApply[T] private[myClass] (name: Symbol) {
        def apply(f: T => Boolean): Unit = {
          // do something
          println("Called property with f: T => Boolean")
        }
        def apply(f: T => List[Int])(implicit d: DummyImplicit): Unit = {
          // do something
          println("Called property with f: T => List[Int]")
        }
      }
      def property[T](name: Symbol) = new PropertyApply[T](name)
    }
    
    object spamDataModel extends myClass {
      property[Int]('myBoolean) {
        x:Int => true
      }
    }
    

    Let's see:

    scala> spamDataModel
    Called property with f: T => Boolean
    res4: spamDataModel.type = spamDataModel$@20880a03
    

    The right overload was called, success.