Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

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

Merged
julienrf merged 26 commits intoscala:mainfromsideeffffect:patch-1
Jan 13, 2023

Conversation

@sideeffffect
Copy link
Contributor

@sideeffffectsideeffffect commentedDec 21, 2022
edited
Loading

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

julienrf and adpi2 reacted with rocket emoji
@sideeffffect
Copy link
ContributorAuthor

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.

@sjrd
Copy link
Member

https://docs.scala-lang.org/overviews/core/binary-compatibility-for-library-authors.html

sideeffffect reacted with heart emoji

@bjornregnell
Copy link
Contributor

I think it should be explained that the ability use pattern matching is lost if no unapply?

sideeffffect reacted with thumbs up emoji

@sjrd
Copy link
Member

IMO this whole section belongs the guide on compatibility for library authors. Application authors never need to care about this.

sideeffffect reacted with thumbs up emoji

@kubukoz
Copy link
Contributor

nitpick: PR title has a typo (patter -> pattern)

sideeffffect reacted with heart emoji


objectPerson:
...
defapply(name:String,age:Int,address:String)=newPerson(name, age, address)
Copy link
Contributor

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.

Copy link
ContributorAuthor

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 😅

Copy link
Contributor

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

@sideeffffectsideeffffect changed the titleDocument the patter for evolving case classes in a compatible mannerDocument the pattern for evolving case classes in a compatible mannerDec 21, 2022
@sideeffffect
Copy link
ContributorAuthor

Thanks for the feedback, I've reworked it.

Copy link
Member

@PhilippusPhilippus left a 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.
Copy link
Member

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.

Copy link
Contributor

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)
Copy link
Contributor

@bjornregnellbjornregnellDec 22, 2022
edited
Loading

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 as
case 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

Copy link
Contributor

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?

Copy link
Contributor

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...

@julienrfjulienrf self-assigned thisDec 22, 2022
@sideeffffectsideeffffect requested review fromPhilippus,bjornregnell andkubukoz and removed request forPhilippus andkubukozDecember 22, 2022 12:37
~~~ 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"))
Copy link
ContributorAuthor

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...

Copy link
Contributor

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.

Copy link
Contributor

@bjornregnellbjornregnellJan 3, 2023
edited
Loading

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
Copy link
ContributorAuthor

sideeffffect commentedJan 1, 2023
edited
Loading

There are parts of this pattern which are pretty repetitive and very regular:

  • add a new constructor, private to the companion object, with the original fields (necessary, unless we want to deal with MiMa)
  • add an apply factory method to the companion (necessary, unless we want to burden the class users with boilerplate)

Are these parts automatable using Macros? We would need the macro(s) to generate as many of these members as there areOptions in the case class (well, only theOptions that are on the right side of the case class constructor definition).

(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
Copy link
Member

sjrd commentedJan 2, 2023

Anything that needs to add members visible during typechecking cannot be done with macros.

@julienrf
Copy link
Contributor

@sideeffffect do you want to rework some parts or should we merge it as it is? (I am fine with both)

@sideeffffect
Copy link
ContributorAuthor

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
Copy link
Contributor

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
Copy link
Member

That would also be a binary breaking change which I assume mima would flag unless we special-case it in mima.

@julienrf
Copy link
Contributor

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
Copy link
Contributor

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?

Co-authored-by: Julien Richard-Foy <julien@richard-foy.fr>
Co-authored-by: Julien Richard-Foy <julien@richard-foy.fr>
@julienrf
Copy link
Contributor

I did another pass and made some final adjustments. I think the guide is ready to go now.

Screenshot 2023-01-13 at 09-51-29 Binary Compatibility for library authors

@bishabosha
Copy link
Member

This doesn't mention defining a new apply method in the companion (and keeping the old one) after adding the address field

@bjornregnell
Copy link
Contributor

Minor thing, fix word ordering to be "this makes the copy method private".

@julienrf
Copy link
Contributor

Thank you for the review,@bishabosha and@bjornregnell, I’ve addressed your comments ina818702

bjornregnell reacted with thumbs up emoji

Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment

Reviewers

@julienrfjulienrfjulienrf left review comments

@sjrdsjrdsjrd left review comments

@oderskyoderskyodersky left review comments

@PhilippusPhilippusPhilippus left review comments

+2 more reviewers

@kubukozkubukozkubukoz requested changes

@bjornregnellbjornregnellbjornregnell left review comments

Reviewers whose approvals may not affect merge requirements

Assignees

@sideeffffectsideeffffect

Labels

None yet

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

9 participants

@sideeffffect@sjrd@bjornregnell@kubukoz@julienrf@smarter@bishabosha@odersky@Philippus

[8]ページ先頭

©2009-2025 Movatter.jp