|
1 | 1 | ---
|
2 | 2 | title: Implicit Conversions
|
3 | 3 | type: section
|
4 |
| -description: This page demonstrates how to implement Implicit Conversions in Scala 3. |
| 4 | +description: This page demonstrates how to implement Implicit Conversions in Scala. |
5 | 5 | languages: [zh-cn]
|
6 | 6 | num: 65
|
7 | 7 | previous-page: ca-multiversal-equality
|
8 | 8 | next-page: ca-summary
|
9 | 9 | ---
|
10 | 10 |
|
| 11 | +Implicit conversions are a powerful Scala feature that allows users to supply an argument |
| 12 | +of one type as if it were another type, to avoid boilerplate. |
11 | 13 |
|
12 |
| -Implicit conversions are defined by `given` instances of the `scala.Conversion` class. |
13 |
| -For example, not accounting for possible conversion errors, this code defines an implicit conversion from `String` to `Int`: |
| 14 | +> Note that in Scala 2, implicit conversions were also used to provide additional members |
| 15 | +> to closed classes (see [Implicit Classes]({% link _overviews/core/implicit-classes.md %})). |
| 16 | +> In Scala 3, we recommend to address this use-case by defining [extension methods] instead |
| 17 | +> of implicit conversions (although the standard library still relies on implicit conversions |
| 18 | +> for historical reasons). |
| 19 | +
|
| 20 | +## Example |
| 21 | + |
| 22 | +Consider for instance a method `findUserById` that takes a parameter of type `Long`: |
| 23 | + |
| 24 | +{% tabs implicit-conversions-1 %} |
| 25 | +{% tab 'Scala 2 and 3' %} |
| 26 | +~~~ scala |
| 27 | +def findUserById(id: Long): Option[User] |
| 28 | +~~~ |
| 29 | +{% endtab %} |
| 30 | +{% endtabs %} |
| 31 | + |
| 32 | +We omit the definition of the type `User` for the sake of brevity, it does not matter for |
| 33 | +our example. |
| 34 | + |
| 35 | +In Scala, it is possible to call the method `findUserById` with an argument of type `Int` |
| 36 | +instead of the expected type `Long`, because the argument will be implicitly converted |
| 37 | +into the type `Long`: |
| 38 | + |
| 39 | +{% tabs implicit-conversions-2 %} |
| 40 | +{% tab 'Scala 2 and 3' %} |
| 41 | +~~~ scala |
| 42 | +val id: Int = 42 |
| 43 | +findUserById(id) // OK |
| 44 | +~~~ |
| 45 | +{% endtab %} |
| 46 | +{% endtabs %} |
| 47 | + |
| 48 | +This code does not fail to compile with an error like “type mismatch: expected `Long`, |
| 49 | +found `Int`” because there is an implicit conversion that converts the argument `id` |
| 50 | +to a value of type `Long`. |
| 51 | + |
| 52 | +## Detailed Explanation |
| 53 | + |
| 54 | +This section describes how to define and use implicit conversions. |
| 55 | + |
| 56 | +### Defining an Implicit Conversion |
| 57 | + |
| 58 | +{% tabs implicit-conversions-3 class=tabs-scala-version %} |
| 59 | + |
| 60 | +{% tab 'Scala 2' %} |
| 61 | +In Scala 2, an implicit conversion from type `S` to type `T` is defined by an |
| 62 | +[implicit class]({% link _overviews/core/implicit-classes.md %}) `T` that takes |
| 63 | +a single constructor parameter of type `S`, an |
| 64 | +[implicit value]({% link _overviews/scala3-book/ca-given-using-clauses.md %}) of |
| 65 | +function type `S => T`, or by an implicit method convertible to a value of that type. |
| 66 | + |
| 67 | +For example, the following code defines an implicit conversion from `Int` to `Long`: |
| 68 | + |
| 69 | +~~~ scala |
| 70 | +import scala.language.implicitConversions |
| 71 | + |
| 72 | +implicit def int2long(x: Int): Long = x.toLong |
| 73 | +~~~ |
| 74 | + |
| 75 | +This is an implicit method convertible to a value of type `Int => Long`. |
| 76 | + |
| 77 | +See the section “Beware the Power of Implicit Conversions” below for an |
| 78 | +explanation of the clause `import scala.language.implicitConversions` |
| 79 | +at the beginning. |
| 80 | +{% endtab %} |
| 81 | + |
| 82 | +{% tab 'Scala 3' %} |
| 83 | +In Scala 3, an implicit conversion from type `S` to type `T` is defined by a |
| 84 | +[`given` instance]({% link _overviews/scala3-book/ca-given-using-clauses.md %}) |
| 85 | +of type `scala.Conversion[S, T]`. For compatibility with Scala 2, it can also |
| 86 | +be defined by an implicit method (read more in the Scala 2 tab). |
| 87 | + |
| 88 | +For example, this code defines an implicit conversion from `Int` to `Long`: |
14 | 89 |
|
15 | 90 | ```scala
|
16 |
| -given Conversion[String, Int] with |
17 |
| - def apply(s: String): Int = Integer.parseInt(s) |
| 91 | +given int2long: Conversion[Int, Long] with |
| 92 | + def apply(x: Int): Long = x.toLong |
18 | 93 | ```
|
19 | 94 |
|
20 |
| -Using an alias this can be expressed more concisely as: |
| 95 | +Like other given definitions, implicit conversions can be anonymous: |
| 96 | + |
| 97 | +~~~ scala |
| 98 | +given Conversion[Int, Long] with |
| 99 | + def apply(x: Int): Long = x.toLong |
| 100 | +~~~ |
| 101 | + |
| 102 | +Using an alias, this can be expressed more concisely as: |
21 | 103 |
|
22 | 104 | ```scala
|
23 |
| -given Conversion[String, Int] = Integer.parseInt(_) |
| 105 | +given Conversion[Int, Long] = (x: Int) => x.toLong |
24 | 106 | ```
|
| 107 | +{% endtab %} |
25 | 108 |
|
26 |
| -Using either of those conversions, you can now use a `String` in places where an `Int` is expected: |
| 109 | +{% endtabs %} |
27 | 110 |
|
28 |
| -```scala |
| 111 | +### Using an Implicit Conversion |
| 112 | + |
| 113 | +Implicit conversions are applied in two situations: |
| 114 | + |
| 115 | +1. If an expression `e` is of type `S`, and `S` does not conform to the expression's expected type `T`. |
| 116 | +2. In a selection `e.m` with `e` of type `S`, if the selector `m` does not denote a member of `S` |
| 117 | + (to support Scala-2-style [extension methods]). |
| 118 | + |
| 119 | +In the first case, a conversion `c` is searched for, which is applicable to `e` and whose result type conforms to `T`. |
| 120 | + |
| 121 | +In our example above, when we pass the argument `id` of type `Int` to the method `findUserById`, |
| 122 | +the implicit conversion `int2long(id)` is inserted. |
| 123 | + |
| 124 | +In the second case, a conversion `c` is searched for, which is applicable to `e` and whose result contains a member named `m`. |
| 125 | + |
| 126 | +An example is to compare two strings `"foo" < "bar"`. In this case, `String` has no member `<`, so the implicit conversion `Predef.augmentString("foo") < "bar"` is inserted. (`scala.Predef` is automatically imported into all Scala programs.) |
| 127 | + |
| 128 | +### How Are Implicit Conversions Brought Into Scope? |
| 129 | + |
| 130 | +When the compiler searches for applicable conversions: |
| 131 | + |
| 132 | +- first, it looks into the current lexical scope |
| 133 | + - implicit conversions defined in the current scope or the outer scopes |
| 134 | + - imported implicit conversions |
| 135 | + - implicit conversions imported by a wildcard import (Scala 2 only) |
| 136 | +- then, it looks into the [companion objects] _associated_ with the argument |
| 137 | + type `S` or the expected type `T`. The companion objects associated with |
| 138 | + a type `X` are: |
| 139 | + - the companion object `X` itself |
| 140 | + - the companion objects associated with any of `X`’s inherited types |
| 141 | + - the companion objects associated with any type argument in `X` |
| 142 | + - if `X` is an inner class, the outer objects in which it is embedded |
| 143 | + |
| 144 | +For instance, consider an implicit conversion `fromStringToUser` defined in an |
| 145 | +object `Conversions`: |
| 146 | + |
| 147 | +{% tabs implicit-conversions-4 class=tabs-scala-version %} |
| 148 | +{% tab 'Scala 2' %} |
| 149 | +~~~ scala |
29 | 150 | import scala.language.implicitConversions
|
30 | 151 |
|
31 |
| -// a method that expects an Int |
32 |
| -def plus1(i: Int) = i + 1 |
| 152 | +object Conversions { |
| 153 | + implicit def fromStringToUser(name: String): User = (name: String) => User(name) |
| 154 | +} |
| 155 | +~~~ |
| 156 | +{% endtab %} |
| 157 | +{% tab 'Scala 3' %} |
| 158 | +~~~ scala |
| 159 | +object Conversions: |
| 160 | + given fromStringToUser: Conversion[String, User] = (name: String) => User(name) |
| 161 | +~~~ |
| 162 | +{% endtab %} |
| 163 | +{% endtabs %} |
33 | 164 |
|
34 |
| -// pass it a String that converts to an Int |
35 |
| -plus1("1") |
36 |
| -``` |
| 165 | +The following imports would equivalently bring the conversion into scope: |
37 | 166 |
|
38 |
| -> Note the clause `import scala.language.implicitConversions` at the beginning, |
39 |
| -> to enable implicit conversions in the file. |
| 167 | +{% tabs implicit-conversions-5 class=tabs-scala-version %} |
| 168 | +{% tab 'Scala 2' %} |
| 169 | +~~~ scala |
| 170 | +import Conversions.fromStringToUser |
| 171 | +// or |
| 172 | +import Conversions._ |
| 173 | +~~~ |
| 174 | +{% endtab %} |
| 175 | +{% tab 'Scala 3' %} |
| 176 | +~~~ scala |
| 177 | +import Conversions.fromStringToUser |
| 178 | +// or |
| 179 | +import Conversions.given |
| 180 | +// or |
| 181 | +import Conversions.{given Conversion[String, User]} |
| 182 | +~~~ |
40 | 183 |
|
41 |
| -## Discussion |
| 184 | +Note that in Scala 3, a wildcard import (ie `import Conversions.*`) does not import given |
| 185 | +definitions. |
| 186 | +{% endtab %} |
| 187 | +{% endtabs %} |
42 | 188 |
|
43 |
| -The Predef package contains “auto-boxing” conversions that map primitive number types to subclasses of `java.lang.Number`. |
44 |
| -For instance, the conversion from `Int` to `java.lang.Integer` can be defined as follows: |
| 189 | +In the introductory example, the conversion from `Int` to `Long` does not require an import |
| 190 | +because it is defined in the object `Int`, which is the companion object of the type `Int`. |
45 | 191 |
|
46 |
| -```scala |
47 |
| -given int2Integer: Conversion[Int, java.lang.Integer] = |
48 |
| - java.lang.Integer.valueOf(_) |
49 |
| -``` |
| 192 | +Further reading: |
| 193 | +[Where does Scala look for implicits? (on Stackoverflow)](https://stackoverflow.com/a/5598107). |
| 194 | + |
| 195 | +### Beware the Power of Implicit Conversions |
| 196 | + |
| 197 | +{% tabs implicit-conversions-6 class=tabs-scala-version %} |
| 198 | +{% tab 'Scala 2' %} |
| 199 | +Because implicit conversions can have pitfalls if used indiscriminately the compiler warns when compiling the implicit conversion definition. |
| 200 | + |
| 201 | +To turn off the warnings take either of these actions: |
| 202 | + |
| 203 | +* Import `scala.language.implicitConversions` into the scope of the implicit conversion definition |
| 204 | +* Invoke the compiler with `-language:implicitConversions` |
| 205 | + |
| 206 | +No warning is emitted when the conversion is applied by the compiler. |
| 207 | +{% endtab %} |
| 208 | +{% tab 'Scala 3' %} |
| 209 | +Because implicit conversions can have pitfalls if used indiscriminately the compiler warns in two situations: |
| 210 | +- when compiling a Scala 2 style implicit conversion definition. |
| 211 | +- at the call site where a given instance of `scala.Conversion` is inserted as a conversion. |
| 212 | + |
| 213 | +To turn off the warnings take either of these actions: |
| 214 | + |
| 215 | +- Import `scala.language.implicitConversions` into the scope of: |
| 216 | + - a Scala 2 style implicit conversion definition |
| 217 | + - call sites where a given instance of `scala.Conversion` is inserted as a conversion. |
| 218 | +- Invoke the compiler with `-language:implicitConversions` |
| 219 | +{% endtab %} |
| 220 | +{% endtabs %} |
| 221 | + |
| 222 | +[extension methods]: {% link _overviews/scala3-book/ca-extension-methods.md %} |
| 223 | +[companion objects]: {% link _overviews/scala3-book/domain-modeling-tools.md %}#companion-objects |
0 commit comments