Type mismatch in implicit conversion

I would like to understand what is going on in the following Scala code:

object Noname {
  def main(args: Array[String]): Unit = {
    import scala.language.implicitConversions
    implicit def Int2Boolean(x: Int): Boolean = x + 1
    println(0: Boolean)
  }
}

The compiler doesn’t complain, but the program doesn’t terminate or print anything.
I would expect the Scala compiler to spot the mismatch between x + 1, which is an Int, and the return type Boolean. Is this related to type erasure perhaps?

The compiler sees that it has to return a Boolean but you returned an Int; however there is an implicit conversion from Int to Boolean in scope (yes the same Int2Boolean function you are defining).
So basically you have an infinite recursion over and over, which will never end.

So this is basically one of the reasons why implicit conversions are one of the worst features of Scala and why people will always tell you that they are discouraged.

Oh, I see.
Why doesn’t the compiler spot this circular dependency? Wouldn’t it be reasonable to require implicit conversions to form a DAG?

Not sure what you mean.

A implicit conversion is just any other method, as such, it can be recursive, call other methods and whatever (even though most people, will only / assume / recommend, just call the constructor of the other class), that is juts another problem with implicit conversions, they may be very costly and you won’t know where they are being called.

Now, as any other method, the compiler can not check if you have an infinite recursion or not. That is the halting problem.

I just realize that implicit conversions are not even “transitive”, right? For example, the following doesn’t compile:

class A
class B
class C

implicit def AtoB(a: A): B = new B
implicit def BtoC(b: B): C = new C

println(((new A): B): C)    // compiler is okay with that
println((new A): C)         // compiler says "no"

A implicit conversion is just any other method, as such, it can be recursive, call other methods and whatever

But without the implicit keyword, the method IntToBoolean would not compile, whereas the method def Int2Boolean_rec(x: Int): Boolean = Int2Boolean_rec(x + 1) does compile, even without the keyword implicit.

Is it correct to say that the compiler changes the original method body of IntToBoolean to the method body of Int2Boolean_rec, and then marks that function as implicit? Wouldn’t it be better if the compiler would not make any implicit conversion adjustments within the bodies of methods that will later be declare to be implicit conversion methods?

I just realize that implicit conversions are not even “transitive”, right?

Yeah, they aren’t; and for good! One is already really bad, allowing the compiler to apply an arbitrary number of implicit transformations would not only result in even harder to understand and debug code but in a slower and more complex compiler.

Is it correct to say that the compiler changes the original method body of IntToBoolean to the method body of Int2Boolean_rec

Yeah, basically when you say implicit def X2Y(x: X): Y = ??? then compiler simply register that transformation function so whenever it sees an X and it has to be a Y then it simply introduce a call to X2Y.
So yeah in your example what the compiler sees is that Int2Boolean has to return a Boolean but it returns an Int, but it sees that there is some implicit conversion from Int to Boolean so it uses that (the fact that it is using itself is irrelevant). So the code ends like:

implicit def Int2Boolean(x: Int): Boolean = Int2Boolean(x + 1)

One may argue that compiler may forbid recursive implicit conversions; however one may also argue that there may be valid use cases for that. For example, consider this contrived and horrendous example.

implicit def List2Int(l: List[Any]): Int = l match {
  case h :: t => h.hashCode + t
  case Nil    => 0
}

List(1, 2, 3) : Int
// val res: Int = 6

In conclusion,
Implicit conversions = bad
Do not use them, rather prefer an explicit transformation through an extension method.

implicit class IntOps(private val i: Int) extends AnyVal {
  def toBoolean: Boolean = x + 1 // Won't compile
  def toBoolean: Boolean = x != 0 // Will compile
}

0.toBoolean
// res: Boolean = false

This is worth underscoring: you’re poking at one of the most problematic aspects of the language, which is part of why you’re finding it iffy. It is being redesigned and de-emphasized in Scala 3, and some folks would like to remove it from the language entirely. So as @BalmungSan says, you should usually avoid it…

1 Like

Just to clarify: I wouldn’t say the compiler should forbid recursive implicit conversions. What I would argue is that the compiler should regard the bodies of methods that are declared to be implicit as “no-go areas” for implicit conversions. So your “contrived and horrendous” example should not compile as is, but if you replace + t by + List2Int(t), the compiler should be okay with it. Although it would still be contrived and horrendous ;).

Thanks for helping me, I think I understand those implicit conversions a lot better now.

Originally, I wanted to define an implicit conversion like this:

implicit def TtoAny2TtoUnit[T](f: T => Any): T => Unit = f

The compiler didn’t complain, but it didn’t work either. What does work, though, is the following:

