The `into` Type Modifier
Scala 3's implicit conversions of the scala.Conversion
class require a language import
import scala.language.implicitConversions
in any code that uses them as implicit conversions (code that calls conversions explicitly is not affected). If the import is missing, a feature warning is currently issued, and this will become an error in a future version of Scala 3. The motivation for this restriction is that code with hidden implicit conversions is hard to understand and might have correctness or performance problems that go undetected.
There is one broad use case, however, where implicit conversions are very hard to replace. This is the case where an implicit conversion is used to adapt a method argument to its formal parameter type. An example from the standard library:
scala> val xs = List(0, 1)
scala> val ys = Array(2, 3)
scala> xs ++ ys
val res0: List[Int] = List(0, 1, 2, 3)
The last input made use of an implicit conversion from Array[Int]
to IterableOnce[Int]
which is defined as a Scala 2 style implicit conversion in the standard library. Once the standard library is rewritten with Scala 3 conversions, this will require a language import at the use site, which is clearly unacceptable. It is possible to avoid the need for implicit conversions using method overloading or type classes, but this often leads to longer and more complicated code, and neither of these alternatives work for vararg parameters.
This is where the into
modifier on parameter types comes in. Here is a signature of the ++
method on List[A]
that uses it:
def ++ (elems: into IterableOnce[A]): List[A]
The into
modifier on the type of elems
means that implicit conversions can be applied to convert the actual argument to an IterableOnce
value, and this without needing a language import.
Function arguments
into
also allows conversions on the results of function arguments. For instance, consider the new proposed signature of the flatMap
method on List[A]
:
def flatMap[B](f: into A => IterableOnce[B]): List[B]
This allows a conversion of the actual argument to the function type A => IterableOnce[B]
. Crucially, it also allows that conversion to be applied to the function result. So the following would work:
scala> val xs = List(1, 2, 3)
scala> xs.flatMap(x => x.toString * x)
val res2: List[Char] = List(1, 2, 2, 3, 3, 3)
Here, the conversion from String
to Iterable[Char]
is applied on the results of flatMap
's function argument when it is applied to the elements of xs
.
Vararg arguments
When applied to a vararg parameter, into
allows a conversion on each argument value individually. For example, consider a method concatAll
that concatenates a variable number of IterableOnce[Char]
arguments, and also allows implicit conversions into IterableOnce[Char]
:
def concatAll(xss: into IterableOnce[Char]*): List[Char] =
xss.foldLeft(List[Char]())(_ ++ _)
Here, the call
concatAll(List('a'), "bc", Array('d', 'e'))
would apply two different implicit conversions: the conversion from String
to Iterable[Char]
gets applied to the second argument and the conversion from Array[Char]
to Iterable[Char]
gets applied to the third argument.
Retrofitting Scala 2 libraries
A new annotation allowConversions
has the same effect as an into
modifier. It is defined as an @experimental
class in package scala.annotation
. It is intended to be used for retrofitting Scala 2 library code so that Scala 3 conversions can be applied to arguments without language imports. For instance, the definitions of ++
and flatMap
in the Scala 2.13 List
class could be retrofitted as follows.
def ++ (@allowConversions elems: IterableOnce[A]): List[A]
def flatMap[B](@allowConversions f: A => IterableOnce[B]): List[B]
For Scala 3 code, the into
modifier is preferred. First, because it is shorter, and second, because it adheres to the principle that annotations should not influence typing and type inference in Scala.
Syntax changes
The addition to the grammar is:
ParamType ::= [‘=>’] ParamValueType
ParamValueType ::= [‘into‘] ExactParamType
ExactParamType ::= Type [‘*’]
As the grammar shows, into
can only applied to the type of a parameter; it is illegal in other positions.