From 6f26b06721a2e886a772d6a6331dbd610e6f5a70 Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Fri, 4 May 2018 10:19:16 -0700 Subject: [PATCH] FAQ rejiggered for implicit scope --- _overviews/FAQ/finding-implicits.md | 194 ++++++++++++++++++++-------- 1 file changed, 141 insertions(+), 53 deletions(-) diff --git a/_overviews/FAQ/finding-implicits.md b/_overviews/FAQ/finding-implicits.md index f10847c167..c12d7b2748 100644 --- a/_overviews/FAQ/finding-implicits.md +++ b/_overviews/FAQ/finding-implicits.md @@ -131,64 +131,116 @@ it implicitly to `sorted`. ## Where do Implicits Come From? -When the compiler sees the need for an implicit, either because you are calling -a method which does not exist on the object's class, or because you are calling -a method that requires an implicit parameter, it will search for an implicit -that will fit the need. - -This search obeys certain rules that define which implicits are visible and -which are not. The following table showing where the compiler will search for -implicits was taken from an excellent [presentation][1] about implicits by Josh -Suereth, which is heartily recommend to anyone wanting to improve their Scala -knowledge. It has been complemented since then with feedback and updates. - -The implicits available under number 1 below have precedence over the ones under -number 2. Other than that, if there are several eligible arguments which match -the implicit parameter’s type, a most specific one will be chosen using the rules -of static overloading resolution (see [Scala Specification][5] §6.26.4). - -1. First look in current scope - * Implicits defined in current scope - * Explicit imports - * wildcard imports - * Same scope in other files -2. Now look at associated types in - * Companion objects of a type - * Implicit scope of an argument's type **(2.9.1)** - * Implicit scope of type arguments **(2.8.0)** - * Outer objects for nested types - -Let's give examples for them. - -### Implicits Defined in Current Scope +As described above, there are several contexts in which an implicit value may be required +for an expression to typecheck. The required implicit type is what determines +which value is selected. That value is found either in lexical scope or, +failing that, in what is called implicit scope. - implicit val n: Int = 5 - def add(x: Int)(implicit y: Int) = x + y - add(5) // takes n from the current scope, res: Int = 10 +### Implicits Defined in Lexical Scope -### Explicit Imports +When a value of a certain name is required, lexical scope is searched for +a value with that name. Similarly, when an implicit value of a certain type is required, +lexical scope is searched for a value with that type. - import scala.collection.JavaConversions.mapAsScalaMap - def env = System.getenv() // Java map - val term = env("TERM") // implicit conversion from Java Map to Scala Map +Any such value which can be referenced with its "simple" name, without +selecting from another value using dotted syntax, is an eligible implicit value. -### Wildcard Imports +For example, here is a function that takes an implicit scaling factor. +The function requires a parameter of type `Int`, and there is a value +of that type in scope. The variable name `n` does not matter in this +case. - def sum[T : Integral](list: List[T]): T = { - val integral = implicitly[Integral[T]] - import integral._ // get the implicits in question into scope - list.foldLeft(integral.zero)(_ + _) + implicit val n: Int = 5 + def scale(x: Int)(implicit y: Int) = x * y + scale(5) // takes n from the current scope, with the result 25 + +The invocation can be rewritten `scale(5)(n)`. If `n` can be referenced +using its simple name, as shown here, it is eligible as an implicit value. + +An implicit value can be introduced into scope by an import statement: + + import scala.collection.JavaConverters._ + def env = System.getenv().asScala // extension method enabled by imported implicit + val term = env("TERM") // it's a Scala Map + +There may be more than one such value because they have different names. + +In that case, overload resolution is used to pick one of them. The algorithm +for overload resolution is the same used to choose the reference for a +given name, when more than one term in scope has that name. For example, +`println` is overloaded, and each overload takes a different parameter type. +An invocation of `println` requires selecting the correct overloaded method. + +In implicit search, overload resolution chooses a value among more than one +that have the same required type. Usually this entails selecting a narrower +type or a value defined in a subclass relative to other eligible values. + +The rule that the value must be accessible using its simple name means +that the normal rules for name binding apply. + +In summary, a definition for `x` shadows a definition in +an enclosing scope. But a binding for `x` can also be introduced by +local imports. Imported symbols can't override definitions of the same +name in an enclosing scope. Similarly, wildcard imports can't override +an import of a specific name, and names in the current package that are +visible from other source files can't override imports or local definitions. + +These are the normal rules for deciding what `x` means in a given context, +and also determine which value `x` is accessible by its simple name and +is eligible as an implicit. + +This means that an implicit in scope can be disabled by shadowing it with +a term of the same name. + +For example, here, `X.f` is supplied the imported `X.s`: `X.f(s)`. +The body of `f` uses an implicit `Int`, from the immediate scope, +which shadows the `n` from `Y`, which is therefore not an eligible +implicit value. The parameter `s` shadows the member `s`. + +The method `g` does not compile because the implicit `t` is shadowed +by a `t` that is not implicit, so no implicit `T` is in scope. + + object Y { + implicit val n: Int = 17 + trait T { + implicit val i: Int = 17 + implicit def t: T = ??? + } + object X extends T { + implicit val n: Int = 42 + implicit val s: String = "hello, world\n" + def f(implicit s: String) = implicitly[String] * implicitly[Int] + override def t: T = ??? + def g = implicitly[T] + } } + import Y.X._ + f + +The invocation of `f` was enabled by importing from `Y.X.`. But it is +not convenient to require an import to access implicit values +providied by a package. -### Same Scope in Other Files +If an implicit value is not found in lexical scope, implicit search +continues in implicit scope. -**Edit**: It seems this does not have a different precedence. If you have some -example that demonstrates a precedence distinction, please make a comment. -Otherwise, don't rely on this one. +### Implicits Defined in Implicit Scope -This is like the first example, but assuming the implicit definition is in a -different file than its usage. See also how [package objects][2] might be used -in to bring in implicits. +Implicit syntax can avoid the [import tax][1], which of course is a "sin tax," +by leveraging "implicit scope", which depends on the type of the implicit +instead of imports in lexical scope. + +When an implicit of type `T` is required, implicit scope includes +the companion object `T`: + + trait T + object T { implicit val t: T = new T { } } + +When an `F[T]` is required, implicit scope includes both the companion +of `F` and the companion of the type argument, e.g., `object C` for `F[C]`. + +In addition, implicit scope includes the companions of the base classes +of `F` and `C`, including package objects, such as `p` for `p.F`. ### Companion Objects of a Type @@ -254,8 +306,6 @@ conversions of that parameter alone, but of the whole expression. For example: // because it is converted into this: A.fromInt(1) + new A(1) -**This available only since Scala 2.9.1.** - ### Implicit scope of type arguments This is required to make the type class pattern really work. Consider @@ -290,8 +340,6 @@ parameter. The implicit looked for above is `Ordering[A]`, where `A` is an actual type, not type parameter: it is a _type argument_ to `Ordering`. See section 7.2 of the [Scala Specification][6]. -**This available only since Scala 2.8.0.** - ### Outer Objects for Nested Types The principle is simple: @@ -308,6 +356,46 @@ The principle is simple: A real world example of this would be welcome. Please share your example! +### Package Objects Can Contribute Implicit Values + +An implicit value in a package object can be made available either +in lexical scope or in implicit scope. + +To be available in lexical scope, the packages must be declared as nested packages: + + package object p { implicit val s: String = "hello, world" } + package p { + package q { + object X { def f = implicitly[String] } + } + } + +This is sensitive to name binding rules. The following example compiles +only if the package object is in a separate file, in which case the import is used: + + package object p { implicit val s: String = "hello, world" } + package p { + package q { + object Y { + implicit val s: String = "bye" + } + object X { + import Y._ + def f = implicitly[String] + } + } + } + +A package object can also offer implicit values of types in subpackages: + + package object p { implicit val c: q.C = new q.C } + package p.q { + class C + object X { def f = implicitly[C] } + } + +Here, the implicit is supplied in implicit scope of `C`. + ### Call To Action Avoid taking this question as being the final arbiter of what is happening.