Skip to content

More details on implicit conversions #5347

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Oct 31, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 121 additions & 0 deletions docs/docs/reference/changed/implicit-conversions-spec.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
---
layout: doc-page
title: "Implicit Conversions - More Details"
---

## Implementation

An implicit conversion, or _view_, from type `S` to type `T` is
defined by either:

- An `implicit def` which has type `S => T` or `(=> S) => T`
- An implicit value which has type `ImplicitConverter[S, T]`

The standard library defines an abstract class `ImplicitConverter`:

```scala
abstract class ImplicitConverter[-T, +U] extends Function1[T, U]
```

Function literals are automatically converted to `ImplicitConverter`
values.

Views are applied in three situations:

1. If an expression `e` is of type `T`, and `T` does not conform to
the expression's expected type `pt`. In this case, an implicit `v`
which is applicable to `e` and whose result type conforms to `pt`
is searched. The search proceeds as in the case of implicit
parameters, where the implicit scope is the one of `T => pt`. If
such a view is found, the expression `e` is converted to `v(e)`.
1. In a selection `e.m` with `e` of type `T`, if the selector `m` does
not denote an accessible member of `T`. In this case, a view `v`
which is applicable to `e` and whose result contains an accessible
member named `m` is searched. The search proceeds as in the case of
implicit parameters, where the implicit scope is the one of `T`. If
such a view is found, the selection `e.m` is converted to `v(e).m`.
1. In an application `e.m(args)` with `e` of type `T`, if the selector
`m` denotes some accessible member(s) of `T`, but none of these
members is applicable to the arguments `args`. In this case, a view
`v` which is applicable to `e` and whose result contains a method
`m` which is applicable to `args` is searched. The search proceeds
as in the case of implicit parameters, where the implicit scope is
the one of `T`. If such a view is found, the application
`e.m(args)` is converted to `v(e).m(args)`.

# Differences with Scala 2 implicit conversions

In Scala 2, views whose parameters are passed by-value take precedence
over views whose parameters are passed by-name. This is no longer the
case in Scala 3. A type error reporting the ambiguous conversions will
be emitted in cases where this rule would be applied in Scala 2:

```scala
implicit def conv1(x: Int): String = x.toString
implicit def conv2(x: => Int): String = x.toString

val x: String = 0 // Compiles in Scala2 (uses `conv1`),
// type error in Scala 3 because of ambiguity.
```

In Scala 2, implicit values of a function type would be considered as
potential views. In Scala 3, these implicit value need to have type
`ImplicitConverter`:

```scala
// Scala 2:
def foo(x: Int)(implicit conv: Int => String): String = x

// Becomes with Scala 3:
def foo(x: Int)(implicit conv: ImplicitConverter[Int, String]): String = x

// Call site is unchanged:
foo(4)(_.toString)

// Scala 2:
implicit val myConverter: Int => String = _.toString

// Becomes with Scala 3:
implicit val myConverter: ImplicitConverter[Int, String] = _.toString
```

Note that implicit conversions are also affected by the [changes to
implicit resolution](implicit-resolution.html) between Scala 2 and
Scala 3.

## Motivation for the changes

The introduction of `ImplicitConverter` in Scala 3 and the decision to
restrict implicit values of this type to be considered as potential
views comes from the desire to remove surprising behavior from the
language:

```scala
implicit val m: Map[Int, String] = Map(1 -> "abc")

val x: String = 1 // scalac: assigns "abc" to x
// Dotty: type error
```

This snippet contains a type error. The right hand side of `val x`
does not conform to type `String`. In Scala 2, the compiler will use
`m` as an implicit conversion from `Int` to `String`, whereas Scala 3
will report a type error, because Map isn't an instance of
`ImplicitConverter`.

## Migration path

Implicit values that are used as views should see their type changed
to `ImplicitConverter`.

For the migration of implicit conversions that are affected by the
changes to implicit resolution, refer to the [Changes in Implicit
Resolution](implicit-resolution.html) for more information.

