21. Implicits - RobertMakyla/scalaWiki GitHub Wiki
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
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.
Implicit conversions are done in 3 situations:
- If actual type of expression differs from an expected one
example: setFraction(3) // setFraction expects Fraction, not an Int
- If an objects accesses a non existing method
example: new File("z:/HOW_TO/hudson_connect.txt").print // there's no File.print() method
- 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 !!!
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.
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)
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)
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]