Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Implicit conversions and null

Following code

import scala.language.implicitConversions

object myObj {
  implicit def nullToInt(x: Null) = 0

  def main(args: Array[String]): Unit = {
    val x = 1 + null
    val y = 1 + nullToInt(null)

    println(x + " " + y)
  }
}

gives below result

1null 1

I was expecting both vals to be Int and equal to 1.

Apparently first val is String and equals "1null".

Xprint:typer shows that source code is translated into

package <empty> {
  import scala.language.implicitConversions;
  object myObj extends scala.AnyRef {
    def <init>(): myObj.type = {
      myObj.super.<init>();
      ()
    };
    implicit def nullToInt(x: Null): Int = 0;
    def main(args: Array[String]): Unit = {
      val x: String = 1.+(null);
      val y: Int = 1.+(myObj.this.nullToInt(null));
      scala.Predef.println(x.+(" ").+(y))
    }
  }
}

There are no symbolic methods for int which accept null

scala> 10+null
res0: String = 10null

scala> 10*null
<console>:12: error: overloaded method value * with alternatives:
  (x: Double)Double <and>
  (x: Float)Float <and>
  (x: Long)Long <and>
  (x: Int)Int <and>
  (x: Char)Int <and>
  (x: Short)Int <and>
  (x: Byte)Int
 cannot be applied to (Null)
       10*null
         ^

scala> 10-null
<console>:12: error: overloaded method value - with alternatives:
  (x: Double)Double <and>
  (x: Float)Float <and>
  (x: Long)Long <and>
  (x: Int)Int <and>
  (x: Char)Int <and>
  (x: Short)Int <and>
  (x: Byte)Int
 cannot be applied to (Null)
       10-null
         ^

I assume both "1" and "null" were converted into String instead of applying implicit nullToInt. Can someone explain how compiler came up with that? What logic/workflow was used?

And another question is whether there is a way to enable implcit nullToInt?

PS. I'm not talking about best practices here. Feel free to consider a question as a matter of academic interest.

like image 293
Dr Y Wit Avatar asked Sep 28 '18 16:09

Dr Y Wit


People also ask

What is a implicit conversion?

An implicit conversion sequence is the sequence of conversions required to convert an argument in a function call to the type of the corresponding parameter in a function declaration. The compiler tries to determine an implicit conversion sequence for each argument.

What is the example of implicit conversion?

Implicit conversions: No special syntax is required because the conversion always succeeds and no data will be lost. Examples include conversions from smaller to larger integral types, and conversions from derived classes to base classes.

Why is implicit type conversion bad?

Implicit conversions allow the compiler to treat values of a type as values of another type. There's at least one set of scenarios in which this is unambiguously bad: non-total conversions. That is, converting an A to a B when there exists A s for which this conversion is impossible.


2 Answers

I'll try to answer my own question.

The subject is a bit misleading and in fact no implicit conversions are applied to the expression for val x at all. Null is a subtype of String and Int has method abstract def +(x: String): String so it can be applied to Null as well.

This is also confirmed by the output of Xprint:typer because it's supposed to show all implicit conversions and apparently it does not show anything for expression for x.

And answering questions "whether there is a way to enable implcit nullToInt", the only way to enable it is to specify explicitly in this case, because compiler will not consider using any implicits when code is successfully compiled without them.

like image 116
Dr Y Wit Avatar answered Sep 20 '22 23:09

Dr Y Wit


So, what @AndreyTyukin says is right, mechanically I think there is more to it. There are two things that are going on as to why.

  1. Any is decorated with an implicit in the Predef, see the following:

    implicit final class any2stringadd[A] extends AnyVal

As you can see, any2stringadd is what is responsible for the + and you can see the signature here:

def +(other: String): String

Correction: There are no implicit conversions, it was even simpler than that

  1. Looking inside the source code of Predef and any2stringadd which is indeed at play is the following

implicit final class any2stringadd[A](private val self: A) extends AnyVal { def +(other: String): String = String.valueOf(self) + other }

String.valueOf of 1 will return a String of 1. In Java (and verify with a jshell), a String of 1 added to null will become 1null.

jshell> "1" + null
$1 ==> "1null"
like image 41
Daniel Hinojosa Avatar answered Sep 21 '22 23:09

Daniel Hinojosa