0

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).

Chris Bedford
  • 2,560
  • 3
  • 28
  • 60

3 Answers3

5

=:= 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)
Peter Neyens
  • 9,770
  • 27
  • 33
  • 1
    This not only answers the question, it does it with clearer code that is more maintainable. That's a plus :) – Sascha Kolberg Aug 30 '15 at 11:02
  • Thanks, Peter. Since your answer also provides a good succinct example of type classes, I added the typeclasses tag to my original question. – Chris Bedford Aug 30 '15 at 21:09
3

A =:= B is not evidence that A can be converted to B. It is evidence that A can be cast to B. And you have no implicit evidence anywhere that Int can be cast to FooInt vice versa (for good reason ;).

What you are looking for is:

def swap(implicit ev: T => S, ev2: S => T) {
Sascha Kolberg
  • 7,092
  • 1
  • 31
  • 37
0

After working through this excercise I think I have a better understanding of WHY you'd want to use implicit evidence serves in your API.

Implicit evidence can be very useful when:

  • you have a type parameterized class that provides various methods that act on the types given by the parameters, and
  • when one or more of those methods only make sense when additional constraints are placed on parameterized types.

So, in the case of the simple API given in my original question:

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

We have a type Pair, which keeps two things together, and we can always call dump() to examine the two things. We can also, under certain conditions, swap the positions of the first and second items in the pair. And those conditions are given by the implicit evidence constraints.


The Programming in Scala book gives a nice example of how this technique is used in Scala collections, specifically on the toMap method of Traversables.

The book points out that Map's constructor

wants key-value pairs, i.e., two-tuples, as arguments. If we have a sequence [Traversable] of pairs, wouldn’t it be nice to create a Map out of them in one step? That’s what toMap does, but we have a dilemma. We can’t allow the user to call toMap if the sequence is not a sequence of pairs.

So there's an example of a type [Traversable] that has a method [toMap] that can't be used in all situations... It can only be used when the compiler can 'prove' (via implicit evidence) that the items in the Traversable are pairs.

Chris Bedford
  • 2,560
  • 3
  • 28
  • 60