implicit def TtoAny2TtoUnit[T](f: T => Any): T => Unit = x => { f(x); () }

I’m not sure if it’s a good idea to include it in Predef, though.

What’s the use case? This seems to be going from type-unsafe to even more type-unsafe…

Why do you need this? We already have value-discard.

For example this works:

def foo(x: Int): Unit = x + y

What I would argue is that the compiler should regard the bodies of methods that are declared to be implicit as “no-go areas” for implicit conversions.

That may be a valid point of debate, but again one may argue that why I have to be explicit about my implicit conversion? Also, what about this?

implicit def Any2Int(a: Any): Int = a.hashCode

implicit def List2Int(l: List[Any]): Int = l match {
  case h :: t => h + t
  case Nil    => 0
}

(well that doesn’t work because there is also Any2String in scope and causes an ambiguity but I guess you get my point; btw, the fact that this doesn’t work because there is already another implicit conversion in Predef serves as another example of why implicit conversions are bad)

The “use case”, if you want to call it that, would be, for example, that you could implement the method .foreach of class List[T] as def foreach(f: T => Unit): Unit, while at the moment it is defined via a type parameter on the method foreach. By the way, why is it not defined as def foreach(f: T => Any): Unit?

Right, you are using the implicit conversion implicit def Any2Unit(x: Any): Unit = (). Or, alternatively, it might be that the compiler appends () at the end of any method that is defined to have a return type of Unit.

The example List2Int should be refused by the compiler, I think, because the compiler should not apply any implicit conversion within the body of method List2Int.

The example List2Int should be refused by the compiler, I think, because the compiler should not apply any implicit conversion within the body of method List2Int.

Yeah, and as I said, you got a point there that may be debated by the contributors of the language.
But, my point is that there is a simple counter-argument, why I have to be explicit if I am using an implicit conversion inside another implicit conversion?
If you are willing to say that implicit conversions inside implicit conversions are dangerous how far are you to say that implicit conversions in general are dangerous?
So, my point is that I believe (note this is completely my personal opinion and subjective) that anyone defending implicit conversion would defend that use case.

Right, you are using the implicit conversion implicit def Any2Unit(x: Any): Unit = () . Or, alternatively, it might be that the compiler appends () at the end of any method that is defined to have a return type of Unit .

Yeah, AFAIK, there is no implicit conversion from Any to Unit, it is just that the compiler will add that () at the end of the body.

The “use case”, if you want to call it that, would be, for example, that you could implement the method .foreach of class List[T] as def foreach(f: T => Unit): Unit

Not sure if this is just curiosity of if you have a real problem with this.
The reason why foreach is not defined as foreach(f: T => Unit) but instead as foreach[U](f: T => U) is so you can do something like this:

var globalAcc: Int = 0

/** Adds an amount to the global accumulator and returns its current value. */
def addToAcc(amount: Int): Int = {
  globalAcc += amount
  globalAcc
}

val data: List[Int] = List(1, 2, 3)
data.foreach(addToAcc)

Now, one may say that it could be defined as foreach(f: T => Any) and due functions being co-variant on their output, then it should just work. But, I believe that can cause some frustrating errors due things like eta-expansion.

So, again, may I ask; is this just about curiosity or do you have a real problem that you are trying to solve? If so, I would like to help you with that meta-problem.

That sounds reasonable. I’m mostly playing devils advocat here ;).

I also don’t know for sure, but the fact that 0: Unit compiles seems to indicate that there is a general implicit conversion.

It is just out of curiosity. I want to understand the language more deeply, and from time to time I stumble upon things.

Okay.

Just about curiosity.

I will ask some more questions in the days and weeks to come. At the moment I am rereading Programming in Scala (Odersky, Spoon, Venners), and after that I think I will turn towards Scalaz. My background is in mathematics.

Not really, it is just expanded as this by the compiler { 0; () } remember a body is also a valid expression, whose return type is the return type of the last statement of the body.

Cool, good luck and feel free to ask more questions :slight_smile:
But, I may say that sometimes it is better to just say “implementation details” and forget about the how and focus on the what and why.

Note that, again that is just speculation, I really do not know too much about how inference works and why those workarounds are used.

Cool!

At the risk of starting a holly war, I would recommend you to use cats instead of scalaz, mainly because of the modular design and the fact that cats is more actively maintained on these days.
Also, Scala with cats is probably the best technical book I have read so far, and is free!

Actually, Intellij does help you out in such cases by showing it as an error.

Okay, I will do that. I like cats.

Interesting. I will try it. At the moment I am using nano as my IDE.

If you like minimalistic text editors I would recommend you to use metals so you get most of the functionality of an IDE on a simple editor.
I personally like VSCode as my editor.