Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Demystifying Implicits

Demystifying Implicits

Implicits are a powerful, code-condensing feature of Scala, considered by some users "the second hardest feature, first being to convince your manager to use Scala". With implicits, dependencies are resolved at compile-time, code base size is drastically reduced by removed boilerplate, classes are loosely coupled, extendable and yet type safe.

A great feature, so what is it so hard?

The implicit system allows the compiler to adjust code using a lookup mechanism and to solve implicit ambiguities using some precedence rules. The multitude of rules of implicits resolution algorithm and the compiler converting types leads to hard-to-debug and possibly dangerous behavior - associating implicits feature with mystery, magic and controversy.

Throughout this presentation, we'll try to better understand what implicits are and how we can use them. In the mean time, we'll zoom into the resolution mechanism to understand some of its rules — basically we'll try to learn how to unleash implicits' great power and to diminish the associated confusion.

Luminita Apostol

April 27, 2016
Tweet

Other Decks in Programming

Transcript

  1. Intro to implicits Common use Implicit parameters Implicit values and

    functions Implicit conversions Use of implicit conversions Implicit classes Context bounds
  2. Common use mark a parameter and a value with implicit

    def example(implicit x: Int) = println(x) implicit val element: Int = 5 example
  3. Common use mark a parameter and a value with implicit

    def example(implicit x: Int) = println(x) implicit val element: Int = 5 example // 5
  4. Implicit parameters tells the compiler that the argument list might

    be missing compiler should resolve the arguments via implicit resolution def example1(implicit x: Int = 5) def example2(implicit x: Int, y: Int) def example3(x: Int, implicit y: Int) def example4(x: Int)(implicit y: Int) def example5(implicit x: Int)(y: Int) def example6(implicit x: Int)(implicit y: Int)
  5. Implicit parameters tells the compiler that the argument list might

    be missing compiler should resolve the arguments via implicit resolution Note: implicit parameters can have default values all parameters following it will be implicit must be the last parameter list given methods can have only one implicit parameter list def example1(implicit x: Int = 5) // correct def example2(implicit x: Int, y: Int) // correct def example3(x: Int, implicit y: Int) // compilation error def example4(x: Int)(implicit y: Int) // correct def example5(implicit x: Int)(y: Int) // compilation error def example6(implicit x: Int)(implicit y: Int) // compilation error
  6. Implicit values and functions tells the compiler that those definitions

    can be used during implicit resolution implicit val b1: Bar = new Bar { override def toString = "val Bar"} implicit def b2(): Bar = new Bar { override def toString = "def Bar"
  7. Implicit conversions (views) an implicit value of unary function type

    A => B an implicit method that has in its first parameter section a single, non-implicit parameter implicit val conv: String => Int = (s: String) => s.length implicit def stringToInt(s: String): Int = s.length implicit def listToX(xs: List[T])(implicit f: T => X):
  8. Use of implicit conversions an expression doesn't meet the type

    expected by the compiler def foo(msg: String) = println(msg) foo(5) implicit def intToString(x: Int): String = x.toString
  9. Use of implicit conversions an expression doesn't meet the type

    expected by the compiler a selection e.t doesn't have a t member def foo(msg: String) = println(msg) foo(5) // 5 implicit def intToString(x: Int): String = x.toString "foo".foo() implicit def stringToFoo(x: String): Foo = new Foo { def foo() = println(s"String $x foos") } trait Foo { def foo(): Unit }
  10. Use of implicit conversions an expression doesn't meet the type

    expected by the compiler a selection e.t doesn't have a t member def foo(msg: String) = println(msg) foo(5) // 5 implicit def intToString(x: Int): String = x.toString "foo".foo() // String foo foos implicit def stringToFoo(x: String): Foo = new Foo { def foo() = println(s"String $x foos") } trait Foo { def foo(): Unit }
  11. Implicit classes makes the class’ primary constructor available for implicit

    conversions implicit class RichInt(a: Int) { def foo() = println(s"Int $a foos") } 3.foo()
  12. Implicit classes makes the class’ primary constructor available for implicit

    conversions Note: they must be defined inside of another trait/class/object they may only take one non-implicit argument in their constructor there may not be any method, member or object in scope with the same name as the implicit class implicit class RichInt(a: Int) { def foo() = println(s"Int $a foos") } 3.foo() // Int 3 foos
  13. Context bounds declares that there must be an implicit value

    available with a given type def foo[A](x: A)(implicit ev: B[A]) // equivalent to def foo[A : B](x: A)
  14. Implicit resolution algorithm 1. First looks in the 2. Next

    looks in the - all companion modules of classes associated with implicit parameter's type current scope implicit scope
  15. Current scope IDENTIFIERS: A DIGRESSION identifiers play a crucial role

    in the selection of implicits how the compiler resolves identifiers within a particular scope
  16. Scala definitions entity = types, values, methods, or classes binding

    = reference to entities using identifiers or names scope = lexical boundary in which bindings are available
  17. Bindings shadowing Scope can be nested - when creating a

    new scope, bindings from the outer scope are still available class Foo(x : Int) { def tmp = { val x = 2 x } } bindings of higher precedence shadow bindings of lower precedence within the same scope bindings of higher or the same precedence shadow bindings in an outer scope Note: shadowing is only a partial order
  18. Teaser ? // ExternalBinding.scala package play.bindings object x { override

    def toString = "4" } object Wildcard { def x = "3" } object Explicit { def x = "2" } // Main.scala package play.bindings object Main extends App { val x = "1" import Wildcard._ import Explicit.x println(x) }
  19. Scala binding precedence 1. Definitions and declarations that are local,

    inherited, or made available by a package clause in the same source file where the definition occurs 2. Explicit imports 3. Wildcard imports 4. Definitions made available by a package clause not in the source file where the definition occurs
  20. How it relates to implicit resolution It uses the same

    mechanism: 1. Implicits that are local, inherited or members of an enclosing template 2. Explicit imports 3. Wildcard imports 4. N/A
  21. Implicit scope Defined by all companion modules of classes that

    are associated with the implicit parameter's type T Form of T Associated classes T1 extends T2 with T3 {T1, T2, T3} T1[S1, S2] {T1, S1, S2} T1 nested within S1 {T1, S1, associated classes(S1)} type T1 = S1 with S2 {S1, S2 & their associated classes} T1 => S1 view {T1, S1 & their associated classes}
  22. Companion object List(1, 2, 3).sorted def sorted[B >: A](implicit ord:

    math.Ordering[B]): List[A] object Ordering { trait IntOrdering extends Ordering[Int] { def compare(x: Int, y: Int) = if (x < y) ­1 else if (x == y) 0 else 1 } implicit object Int extends IntOrdering }
  23. Companion objects of the superclasses class A class B extends

    A object A { implicit def aToString(a: A): String = "A object" } val s: String = new B Note: the most specific argument that matches the implicit parameter's type will be chosen using the rules of static overloading resolution
  24. Implicit conversion List(1, 2, 3).flatMap(x => Some(0).map(y => (x, y)))

    def flatMap[B, That](f: A => GenTraversableOnce[B]) object Option { /** An implicit conversion that converts an option to an iterable value * implicit def option2Iterable[A](xo: Option[A]): Iterable[A] = xo.toList }
  25. Outer objects for nested types // for summoning implicit values

    from the nether world def implicitly[T](implicit e: T) = e object A { object B { override def toString = "B object" } implicit val b: B.type = B } println(implicitly[B.type])
  26. Outer objects for nested types // for summoning implicit values

    from the nether world def implicitly[T](implicit e: T) = e object A { object B { override def toString = "B object" } implicit val b: B.type = B } println(implicitly[B.type]) // B object
  27. Companion object of type arguments List(new Foo(5), new Foo(17), new

    Foo(4)).sorted class Foo(val n: Int) object Foo { implicit val ord: Ordering[Foo] = new Ordering[Foo] { def compare(x: Foo, y: Foo) = implicitly[Ordering[Int]].compare(x.n, } } def sorted[B >: A](implicit ord: Ordering[B])
  28. Companion object of type arguments List(new Foo(5), new Foo(17), new

    Foo(4)).sorted // List(4, 5, 17) class Foo(val n: Int) object Foo { implicit val ord: Ordering[Foo] = new Ordering[Foo] { def compare(x: Foo, y: Foo) = implicitly[Ordering[Int]].compare(x.n, } } def sorted[B >: A](implicit ord: Ordering[B])
  29. Package objects Any class that’s defined within a package is

    nested inside the package Any implicits defined on a package object will be on the implicit scope for all types defined inside the package Handy location to store implicits rather than defining companion objects for every type in a package
  30. Why are implicits great useful for removing boiler plate parameter

    passing can make your code more readable* decoupled dependency injection class extensions type classes flexibility
  31. Pitfalls "If a programmer is not familiar with all the

    views in scope, the code is harder to interpret." "Implicit views are the most abused feature in Scala." "Implicits make Scala code look sexy yet impossible to understand. Sad but true, it is easy to write, but not to understand." "It is common to use package objects with implicits, so there is no way to eyeball that this small import on top of a file feeds several function calls with implicit variables." Checkout on Implicit Kryptonite http://scalapuzzlers.com
  32. With great power comes great responsibility limit the number of

    implicits that are in scope provide implicits that can be overridden or hidden limit importable implicits package objects singleton objects that have the postfix Implicits implicits without the import tax
  33. References Scala in depth Scala lang - Implicits Implicit Parameters

    in Scala Scala docs - Finding implicits Joshua Suereth - Implicits without the import tax Sohum Banerjea - Demystifying implicits and typeclasses in scala Spiridon - Scala implicit hell Programming in Scala - Implicit Conversions and Parameters