Skip to content

Commit 83173ab

Browse files
committed
Tweak Seq migration guide
1 parent cc472a1 commit 83173ab

File tree

1 file changed

+81
-36
lines changed

1 file changed

+81
-36
lines changed

_overviews/core/collections-migration-213.md

Lines changed: 81 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -31,95 +31,140 @@ The [scala-collection-compat](https://github.com/scala/scala-collection-compat)
3131

3232
The module also provides [migratrion rules](https://github.com/scala/scala-collection-compat#migration-tool) for [scalafix](https://scalacenter.github.io/scalafix/docs/users/installation.html) that can update a project's source code to work with the 2.13 collections library.
3333

34-
## scala.Seq and scala.IndexedSeq migration
34+
## scala.Seq, varargs and scala.IndexedSeq migration
3535

36-
In Scala 2.13 `scala.Seq[+A]` is an alias for `scala.collection.immutable.Seq[A]` ("ISeq"), instead of `scala.collection.Seq[A]` ("CSeq"). Similarly, `scala.IndexedSeq[+A]` is an alias for `scala.collection.immutable.IndexedSeq[A]`. These changes require some planning depending on how your code is going to be used.
36+
In Scala 2.13 `scala.Seq[+A]` is an alias for `scala.collection.immutable.Seq[A]`, instead of `scala.collection.Seq[A]`, and `scala.IndexedSeq[+A]` is an alias for `scala.collection.immutable.IndexedSeq[A]`. These changes require some planning depending on how your code is going to be used.
3737

38-
If you're making a library intended to be used by other programmers, then using `scala.Seq`, `scala.IndexedSeq`, or vararg is going to be a breaking change in the API semantics. For example, if there was a function `def orderFood(order: Seq[Order]): Seq[Food]`, previously the library user would have been able to pass in an array of `Order`, but it won't work for 2.13.
38+
The change in definition of `scala.Seq` also has the effect of making the type of varargs parameters immutable sequences, due to [SLS 6.6][], so in
39+
a method such as `orderFood(xs: _*)` the varargs parameter `xs` must be an immutable sequence.
3940

40-
- if you cross build with Scala 2.12 and want to maintain the API semantics for 2.13 version of your library, or
41-
- if your library users frequently uses mutable collections such as `Array`
41+
[SLS 6.6]: https://www.scala-lang.org/files/archive/spec/2.12/06-expressions.html#function-applications
4242

43-
you can import collection Seq explicitly in your code.
43+
Therefore any method signature in Scala 2.13 which includes `scala.Seq`, varargs, or `scala.IndexedSeq` is going
44+
to have a breaking change in API semantics (as the immutable sequence types require more — immutability — than the
45+
not-immutable types). For example, users of a method like `def orderFood(order: Seq[Order]): Seq[Food]` would
46+
previously have been able to pass in an `ArrayBuffer` of `Order`, but cannot in 2.13.
47+
48+
### Migrating varargs
49+
50+
The change for varargs is unavoidable, as you cannot change the type used at definition site. The options
51+
available for migrating the usage sites are the following:
52+
53+
- change the value to already be an immutable sequence, which allows for direct varargs usage: `xs: _*`,
54+
- change the value to be an immutable sequence on the fly by calling `.toSeq`: `xs.toSeq: _*`, which will only
55+
copy data if the sequence wasn't already immutable
56+
- use `scala.collection.immutable.ArraySeq.unsafeWrapArray` to wrap your array and avoid copying, but see its
57+
scaladoc
58+
59+
### Option 1: migrate back to scala.collection.Seq
60+
61+
The first, in some ways simplest, migration strategy for all non-varargs usages of `scala.Seq` is to replace
62+
them with `scala.collection.Seq` (and require users to call `.toSeq` or `unsafeWrapArray` when passing such
63+
sequences to varargs methods).
64+
65+
We recommend using `import scala.collection`/`import scala.collection.immutable` and
66+
`collection.Seq`/`immutable.Seq`.
67+
68+
We recommend against using `import scala.collection.Seq`, which shadows the automatically imported `scala.Seq`,
69+
because even if it's a oneline change it causes name confusion. For code generation or macros the safest option
70+
is using the fully-qualified `_root_.scala.collection.Seq`.
71+
72+
As an example, the migration would look something like this:
4473

4574
~~~ scala
46-
import scala.collection.Seq
75+
import scala.collection
4776

4877
object FoodToGo {
49-
def orderFood(order: Seq[Order]): Seq[Food]
78+
def orderFood(order: collection.Seq[Order]): collection.Seq[Food]
5079
}
5180
~~~
5281

53-
Note that this might still break the source compatibility if `scala.Seq` (or just `Seq`) appears in the source code.
82+
However users of this code in Scala 2.13 would also have to migrate, as the result type is source-incompatible
83+
with any `scala.Seq` (or just `Seq`) usage in their code:
5484

5585
~~~ scala
5686
val food: Seq[Food] = FoodToGo.orderFood(order) // won't compile
5787
~~~
5888

59-
Since `Seq`, an alias for ISeq in 2.13, is narrower than CSeq, the above code will no longer compile. One workaround would be to ask your users to add `toSeq`, which returns ISeq.
89+
The simplest workaround is to ask your users to call `.toSeq` on the result which will return an immutable Seq,
90+
and only copy data if the sequence wasn't immutable:
6091

6192
~~~ scala
6293
val food: Seq[Food] = FoodToGo.orderFood(order).toSeq // add .toSeq
6394
~~~
6495

65-
Another workaround might be to accept CSeq, but return ISeq.
96+
### Option 2: use scala.collection.Seq for parameters and scala.collection.immutable.Seq for result types
97+
98+
The second, intermediate, migration strategy would be to change all methods to accept not-immutable Seq but
99+
return immutable Seq, following the [robustness principle][] (also known as "Postel's law"):
100+
101+
[robustness principle]: https://en.wikipedia.org/wiki/Robustness_principle
66102

67103
~~~ scala
68-
import scala.collection.{ Seq => CSeq }
69-
import scala.collection.immutable.{ Seq => ISeq }
104+
import scala.collection
105+
import scala.collection.immutable
70106

71107
object FoodToGo {
72-
def orderFood(order: CSeq[Order]): ISeq[Food]
108+
def orderFood(order: collection.Seq[Order]): immutable.Seq[Food]
73109
}
74110
~~~
75111

76-
In the future when your API is able to break the source compatibility, it might also make sense to migrate towards ISeq for both Scala 2.12 and Scala 2.13.
112+
### Option 3: use immutable sequences
113+
114+
The third migration strategy is to change your API to use immutable sequences for both parameter and result
115+
types. When cross-building your library for Scala 2.12 and 2.13 this could either mean:
116+
117+
- continuing to use `scala.Seq` which means it stays source and binary-compatible in 2.12, but would have to
118+
have immutable sequence semantics (but that might already be the case).
119+
- switch to explicitly using immutable Seq in both Scala 2.12 and 2.13, which means breaking source, binary and
120+
(possibly) semantic compatibility in 2.12:
77121

78122
~~~ scala
79-
import scala.collection.immutable.{ Seq => ISeq }
123+
import scala.collection.immutable
80124

81125
object FoodToGo {
82-
def orderFood(order: ISeq[Order]): ISeq[Food]
126+
def orderFood(order: immutable.Seq[Order]): immutable.Seq[Food]
83127
}
84128
~~~
85129

86-
Similarly, if you're making an end-user application, unifying to CSeq might be the easier and safer initial path especially for a larger and complex code base. Switching to ISeq will be a more advanced refactoring.
87-
88-
Note that in Scala 2.13 the sequence passed into as a varargs as `orderFood(xs: _*)` must also be immutable. This is because the sequence passed in as a varargs must conform to `scala.Seq` according to [SLS 6.6](https://www.scala-lang.org/files/archive/spec/2.12/06-expressions.html#function-applications). Thus, if your API exposes varargs it will be an unavoidable breaking change. This might affect Java interoperability.
89-
90-
### Masking scala.Seq
130+
### Shadowing scala.Seq and scala.IndexedSeq
91131

92-
To use the compiler to bad the use of plain `Seq`, you can declare your own `Seq` to mask `scala.Seq`.
132+
You maybe be interested in entirely banning plain `Seq` usage. You can use the compiler to do so by declaring
133+
your own package-level (and package private) `Seq` type which will mask `scala.Seq`.
93134

94135
~~~ scala
95136
package example
96137

97138
import scala.annotation.compileTimeOnly
98139

99140
/**
100-
* In Scala 2.13, scala.Seq moved from scala.collection.Seq to scala.collection.immutable.Seq.
101-
* In this code base, we'll require you to name ISeq or CSeq.
141+
* In Scala 2.13, `scala.Seq` changed from aliasing `scala.collection.Seq` to aliasing
142+
* `scala.collection.immutable.Seq`. In this code base usage of unqualified `Seq` is banned: use
143+
* `immutable.Seq` or `collection.Seq` instead.
102144
*
103-
* import scala.collection.{ Seq => CSeq }
104-
* import scala.collection.immutable.{ Seq => ISeq }
145+
* import scala.collection
146+
* import scala.collection.immutable
105147
*
106-
* This Seq trait is a dummy type to prevent the use of `Seq`.
148+
* This `Seq` trait is a dummy type to prevent the use of `Seq`.
107149
*/
108-
@compileTimeOnly("Use ISeq or CSeq") private[example] trait Seq[A1, F1[A2], A3]
150+
@compileTimeOnly("Use immutable.Seq or collection.Seq")
151+
private[example] trait Seq[A1]
109152

110153
/**
111-
* In Scala 2.13, scala.IndexedSeq moved from scala.collection.IndexedSeq to scala.collection.immutable.IndexedSeq.
112-
* In this code base, we'll require you to name ISeq or CSeq.
154+
* In Scala 2.13, `scala.IndexedSeq` changed from aliasing `scala.collection.IndexedSeq` to aliasing
155+
* `scala.collection.immutable.IndexedSeq`. In this code base usage of unqualified `IndexedSeq` is
156+
* banned: use `immutable.IndexedSeq` or `collection.IndexedSeq`.
113157
*
114-
* import scala.collection.{ IndexedSeq => CIndexedSeq }
115-
* import scala.collection.immutable.{ IndexedSeq => IIndexedSeq }
158+
* import scala.collection
159+
* import scala.collection.immutable
116160
*
117-
* This IndexedSeq trait is a dummy type to prevent the use of `IndexedSeq`.
161+
* This `IndexedSeq` trait is a dummy type to prevent the use of `IndexedSeq`.
118162
*/
119-
@compileTimeOnly("Use IIndexedSeq or CIndexedSeq") private[example] trait IndexedSeq[A1, F1[A2], A3]
163+
@compileTimeOnly("Use immutable.IndexedSeq or collection.IndexedSeq")
164+
private[example] trait IndexedSeq[A1]
120165
~~~
121166

122-
This might be useful during the transition period where you have to remember to import CSeq.
167+
This might be useful during the migration to catch usages of unqualified `Seq` and `IndexedSeq`.
123168

124169
## What are the breaking changes?
125170

0 commit comments

Comments
 (0)