21. Implicits - RobertMakyla/scalaWiki GitHub Wiki

Implicit CONVERSION

implicit - domniemany explicit - wyraźny, sprecyzowany

case class Fraction(val a:Int , val b: Int){
   def *(that: Fraction) = Fraction(this.a * that.a, this.b * that.b)
}

implicit def int2Fraction(n: Int) = Fraction(n, 1)

3 * Fraction(4,5)  // 3 is transformed to Fraction(3,1) using int2Fraction

Using Implicits for Enriching Existing Libraries

new File("somePath/SomeFile").print

I need to enrich library:

class RichFile(val from: File) {
   def print { println( Source.fromFile(from.getPath).mkString) }
}

implicit def file2RichFile(from: File) = new RichFile(from)    // File --> RichFile

new File("z:/HOW_TO/hudson_connect.txt").print

3 Importing Implicits

    :implicits        // to see all implicits from Predef.
    :implicits -v     // to see all implicits from Predef and imported by us.

Rules for Implicit Conversions

Implicit conversions are done in 3 situations:

  1. If actual type of expression differs from an expected one

example: setFraction(3) // setFraction expects Fraction, not an Int

  1. If an objects accesses a non existing method

example: new File("z:/HOW_TO/hudson_connect.txt").print // there's no File.print() method

  1. If an object invokes a method whose parameters don't match the given arguments:

example: 3 * Fraction(1,2)

However:

  • no implicit conversion is done if code compiles without it

(if a * b compiles, the compiler won’t try a * convert(b) or convert(a) * b.)

  • The compiler will never attempt multiple conversions, such as convert1(convert2(a)) * b

  • Ambiguous conversions are an error. (if both 'convert1(a) * b' and 'convert2(a) * b' are valid, the compiler will report an error)

Example:

    implicit def int2Fraction(n: Int) = Fraction(n, 1)
    implicit def fraction2Double(f: Fraction) = f.a * 1.0 / f.b

    Fraction(3,4) * 5

Caution: it is not ambiguity that both are valid:

    Fraction(3, 4) * int2Fraction(5)
    fraction2Double(Fraction(3, 4)) * 5

Here, the first conversion is used since it does not require modification of the object which contains *() method !!!

Implicit Parameters

case class Delimiters(left: String, right: String)

def quote(what: String)(implicit delims: Delimiters) =
   delims.left + what + delims.right

I can still provide explicit parameter:

   quote("Bonjour")(Delimiters("'","'"))

Or I can import an implicit one:

object FrenchPunctuation {
   implicit val quoteDelimiters = Delimiters("<<", ">>")
   // ... other french stuff
}

import FrenchPunctuation.quoteDelimiters
quote("Bonjour")

Note: There can only be one implicit value for a given data type. Thus, it is not a good idea to use implicit parameters of common types.

For example:

def quote(what: String)(implicit left: String, right: String) // No!!! 
                        // String already has implicit converter.

Implicit CONVERSIONS with Implicit Parameters - Context Bound

def smaller[T](a: T, b: T) = if (a < b) a else b  // ERROR as Type T does not have < method

def smaller[T](a: T, b: T)(implicit ord: Ordering[T])
   = if (ord.compare(a,b) < 0) a else b

smaller("abc", "def")       // --> "abc"

I can put it in the class, making parameters 'a' and 'b' the fields of the class:

class Pair[T](val a: T, val b: T) {

   def smaller(implicit ord: Ordering[T]) = 
     if (ord.compare(a,b) < 0) a else b
}
Or even saver - with Context Bound
A context bound 'T : Ordering' requires that there is implicit value of Ordering[T]
        class Pair[T : Ordering](val a: T, val b: T) {

            def smaller(implicit ord: Ordering[T]) = if (ord.compare(a,b) < 0) a else b
        }
the same:
class Pair[T : Ordering](val a: T, val b: T) {
    def smaller = if (implicitly[Ordering[T]].compare(a,b) < 0) a else b
}

The 'implicitly' method is defined in Predef.scala :

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

The 'implicitly[Ordering[String]]' simply means that we want a reference to the object instance provided by Ordering[String]

Now let's say we want to create Pair[Point]. We need to provide implicit value of Ordering[Point]

case class Point(val value: Int)

object SomeOrderings{
    val loadOrdering = Ordering.by { p: Point => p.value }
}

implicit val ord = MyOrderings.loadOrdering

new Pair(Point(60), Point(4)) smaller          // --> Point(4)
or in more clear way:
case class Point(val value: Int)

implicit object PointOrdering extends Ordering[Point] {
     def compare(a: Point, b: Point) = implicitly[Ordering[Int]].compare(a.value, b.value)
}

new Pair(Point(60), Point(4)) smaller          // --> Point(4)

Evidence Object - We don't use it, it's just to prove the right relations between types

Some types relations from Predef:

    T =:= U   - types are equal
    T <:< U   - T is subtype of U
    T <%< U   - T is view-convertible to U
def firstLast[I, R](it: R)(implicit ev: R <:< Iterable[I]) = (it.head, it.last)

firstLast[Int, Range]( 1 to 10 ) // Range <:< Iterable[Int]

ev is Evidence Object, it's existence is evidence that Range is subtype of Iterable[Int]

compiler uses Evidence Object 'ev', to apply: (ev(it).head and ev(it).last)

8 The @implicitNotFound Annotation

Defines error message when compiler cannot construct implicit parameter

It's for useful error messages.

We define it over classes with implicit values:

    @implicitNotFound(msg = "Cannot prove that ${From} <:< ${To}.")
    abstract class <:<[-From, +To] extends Function1[From, To]
⚠️ **GitHub.com Fallback** ⚠️