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?

Programming in Scala -- Chapter 21. Implicit Conversions and Parameters

1.  Implicit conversions are often helpful for working with two bodies of software that were developed without each other in mind. Each library has its own way to encode a concept that is essentially the same thing. Implicit conversions help by reducing the number of explicit conversions that are needed from one type to another.

2.  Implicit definitions are those that the compiler is allowed to insert into a program in order to fix any of its type errors. Implicit conversions are governed by the following general rules:
  • Marking rule: Only definitions marked implicit are available. You can use implicit to mark any variable, function, or object definition. Variables and singleton objects marked implicit can be used as implicit parameters.
  • Scope rule: An inserted implicit conversion must be in scope as a single identifier, or be associated with the source or target type of the conversion. If you want to make someVariable.convert available as an implicit, you would need to import it, which would make it available as a single identifier. The compiler will also look for implicit definitions in the companion object of the source or expected target types of the conversion. For example, if you're attempting to pass a Dollar object to a method that takes a Euro, the source type is Dollar and the target type is Euro. You could, therefore, package an implicit conversion from Dollar to Euro in the companion object of either class, Dollar or Euro.
  • One-at-a-time rule: Only one implicit is inserted. The compiler will never rewrite x + y to convert1(convert2(x)) + y. However, it's possible to circumvent this restriction by having implicits take implicit parameters.
  • Explicits-first rule: Whenever code type checks as it is written, no implicits are attempted. You can always replace implicit identifiers by explicit ones, thus making the code longer but with less apparent ambiguity.

3.  There are three places implicits are used in the language: conversions to an expected type, conversions of the receiver of a method, and implicit parameters.

4.  Implicit conversions to an expected type let you use one type in a context where a different type is expected. The rule is simple. Whenever the compiler sees an X, but needs a Y, it will look for an implicit function that converts X to Y.

5.  The scala.Predef object, which is implicitly imported into every Scala program, defines implicit conversions that convert "smaller" numeric types to "larger" ones.

6.  Implicit conversions also apply to the receiver of a method call, the object on which the method is invoked. This kind of implicit conversion has two main uses. First, receiver conversions allow smoother integration of a new class into an existing class hierarchy. And second, they support writing domain-specific languages (DSLs) within the language.

7.  -> is a method of the class ArrowAssoc, a class defined inside the standard Scala preamble (scala.Predef). The preamble also defines an implicit conversion from Any to ArrowAssoc:

package scala

object Predef {

    class ArrowAssoc[A](x: A) {

        def -> [B](y: B): Tuple2[A, B] = Tuple2(x, y)

    }

    implicit def any2ArrowAssoc[A](x: A): ArrowAssoc[A] = new ArrowAssoc(x)

    ...

}

8.  An implicit class is a class that is preceded by the implicit keyword. For any such class, the compiler generates an implicit conversion from the class's constructor parameter to the class itself. An implicit class cannot be a case class, and its constructor must have exactly one parameter. Also, an implicit class must be located within some other object, class, or trait.

9.  The compiler will sometimes replace someCall(a) with someCall(a)(b), or new SomeClass(a) with new SomeClass(a)(b), thereby adding a missing parameter list to complete a function call. It is the entire last curried parameter list that's supplied, not just the last parameter. Every argument you provide as the default values of the parameters should be marked implicit and the whole last parameter list should be marked as implicit. Note that the implicit keyword applies to an entire parameter list. The compiler selects implicit parameters by matching types of parameters against types of values in scope, implicit parameters usually have "rare" or "special" enough types that accidental matches are unlikely.

10.  The maxListOrdering function below is an example of an implicit parameter used to provide more information about a type mentioned explicitly in an earlier parameter list. To be specific, the implicit parameter ordering, of type Ordering[T], provides more information about type T—in this case, how to order Ts. Type T is mentioned in List[T], the type of parameter elements, which appears in the earlier parameter list. Because elements must always be provided explicitly in any invocation of maxListOrdering, the compiler will know T at compile time and can therefore determine whether an implicit definition of type Ordering[T] is available. If so, it can pass in the second parameter list, ordering, implicitly:

def maxListOrdering[T](elements: List[T])(implicit ordering: Ordering[T]): T

This pattern is so common that the standard Scala library provides implicit "ordering" methods for many common types.

11.  When you use implicit on a parameter, not only will the compiler try to supply that parameter with an implicit value, but the compiler will also use that parameter as an available implicit in the body of the method.

12.  The following method is defined in the standard library:

def implicitly[T](implicit t: T) = t

The effect of calling implicitly[Foo] is that the compiler will look for an implicit definition of type Foo. It will then call the implicitly method with that object, which in turn returns the object right back. Thus you can write implicitly[Foo] whenever you want to find an implicit object of type Foo in the current scope.

13.   Instead of below function definition:

def maxList[T](elements: List[T])(implicit ordering: Ordering[T]) : T

Scala lets you leave out the name of this parameter and shorten the method header by using a context bound. Using a context bound, you would write the signature of maxList as below :

def maxList[T : Ordering](elements: List[T]): T

The syntax [T : Ordering] is a context bound, and it does two things. First, it introduces a type parameter T as normal. Second, it adds an implicit parameter of type Ordering[T]. When using a context bound you don't know what the implicit parameter will be called. It's usually used to provide an implicit parameter for some function call within maxList.  A context bound is quite flexible. It allows you to use code that requires orderings—or any other property of a type—without having to change the definition of that type.

14.  If multiple implicit conversions are in scope and each would work, Scala refuses to insert a conversion in such a case. If one of the available conversions is strictly more specific than the others, then the compiler will choose the more specific one. To be more precise, one implicit conversion is more specific than another if one of the following applies:
  • The argument type of the former is a subtype of the latter's.
  • Both conversions are methods, and the enclosing class of the former extends the enclosing class of the latter.
Method overloading has the same relaxation. If one of the available foo methods takes a String while the other takes an Any, then the String version is chosen for foo(null) call.

15.  When you are debugging a program, it can sometimes help to see what implicit conversions the compiler is inserting. The -Xprint:typer option to the compiler is useful for this. If you run scalac with this option, the compiler will show you what your code looks like after all implicit conversions have been added by the type checker.