Skip to content

Commit 864d1a5

Browse files
authored
Add Scala 2 version of case classes that can evolve (#2760)
The same approach as with Scala 3 also works with Scala 2 if you add the `-Xsource:3` compiler option.
1 parent 1eb3108 commit 864d1a5

File tree

1 file changed

+50
-8
lines changed

1 file changed

+50
-8
lines changed

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

+50-8
Original file line numberDiff line numberDiff line change
@@ -182,21 +182,39 @@ To achieve that, follow this pattern:
182182
* define a private `unapply` function in the companion object (note that by doing that the case class loses the ability to be used as an extractor in match expressions)
183183
* for all the fields, define `withXXX` methods on the case class that create a new instance with the respective field changed (you can use the private `copy` method to implement them)
184184
* create a public constructor by defining an `apply` method in the companion object (it can use the private constructor)
185+
* in Scala 2, you have to add the compiler option `-Xsource:3`
185186

186187
Example:
187188

188-
{% tabs case_class_compat_1 %}
189-
{% tab 'Scala 3 Only' %}
189+
{% tabs case_class_compat_1 class=tabs-scala-version %}
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 (private) copy method
195+
def withName(name: String): Person = copy(name = name)
196+
def withAge(age: Int): Person = copy(age = age)
197+
}
198+
199+
object Person {
200+
// Create a public constructor (which uses the private primary constructor)
201+
def apply(name: String, age: Int) = new Person(name, age)
202+
// Make the extractor private
203+
private def unapply(p: Person): Some[Person] = Some(p)
204+
}
205+
~~~
206+
{% endtab %}
207+
{% tab 'Scala 3' %}
190208

191209
```scala
192210
// Mark the primary constructor as private
193211
case class Person private (name: String, age: Int):
194-
// Create withXxx methods for every field, implemented by using the copy method
212+
// Create withXxx methods for every field, implemented by using the (private) copy method
195213
def withName(name: String): Person = copy(name = name)
196214
def withAge(age: Int): Person = copy(age = age)
197215

198216
object Person:
199-
// Create a public constructor (which uses the primary constructor)
217+
// Create a public constructor (which uses the private primary constructor)
200218
def apply(name: String, age: Int): Person = new Person(name, age)
201219
// Make the extractor private
202220
private def unapply(p: Person) = p
@@ -239,8 +257,21 @@ Later in time, you can amend the original case class definition to, say, add an
239257
* update the public `apply` method in the companion object to initialize all the fields,
240258
* tell MiMa to [ignore](https://github.com/lightbend/mima#filtering-binary-incompatibilities) changes to the class constructor. This step is necessary because MiMa does not yet ignore changes in private class constructor signatures (see [#738](https://github.com/lightbend/mima/issues/738)).
241259

242-
{% tabs case_class_compat_4 %}
243-
{% tab 'Scala 3 Only' %}
260+
{% tabs case_class_compat_4 class=tabs-scala-version %}
261+
{% tab 'Scala 2' %}
262+
~~~ scala
263+
case class Person private (name: String, age: Int, address: Option[String]) {
264+
...
265+
def withAddress(address: Option[String]) = copy(address = address)
266+
}
267+
268+
object Person {
269+
// Update the public constructor to also initialize the address field
270+
def apply(name: String, age: Int): Person = new Person(name, age, None)
271+
}
272+
~~~
273+
{% endtab %}
274+
{% tab 'Scala 3' %}
244275
```scala
245276
case class Person private (name: String, age: Int, address: Option[String]):
246277
...
@@ -295,8 +326,19 @@ A regular case class not following this pattern would break its usage, because b
295326
Optionally, you can also add overloads of the `apply` method in the companion object to initialize more fields
296327
in one call. In our example, we can add an overload that also initializes the `address` field:
297328
298-
{% tabs case_class_compat_7 %}
299-
{% tab 'Scala 3 Only' %}
329+
{% tabs case_class_compat_7 class=tabs-scala-version %}
330+
{% tab 'Scala 2' %}
331+
~~~ scala
332+
object Person {
333+
// Original public constructor
334+
def apply(name: String, age: Int): Person = new Person(name, age, None)
335+
// Additional constructor that also sets the address
336+
def apply(name: String, age: Int, address: String): Person =
337+
new Person(name, age, Some(address))
338+
}
339+
~~~
340+
{% endtab %}
341+
{% tab 'Scala 3' %}
300342
~~~ scala
301343
object Person:
302344
// Original public constructor

0 commit comments

Comments
 (0)