You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: _overviews/tutorials/binary-compatibility-for-library-authors.md
+43-14Lines changed: 43 additions & 14 deletions
Original file line number
Diff line number
Diff line change
@@ -175,52 +175,81 @@ Again, we recommend using MiMa to double-check that you have not broken binary c
175
175
176
176
### Changing a case class definition in a backwards-compatible manner
177
177
178
-
Sometimes, it is desirable to change the definition of a case class (adding and/or removing fields) while still staying backwards-compatible with the existing usage of the case class, i.e. not breaking the so-called _binary compatibility_.
178
+
Sometimes, it is desirable to change the definition of a case class (adding and/or removing fields) while still staying backwards-compatible with the existing usage of the case class, i.e. not breaking the so-called _binary compatibility_. The first question you should ask yourself is “do you need a _case_ class?” (as opposed to a regular class, which can be easier to evolve in a binary compatible way). A good reason for using a case class is when you need a structural implementation of `equals` and `hashCode`.
179
179
180
180
To achieve that, follow this pattern:
181
-
* make the constructor private (this makes private the `copy` method of the class)
182
-
* define a private `unapply` function in the companion object (note that by doing that the case class loses the ability to be used in an extractor pattern match)
183
-
* for all the fields, define `withXXX` methods on the case class that create a new instance with the respective field changed
184
-
*define custom `apply`factory method(s) in the companion object (these can use the private constructor)
181
+
* make the primary constructor private (this makes private the `copy` method of the class)
182
+
* 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)
183
+
* 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)
184
+
*create a public constructor by defining an `apply` method in the companion object (it can use the private constructor)
185
185
186
186
Example:
187
187
188
188
{% tabs case_class_compat_1 %}
189
189
{% tab 'Scala 3 Only' %}
190
190
191
191
```scala
192
+
// Mark the primary constructor as private
192
193
caseclassPersonprivate (name: String, age: Int):
194
+
// Create withXxx methods for every field, implemented by using the copy method
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:
215
+
216
+
~~~scala
217
+
alice match
218
+
caseperson: Person=> person.name
219
+
~~~
220
+
Later in time, you can amend the original case class definition to, say, add an optional `address` field. You
221
+
* add a new field `address` and a custom `withAddress` method,
222
+
* add the former constructor signature as a secondary constructor, private to the companion object. This step is necessary because the public `apply` method in the companion object calls the former constructor, which was effectively public in the bytecode produced by the compiler.
> Note that an alternative solution, instead of adding back the previous constructor signatures as secondary constructors, consists of adding a [MiMa filter](https://github.com/lightbend/mima#filtering-binary-incompatibilities) to simply ignore the problem. Even though the constructors are effectively public in the bytecode, they can’t be called from Scala programs (but they could be called by Java programs). In an sbt build definition you would add the following setting:
>Otherwise, MiMa would fail with an error like “method this(java.lang.String,Int)Unit in classPerson does not have a correspondent in current version”.
222
242
The original users can use the caseclass`Person`asbefore, all the methods that existed before are present unmodified after this change, thus the compatibility with the existing usage is maintained.
223
243
244
+
Thenew field `address` can be used asfollows:
245
+
246
+
~~~ scala
247
+
// The public constructor sets the address to None by default.
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.
0 commit comments