2016年9月13日星期二

More about Scala Implicit

Today I'm reviewing my reading notes about scala implicit and experiment on some sample project to furthur understand the mechnism.

The second rule of implicit states that the implicit function applicable should be a single identifier or some one defined in the "Source" or "Target" companion object.

So from the statements, we can see that if the implicit function exists in the "Source" or "Target" companion object, it's not necessary to be a single identifier. Below codes demonstrated it:

Source.scala

package sean.implicits

class Source (val name : String)

Target.scala

package sean.implicits

class Target (val s : Source) {
   def sourceName = s.name
}

object Target {
  implicit def source2Target(s : Source) = {
    new Target(s)
  }
}

Test.scala

package sean.implicits

object Test extends App{

  def p (t : Target) = {
    println(t.sourceName)
  }

  p(new Source("Test"))

}

Running Test.scala will print "Test" in the console. Here the implicit function is defined in the companion object of class Target which is the expected parameter type of function p.

However I found that "Source" and "Target" is with respect to the situation when "a specific type is expected". For the situation when a non-existed method is invoked, compiler doesn't expect any specific target type, instead , it's just looking for a type that has the corresponding method. So in that case, there is only "Source" class but "Target" class. e.g. :

Source.scala

package sean.implicits

class Source (val name : String)

Target.scala

package sean.implicits

class Target (val s : Source) {
   def sourceName = s.name
}

object Target {
  implicit def source2Target(s : Source) = {
    new Target(s)
  }
}

Test.scala

package sean.implicits

object Test extends App{

  println(new Source("Test").sourceName) //Compilation Error!

}

To compile the Test.scala above, you will get compilation error because here compiler is looking for a type that has the method "sourceName" , it's not necessary to be class "Target" , so the object Target won't be searched for implicit functions. After moving the implicit function source2Target from object Target to object Source, you are able to compile the Test.scala and running it will print "Test". So here the method receiver is new Source("Test"), the class Source is the "Source" class, its companion object will be searched for implicit functions.

One more thing come to my mind is that if we have a method call on the receiver while the method name is correct while the argument type is incorrect. Will the compiler first try to convert the argument to correct type or it will try to convert the method receiver to a type with the matching method signaure? Below example illustrates how it works :

Source.scala

package sean.implicits

class Source (val name : String) {
  def show(t : Target) = println(name)
}

object Source{
  implicit def source2Target(s : Source) = {
    new Target(s)
  }
}

Target.scala

package sean.implicits

class Target (val s : Source) {
   def sourceName = s.name
   def show(t : String) = println(t)
}

object Target{
  implicit def string2Target(s : String) = new Target(new Source(s))
}

Test.scala

package sean.implicits

object Test extends App{
  new Source("Source") show "Target"
}

Here class  Source has a method show whose parameter type is Target and it will print the name of the method receiver. while class Target also has a method show but its parameter type is String and it will print the passed string. When invoking show on object of type Source and passing it a String as argument, compiler will first try to look for an implicit function to convert the argument to the correct type, thus implicit function string2Target was applied. Running Test.scala will print "Source". However if we remove the implicit function string2Target above, the implicit function source2Target will be applied. Running the Test.scala will print "Target".

Also, I was thinking about why case class can't be implicit. And in the Scala documentation below :

http://docs.scala-lang.org/overviews/core/implicit-classes.html

It's even required that "There may not be any method, member or object in scope with the same name as the implicit class." However such statement is not precise and the example codes list there is not true. Below codes :

object Test extends App{
  def im = println("ll")
  implicit class im(i:Int)
  5 : im
}

or below codes :

object Test extends App{
  val im = 3
  implicit class im(i:Int)
  5 : im
}

and below codes:

object Test extends App{
  obj im
  implicit class im(i:Int)
  5 : im
}

can work correctly.

The reason why case class can't be implicit was found in below post:

https://issues.scala-lang.org/browse/SI-6227

for example if you have below codes:

implicit case class im(i:Int)

Then the scala compiler will generate an implicit constructor im : (Int) => im and an implicit companion object im with method apply : (Int) => im. So the object im itself is an implicit function. So in the same scope, we have two implicit function that can convert Int to im which is ambiguous.

So below codes :

object Test extends App{
  def im(i :Int) = i //actually the return type doesn't matter , it's not part of the method signature
  implicit class im(i:Int)
  5 : im
}

and below codes :

object Test extends App{
  object im {
    def apply(i:Int) = new im(i)
  }
  implicit class im(i:Int)
  5 : im
}

won't compile. The first piece of codes have two functions with same signature im : (Int) defined in the same scope. The compiler will complain :

method im is defined twice

And the second piece of codes have one implicit constructor im : (Int) => im and one object im with method apply : (Int) => im. The compiler will complain :

ambiguous reference to overloaded definition,
both method im in object Test of type (i: Int)sean.implicits.Test.im
and  object im in object Test of type sean.implicits.Test.im.type
match argument types (Int) and expected result type sean.implicits.Test.im

What I don't quite understand is that here object im is not implicit, why it will be looked up for implicit conversion?

没有评论:

发表评论