## Reference

For more information about implicit resolution, see [Changes in
Implicit Resolution](implicit-resolution.html).
Other details are available in
[PR #2065](https://github.com/lampepfl/dotty/pull/2065)

77 changes: 46 additions & 31 deletions docs/docs/reference/changed/implicit-conversions.md
Original file line number Diff line number Diff line change
@@ -1,49 +1,64 @@
---
layout: doc-page
title: "Restrictions to Implicit Conversions"
title: "Implicit Conversions"
---

Previously, an implicit value of type `Function1`, or any of its subtypes
could be used as an implicit conversion. That is, the following code would compile
even though it probably masks a type error:
An _implicit conversion_, also called _view_, is a conversion that
is applied by the compiler in several situations:

implicit val m: Map[Int, String] = Map(1 -> "abc")
1. When an expression `e` of type `T` is encountered, but the compiler
needs an expression of type `S`.
1. When an expression `e.m` where `e` has type `T` but `T` defines no
member `m` is encountered.

val x: String = 1 // scalac: assigns "abc" to x
// Dotty: type error
In those cases, the compiler looks in the implicit scope for a
conversion that can convert an expression of type `T` to an expression
of type `S` (or to a type that defines a member `m` in the second
case).

By contrast, Dotty only considers _methods_ as implicit conversions, so the
`Map` value `m` above would not qualify as a conversion from `String` to `Int`.
This conversion can be either:

To be able to express implicit conversions passed as parameters, `Dotty`
introduces a new type
1. An `implicit def` of type `T => S` or `(=> T) => S`
1. An implicit value of type `ImplicitConverter[T, S]`

abstract class ImplicitConverter[-T, +U] extends Function1[T, U]
Defining an implicit conversion will emit a warning unless the import
`scala.language.implicitConversions` is in scope, or the flag
`-language:implicitConversions` is given to the compiler.

Implicit values of type `ImplicitConverter[A, B]` do qualify as implicit
conversions. It is as if there was a global implicit conversion method
## Examples

def convert[A, B](x: A)(implicit converter: ImplicitConverter[A, B]): B =
converter(x)
The first example is taken from `scala.Predef`. Thanks to this
implicit conversion, it is possible to pass a `scala.Int` to a Java
method that expects a `java.lang.Integer`

(In reality the Dotty compiler simulates the behavior of this method directly in
its type checking because this turns out to be more efficient).
```scala
import scala.language.implicitConversions
implicit def int2Integer(x: Int): java.lang.Integer =
x.asInstanceOf[java.lang.Integer]
```

In summary, previous code using implicit conversion parameters such as
The second example shows how to use `ImplicitConverter` to define an
`Ordering` for an arbitrary type, given existing `Ordering`s for other
types:

def useConversion(implicit f: A => B) = {
val y: A = ...
val x: B = y // error under Dotty
}
```scala
import scala.language.implicitConversions
implicit def ordT[T, S](
implicit conv: ImplicitConverter[T, S],
ordS: Ordering[S]
): Ordering[T] = {
// `ordS` compares values of type `S`, but we can convert from `T` to `S`
(x: T, y: T) => ordS.compare(x, y)
}

is no longer legal and has to be rewritten to
class A(val x: Int) // The type for which we want an `Ordering`

def useConversion(implicit f: ImplicitConverter[A, B]) = {
val y: A = ...
val x: B = y // OK
}
// Convert `A` to a type for which an `Ordering` is available:
implicit val AToInt: ImplicitConverter[A, Int] = _.x

### Reference

For more info, see [PR #2065](https://github.com/lampepfl/dotty/pull/2065).
implicitly[Ordering[Int]] // Ok, exists in the standard library
implicitly[Ordering[A]] // Ok, will use the implicit conversion from
// `A` to `Int` and the `Ordering` for `Int`.
```

[More details](implicit-conversions-spec.html)