Skip to content

Commit 4f548e9

Browse files
Apply suggestions from code review
Co-authored-by: Julien Richard-Foy <julien@richard-foy.fr>
1 parent 5c1d9b3 commit 4f548e9

File tree

1 file changed

+56
-9
lines changed

1 file changed

+56
-9
lines changed

_overviews/tutorials/binary-compatibility-for-library-authors.md

Lines changed: 56 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -185,10 +185,27 @@ To achieve that, follow this pattern:
185185

186186
Example:
187187

188-
{% tabs case_class_compat_1 %}
189-
{% tab 'Scala 3 Only' %}
188+
{% tabs case_class_compat_1 class=tabs-scala-version %}
190189

191-
```scala
190+
{% tab 'Scala 2' %}
191+
~~~ scala
192+
// Mark the primary constructor as private
193+
case class Person private (name: String, age: Int) {
194+
// Create withXxx methods for every field, implemented by using the copy method
195+
def withName(name: String): Person = copy(name = name)
196+
def withAge(age: Int): Person = copy(age = age)
197+
}
198+
object Person {
199+
// Create a public constructor (which uses the primary constructor)
200+
def apply(name: String, age: Int) = new Person(name, age)
201+
// Make the extractor private
202+
private def unapply(p: Person): Some[Person] = Some(p)
203+
}
204+
~~~
205+
{% endtab %}
206+
207+
{% tab 'Scala 3' %}
208+
~~~ scala
192209
// Mark the primary constructor as private
193210
case class Person private (name: String, age: Int):
194211
// Create withXxx methods for every field, implemented by using the copy method
@@ -198,38 +215,64 @@ object Person:
198215
// Create a public constructor (which uses the primary constructor)
199216
def apply(name: String, age: Int) = new Person(name, age)
200217
// Make the extractor private
201-
private def unapply(p: Person) = p
202-
```
218+
private def unapply(p: Person): Person = p
219+
~~~
203220
{% endtab %}
221+
204222
{% endtabs %}
205223
This class can be published in a library and used as follows:
206224

225+
{% tabs case_class_compat_1_b %}
226+
{% tab 'Scala 2 and 3' %}
207227
~~~ scala
208228
// Create a new instance
209229
val alice = Person("Alice", 42)
210230
// Transform an instance
211231
println(alice.withAge(alice.age + 1)) // Person(Alice, 43)
212232
~~~
233+
{% endtab %}
234+
{% endtabs %}
213235

214236
If you try to use `Person` as an extractor in a match expression, it will fail with a message like “method unapply cannot be accessed as a member of Person.type”. Instead, you can use it as a typed pattern:
215237

238+
{% tabs case_class_compat_1_c class=tabs-scala-version %}
239+
{% tab 'Scala 2' %}
240+
~~~ scala
241+
alice match {
242+
case person: Person => person.name
243+
}
244+
~~~
245+
{% endtab %}
246+
{% tab 'Scala 3' %}
216247
~~~ scala
217248
alice match
218249
case person: Person => person.name
219250
~~~
251+
{% endtab %}
252+
{% endtabs %}
220253
Later in time, you can amend the original case class definition to, say, add an optional `address` field. You
221254
* add a new field `address` and a custom `withAddress` method,
222255
* add the former constructor signature as a secondary constructor, private to the companion object. This step is necessary because the compilers currently emit the private constructors as public constructors in the bytecode (see [#12711](https://github.com/scala/bug/issues/12711) and [#16651](https://github.com/lampepfl/dotty/issues/16651)).
223256

224-
{% tabs case_class_compat_2 %}
225-
{% tab 'Scala 3 Only' %}
226-
```scala
257+
{% tabs case_class_compat_2 class=tabs-scala-version %}
258+
{% tab 'Scala 2' %}
259+
~~~ scala
260+
case class Person private (name: String, age: Int, address: Option[String]) {
261+
...
262+
// Add back the former primary constructor signature
263+
private[Person] def this(name: String, age: Int) = this(name, age, None)
264+
def withAddress(address: Option[String]) = copy(address = address)
265+
}
266+
~~~
267+
{% endtab %}
268+
{% tab 'Scala 3' %}
269+
~~~ scala
227270
case class Person private (name: String, age: Int, address: Option[String]):
228271
...
229272
// Add back the former primary constructor signature
230273
private[Person] def this(name: String, age: Int) = this(name, age, None)
231274
def withAddress(address: Option[String]) = copy(address = address)
232-
```
275+
~~~
233276
{% endtab %}
234277
{% endtabs %}
235278

@@ -243,12 +286,16 @@ The original users can use the case class `Person` as before, all the methods th
243286
244287
The new field `address` can be used as follows:
245288
289+
{% tabs case_class_compat_3 %}
290+
{% tab 'Scala 2 and 3' %}
246291
~~~ scala
247292
// The public constructor sets the address to None by default.
248293
// To set the address, we call withAddress:
249294
val bob = Person("Bob", 21).withAddress(Some("Atlantic ocean"))
250295
println(bob.address)
251296
~~~
297+
{% endtab %}
298+
{% endtabs %}
252299
253300
A regular case class not following this pattern would break its usage, because by adding a new field changes some methods (which could be used by somebody else), for example `copy` or the constructor itself.
254301

0 commit comments

Comments
 (0)