vkubicki
vkubicki

Reputation: 1124

Implicit conversion not performed on Int

In Scala, I want to generate some aliases for basic types, and then implement conversions through a type class. This is both useful for me, and an opportunity to understand type classes. The code is the following:

type Index = Int
val Index = Int

type Integer = Int
val Integer = Int

type Real = Double
val Real = Double // to have companion object of Double also be the companion object of Real

trait Convertible[A] {
  def toIndex(a: A): Index
  def toInteger(a: A): Integer
  def toReal(a: A): Real
}

implicit val ConvertibleIndex: Convertible[Index] = new Convertible[Index] {
  def toIndex(i: Index) = i
  def toInteger(i: Index) = i
  def toReal(i: Index) = i.toDouble
}

implicit val ConvertibleInteger: Convertible[Integer] = new Convertible[Integer] {
  def toIndex(i: Integer) = i
  def toInteger(i: Integer) = i
  def toReal(i: Integer) = i.toDouble
}

implicit val ConvertibleReal: Convertible[Real] = new Convertible[Real] {
  def toIndex(r: Real) = r.toInt
  def toInteger(r: Real) = r.toInt
  def toReal(r: Real) = r
}

implicit val ConvertibleString: Convertible[String] = new Convertible[String] {
  def toIndex(s: String) = s.toInt
  def toInteger(s: String) = s.toInt
  def toReal(s: String) = s.toDouble
}

implicit class ConvertibleSyntax[A](a: A)(implicit val c: Convertible[A]) {
  def toIndex = c.toIndex(a)
  def toInteger = c.toInteger(a)
  def toReal = c.toReal(a)
}

Consider now:

val a = 3.toReal
val b = 3.0.toReal
val c = "3".toReal

The statement for a does not compile, with the compilation error: method toReal is not a member of Int. But, for the b and c statements, the implicit conversion to ConvertibleSyntax is properly done.

Why is the implicit conversion not working on Int, but is working on Double and String ?

Upvotes: 1

Views: 206

Answers (2)

Mike Allen
Mike Allen

Reputation: 8249

I think you might be a little confused about how Scala does implicit conversions. (A common mistake, as implicit is a little overused.)

I think that what you want, first of all, is an implicit conversion function - or even an implicit class. Here's how you could do this using the latter:

Note: Int, Index and Integer are treated identically, so are Real and Double, confusing matters somewhat, so I've pared this down to something that will work. Also, Convertible does not need to be generic as its conversion functions need no arguments. Finally, you shouldn't have both type and val declarations for your types.

type Index = Int

type Integer = Int

type Real = Double

trait Convertible {
  def toIndex: Index
  def toInteger: Integer
  def toReal: Real
}

// Implicit classes cannot be defined in top-level scope, so they belong to an object.
object Implicits {

   implicit class ConvertibleInt(i: Int)
   extends Convertible {
     override def toIndex = i
     override def toInteger = i
     override def toReal = i.toDouble
   }

   implicit class ConvertibleDouble(d: Double)
   extends Convertible {
     override def toIndex = d.toInt
     override def toInteger = d.toInt
     override def toReal = d
  }

   implicit class ConvertibleString(s: String)
   extends Convertible {
     override def toIndex = s.toInt
     override def toInteger = s.toInt
     override def toReal = s.toDouble
  }
}

Now try this:

import Implicits._
val a = 3.toReal
val b = 3.0.toReal
val c = "3".toReal

What's happening here? Well, the implicit class declarations define classes that decorate the sole constructor argument with additional functions. If the compiler sees that you're trying to call a method on a type that doesn't have that method, it will look to see if there's an implicit conversion, in scope, to a type that does. If so, it is used and the function is called; if not, you get a compiler error. (The import statement is used to bring the classes into your current scope.)

So, for example, when the compiler sees "3".toReal it firstly determines that "3" is a String. Since this type doesn't have a .toReal member, it tries to find a conversion from a String to a type that does have such a member. It finds the ConvertibleString implicit class that takes a String argument and provides a .toReal method. Yay! So the compiler creates an instance of this class by passing "3" to ConvertibleString's constructor, then calls .toReal on the result.

On the other hand, when implicit is used with a value, it tells the compiler that the value is a default for any matching implicit arguments of the same type that are not provided. NEVER USE implicit WITH A PRIMITIVE OR COMMON LIBRARY TYPE!

For example:

final case class Example(i: Int)

// Default.
implicit val nameCanBeAnythingAtAll = Example(5)

// Function with implicit argument.
def someFunc(implicit x: Example): Unit = println(s"Value is $x")

Now, if you write something like this:

someFunc

the output will be Value is Example(5).

implicit values and arguments are an advanced topic, and I wouldn't worry about how they're used right now.

Upvotes: 1

Evgeny
Evgeny

Reputation: 1770

Because you define ambiguous implicits for Index and Integer (both Int).

Which one should be chosen by compiler?

Upvotes: 4

Related Questions