- Notifications
You must be signed in to change notification settings - Fork1k
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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
Uh oh!
There was an error while loading.Please reload this page.
Conversation
sideeffffect commentedDec 21, 2022
Also, do we have some part(s) of the documentation where we discuss the concept of "binary compatibility"? It would be desirable from them to this newly created section. |
bjornregnell commentedDec 21, 2022
I think it should be explained that the ability use pattern matching is lost if no unapply? |
sjrd commentedDec 21, 2022
IMO this whole section belongs the guide on compatibility for library authors. Application authors never need to care about this. |
kubukoz commentedDec 21, 2022
nitpick: PR title has a typo (patter -> pattern) |
| objectPerson: | ||
| ... | ||
| defapply(name:String,age:Int,address:String)=newPerson(name, age, address) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
this isn't a binary compatible change, right? You're removing the methodapply that takes 2 parameters.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Nope, you're not removing anything. You're only adding new stuff. That's the point.
Unless I missed something 😅
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
ahh sorry, I assumed this was replacing theapply present before. Of course you're right
sideeffffect commentedDec 21, 2022
Thanks for the feedback, I've reworked it. |
Uh oh!
There was an error while loading.Please reload this page.
Co-authored-by: Jakub Kozłowski <kubukoz@gmail.com>
Philippus left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Maybe add a note on the "deprecated" annotation?
| 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. | ||
| 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 comment
The 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 comment
The reason will be displayed to describe this comment to others.Learn more.
"would brake usage" or "would break compatible usage" or similar perhaps?
| To achieve that, follow this pattern: | ||
| * make the constructor private | ||
| * 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) |
bjornregnellDec 22, 2022 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The 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 ascase p: Person =>
so it is "only"constructor patterns andextractor patterns that are not possible, if I'm correct?
Socase Person(n,a) => would not work asunapply is private, but other kind of patterns work. So I think this should be clarified as the reader might not know about all the unapply mechanics under the hood...
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 comment
The 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 comment
The 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...
| ~~~ scala | ||
| // The public constructor sets the address to None by default. | ||
| // To set the address, we call withAddress: | ||
| valbob=Person("Bob",21).withAddress(Some("Atlantic ocean")) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
@julienrf Thanks for you suggestions. I've applied them all. But I think this part is worth a discussion.
Shouldn't we recommend readers to also include theapply methods in the companion object? Because
Person("Bob",21).withAddress(Some("Atlantic ocean"))
looks a bit clumsy. This looks much better
Person("Bob",21,"Atlantic ocean")
I know, we save on some boilerplate in the class definition. But we're just pushing the boilerplate onto the users of the class. Is that the right choice to make?
Ideally we'd have our cake and eat it too, but I don't know if it's possible without a new language level feature...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
I don’t think the pattern I suggested is clumsy. It is similar to the “builder” pattern we often see around.
Anyway, there are probably several possible variations. The one I suggest here provides a public-facing API that (I think) is simpler because it contains fewer overloads of theapply methods.
bjornregnellJan 3, 2023 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Well, documentation space is not scarce :) so I suggest to explain both possibilities here: withX or overloaded apply constructors. It's up to the library author. And if the library user is "refused" a more concise constructor then it's just a self-made extension away...
sideeffffect commentedJan 1, 2023 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
There are parts of this pattern which are pretty repetitive and very regular:
Are these parts automatable using Macros? We would need the macro(s) to generate as many of these members as there are (Btw good things about this patten is that it seems to work with e.g. Circe for JSON codecshttps://scastie.scala-lang.org/huP4tCstQBufYKFwXUFs4Q ) |
sjrd commentedJan 2, 2023
Anything that needs to add members visible during typechecking cannot be done with macros. |
julienrf commentedJan 6, 2023
@sideeffffect do you want to rework some parts or should we merge it as it is? (I am fine with both) |
sideeffffect commentedJan 6, 2023
I thought we want to first finish theMiMa vs "private" primary constructors debate. https://contributors.scala-lang.org/t/private-primary-constructor-vs-mima/6050/5 But I'm not against merging this documentation before. What do you guys think? |
julienrf commentedJan 6, 2023
That’s a good point. @smarter, what do you think about Sébastien’s suggestion of emitting the constructors as “package private” constructors? If we can do that, then I guess we would not need to add the MiMa exception anymore. |
smarter commentedJan 6, 2023
That would also be a binary breaking change which I assume mima would flag unless we special-case it in mima. |
julienrf commentedJan 9, 2023
I assume MiMa already treats package private definitions like private definitions. If this is not the case, that should be fixed indeed. But then MiMa would not flag changes to the class constructors if they are emitted as package private. Of course, code compiled with a new version of the compiler (emitting private case class constructors as package private constructors at the bytecode level) would be binary incompatible with code compiled with a previous compiler, so library authors would have to add a MiMa exception for that. But at least, the emitted bytecode would be more correct because it would become impossible to call the constructor from Java code. |
julienrf commentedJan 10, 2023
I createdscala/bug#12711 andscala/scala3#16651 to track the issue with private constructors that are still public. We could add a note about that in the documentation, what do you think? |
Uh oh!
There was an error while loading.Please reload this page.
Co-authored-by: Julien Richard-Foy <julien@richard-foy.fr>
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
Co-authored-by: Julien Richard-Foy <julien@richard-foy.fr>
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
This reverts commit4f548e9.
julienrf commentedJan 13, 2023
bishabosha commentedJan 13, 2023
This doesn't mention defining a new apply method in the companion (and keeping the old one) after adding the address field |
bjornregnell commentedJan 13, 2023
Minor thing, fix word ordering to be "this makes the copy method private". |
julienrf commentedJan 13, 2023
Thank you for the review,@bishabosha and@bjornregnell, I’ve addressed your comments ina818702 |

Uh oh!
There was an error while loading.Please reload this page.
Documenting the patter coming fromSIP-50 - Struct Classesscala/improvement-proposals#50 (comment)
English is not my first language, so I'd be very grateful for suggestions on how to improve the wording, etc.
Rendered:
https://github.com/sideeffffect/docs.scala-lang/blob/patch-1/_overviews/tutorials/binary-compatibility-for-library-authors.md#evolving-code-without-breaking-binary-compatibility