-
Notifications
You must be signed in to change notification settings - Fork 1k
Document the pattern for evolving case classes in a compatible manner #2662
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 8 commits
e54f7f1
5c8df68
0361062
3b1fc57
6b8b157
75b5937
1118fb3
64776be
b583d46
a6d4fc0
85bf8e7
359219e
25af00f
b328958
caaa532
9afdd84
7806dbd
a764a9c
a52d7d6
7668763
293a0bd
5c1d9b3
4f548e9
f3e6c15
3ab5b4b
a818702
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -173,6 +173,54 @@ You can find detailed explanations, runnable examples and tips to maintain binar | |
|
||
Again, we recommend using MiMa to double-check that you have not broken binary compatibility after making changes. | ||
|
||
### Changing case class definition in a compatible manner | ||
|
||
Sometimes it is desirable to be able to change the definition of a case class (adding and/or removing fields) while still staying compatible with the users of the case class, i.e. not breaking the so called _binary compatibility_. | ||
sideeffffect marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
To achieve that, follow this pattern: | ||
* make the constructor private | ||
sideeffffect marked this conversation as resolved.
Show resolved
Hide resolved
|
||
* define a private `unapply` function in the companion object (note that by doing that the case class loses the ability to be used in a pattern match) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Well, usage of instances in patterns is possible e.g. typed patterns such as So The terminology of all the 15, or so, different kinds of patterns is available here: https://scala-lang.org/files/archive/spec/2.13/08-pattern-matching.html There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Edit: I guess extractor patterns could be made to work if the library author provides custom extractors? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Anyway, I think it would be good to explain the implications on pattern matching in more detail... |
||
* define `withXXX` methods on the case class that create a new instance with the respective field changed | ||
sideeffffect marked this conversation as resolved.
Show resolved
Hide resolved
|
||
* define custom `apply` factory method(s) in the companion object (these can use the private constructor) | ||
|
||
Example: | ||
|
||
{% tabs case_class_compat_1 %} | ||
{% tab 'Scala 3 Only' %} | ||
|
||
```scala | ||
case class Person private (name: String, age: Int): | ||
def withName(name: String) = copy(name = name) | ||
sideeffffect marked this conversation as resolved.
Show resolved
Hide resolved
|
||
def withAge(age: Int) = copy(age = age) | ||
sideeffffect marked this conversation as resolved.
Show resolved
Hide resolved
|
||
object Person: | ||
def apply(name: String, age: Int) = new Person(name, age) | ||
private def unapply(p: Person) = p | ||
``` | ||
{% endtab %} | ||
{% endtabs %} | ||
|
||
sideeffffect marked this conversation as resolved.
Show resolved
Hide resolved
|
||
Later in time, you can ammend the original case class definition. You | ||
sideeffffect marked this conversation as resolved.
Show resolved
Hide resolved
|
||
* add a new field `address`, | ||
* add a custom `withAddress` method and | ||
* add an `apply` factory method to the companion. | ||
|
||
{% tabs case_class_compat_2 %} | ||
{% tab 'Scala 3 Only' %} | ||
```scala | ||
case class Person private (name: String, age: Int, address: String = ""): | ||
sideeffffect marked this conversation as resolved.
Show resolved
Hide resolved
|
||
... | ||
def withAddress(address: String) = copy(address = address) | ||
object Person: | ||
... | ||
def apply(name: String, age: Int, address: String) = new Person(name, age, address) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think you should also say that the previous - def apply(name: String, age: Int) = new Person(name, age)
+ def apply(name: String, age: Int) = new Person(name, age, None) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It doesn't, because we manually re-introduce the constructor with the original fields. |
||
``` | ||
julienrf marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{% endtab %} | ||
{% endtabs %} | ||
|
||
sideeffffect marked this conversation as resolved.
Show resolved
Hide resolved
|
||
The original users can use the case class `Person` as before, all the methods that existed before are present unmodified after this change, thus the compatibility with the users is maintained. | ||
sideeffffect marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
sideeffffect marked this conversation as resolved.
Show resolved
Hide resolved
|
||
A regular case class not following this pattern would break its users, because by adding a new field some methods (which could be used by somebody else) change, for example `copy` or the constructor itself. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "would break its users" sounds a bit strange, don't have an alternative yet though. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "would brake usage" or "would break compatible usage" or similar perhaps? |
||
|
||
## Versioning Scheme - Communicating compatibility breakages | ||
|
||
Library authors use versioning schemes to communicate compatibility guarantees between library releases to their users. Versioning schemes like [Semantic Versioning](https://semver.org/) (SemVer) allow | ||
|
Uh oh!
There was an error while loading. Please reload this page.