Search code examples
scalaimplicit-conversiontypeclassimplicits

unclear why my in-scope implicit conversions are not accepted as 'implicit evidence'


I've been experimenting with implicit conversions, and I have a decent understanding of the 'enrich-my-libray' pattern that uses these. I tried to combine my understanding of basic implicits with the use of implicit evidence... But I'm misunderstanding something crucial, as shown by the method below:

import scala.language.implicitConversions

object Moo extends App {

  case class FooInt(i: Int)
  implicit def cvtInt(i: Int) : FooInt = FooInt(i)
  implicit def cvtFoo(f: FooInt) : Int = f.i

  class Pair[T, S](var first: T, var second: S) {
    def swap(implicit ev: T =:= S, ev2: S =:= T) {
      val temp = first
      first = second
      second = temp
    }

    def dump() = {
      println("first is " + first)
      println("second is " + second)
    }
  }

  val x  = new Pair(FooInt(200), 100)
  x.dump
  x.swap
  x.dump
}

When I run the above method I get this error:

    Error:(31, 5) Cannot prove that nodescala.Moo.FooInt =:= Int.
      x.swap
        ^

I am puzzled because I would have thought that my in-scope implict conversion would be sufficient 'evidence' that Int's can be converted to FooInt's and vice versa. Thanks in advance for setting me straight on this !

UPDATE:

After being unconfused by Peter's excellent answer, below, the light bulb went on for me one good reason you would want to use implicit evidence in your API. I detail that in my own answer to this question (also below).


Solution

  • =:= checks if the two types are equal and FooInt and Int are definitely not equal, although there exist implicit conversion for values of these two types.

    I would create a CanConvert type class which can convert an A into a B :

    trait CanConvert[A, B] {
      def convert(a: A): B
    }
    

    We can create type class instances to transform Int into FooInt and vise versa :

    implicit val Int2FooInt = new CanConvert[Int, FooInt] {
      def convert(i: Int) = FooInt(i)
    }
    
    implicit val FooInt2Int = new CanConvert[FooInt, Int] {
      def convert(f: FooInt) = f.i
    }
    

    Now we can use CanConvert in our Pair.swap function :

    class Pair[A, B](var a: A, var b: B) {
      def swap(implicit a2b: CanConvert[A, B], b2a: CanConvert[B, A]) {
        val temp = a
        a = b2a.convert(b)
        b = a2b.convert(temp)
      }
    
      override def toString = s"($a, $b)"
    
      def dump(): Unit = println(this)
    }
    

    Which we can use as :

    scala> val x = new Pair(FooInt(200), 100)
    x: Pair[FooInt,Int] = (FooInt(200), 100)
    
    scala> x.swap
    
    scala> x.dump
    (FooInt(100), 200)