Skip to content

Commit 29d1c71

Browse files
committed
Tweak Seq migration guide
1 parent cc472a1 commit 29d1c71

File tree

1 file changed

+83
-36
lines changed

1 file changed

+83
-36
lines changed

_overviews/core/collections-migration-213.md

Lines changed: 83 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -31,95 +31,142 @@ 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+
This 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, with a method like `def orderFood(order: Seq[Order]): Seq[Food]`, previously
46+
the method user would have been able to pass in an array of `Order`, but that won't work for 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). We recommend, in order of preference, doing such a replacement with one of:
64+
65+
- `import scala.{ collection => sc }` and using `sc.Seq`
66+
- `import scala.collection.{ Seq => CSeq }` and using `CSeq`
67+
- replacing `Seq[...]` usage with the fully-qualified `scala.collection.Seq[...]` (e.g. in code gen or macros)
68+
- `import scala.collection.Seq`, which shadows `scala.Seq` and is a 1-line change, but causes name confusion
69+
70+
As an example, it would look something like this:
4471

4572
~~~ scala
46-
import scala.collection.Seq
73+
import scala.{ collection => sc }
4774

4875
object FoodToGo {
49-
def orderFood(order: Seq[Order]): Seq[Food]
76+
def orderFood(order: sc.Seq[Order]): sc.Seq[Food]
5077
}
5178
~~~
5279

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

5583
~~~ scala
5684
val food: Seq[Food] = FoodToGo.orderFood(order) // won't compile
5785
~~~
5886

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.
87+
The simplest workaround is to ask your users to call `.toSeq` on the result which will return an immutable Seq,
88+
and only copy data if the sequence wasn't immutable:
6089

6190
~~~ scala
6291
val food: Seq[Food] = FoodToGo.orderFood(order).toSeq // add .toSeq
6392
~~~
6493

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

67101
~~~ scala
68-
import scala.collection.{ Seq => CSeq }
69-
import scala.collection.immutable.{ Seq => ISeq }
102+
import scala.{ collection => sc }
103+
import scala.collection.{ immutable => sci }
70104

71105
object FoodToGo {
72-
def orderFood(order: CSeq[Order]): ISeq[Food]
106+
def orderFood(order: sc.Seq[Order]): sci.Seq[Food]
73107
}
74108
~~~
75109

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.
110+
### Option 3: use immutable sequences
111+
112+
The third migration strategy is to change your API to use immutable sequences for both parameter and result
113+
types. When cross-building your library for Scala 2.12 and 2.13 this could either mean:
114+
115+
- continuing to use `scala.Seq` which means it stays source and binary-compatible in 2.12, but would have to
116+
have immutable sequence semantics (but that might already be the case).
117+
- switch to explicitly using immutable Seq in both Scala 2.12 and 2.13, which means breaking source, binary and
118+
semantic compatibility in 2.12:
77119

78120
~~~ scala
79-
import scala.collection.immutable.{ Seq => ISeq }
121+
import scala.collection.{ immutable => sci }
80122

81123
object FoodToGo {
82-
def orderFood(order: ISeq[Order]): ISeq[Food]
124+
def orderFood(order: sci.Seq[Order]): sci.Seq[Food]
83125
}
84126
~~~
85127

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
128+
### Shadowing scala.Seq and scala.IndexedSeq
91129

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

94133
~~~ scala
95134
package example
96135

97136
import scala.annotation.compileTimeOnly
98137

99138
/**
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.
139+
* In Scala 2.13, `scala.Seq` changed from aliasing `scala.collection.Seq` to aliasing
140+
* `scala.collection.immutable.Seq`. In this code base usage of unqualified `Seq` is banned: use
141+
* `sc.Seq` or `sci.Seq` instead.
102142
*
103-
* import scala.collection.{ Seq => CSeq }
104-
* import scala.collection.immutable.{ Seq => ISeq }
143+
* {{{
144+
* import scala.{ collection => sc }
145+
* import scala.collection.{ immutable => sci }
146+
* }}}
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 sci.Seq or sc.Seq")
151+
private[example] trait Seq[A1, F1[A2], A3]
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 `sc.IndexedSeq` or `sci.IndexedSeq`.
113157
*
114-
* import scala.collection.{ IndexedSeq => CIndexedSeq }
115-
* import scala.collection.immutable.{ IndexedSeq => IIndexedSeq }
158+
* {{{
159+
* import scala.{ collection => sc }
160+
* import scala.collection.{ immutable => sci }
161+
* }}}
116162
*
117-
* This IndexedSeq trait is a dummy type to prevent the use of `IndexedSeq`.
163+
* This `IndexedSeq` trait is a dummy type to prevent the use of `IndexedSeq`.
118164
*/
119-
@compileTimeOnly("Use IIndexedSeq or CIndexedSeq") private[example] trait IndexedSeq[A1, F1[A2], A3]
165+
@compileTimeOnly("Use sci.IndexedSeq or sc.IndexedSeq")
166+
private[example] trait IndexedSeq[A1, F1[A2], A3]
120167
~~~
121168

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

124171
## What are the breaking changes?
125172

0 commit comments

Comments
 (0)