Skip to content
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

Revise existing case classes to prepare a better binary compatibility story #4480

Open
lefou opened this issue Feb 5, 2025 · 5 comments
Open

Comments

@lefou
Copy link
Member

lefou commented Feb 5, 2025

Following this document: Binary Compatibility for library authors, we should revise our existing API to prepare easier backward compatible changes.

For case classes, this means:

  • make the primary constructor private (this makes the copy method of the class private as well)
  • 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)
  • create a public constructor by defining an apply method in the companion object (it can use the private constructor)

Optional, if the case class is meant to be changed by the API use regularly, you should also add a withXXX-method. Otherwise, the apply-method should be already sufficient.

  • 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)
@lihaoyi
Copy link
Member

lihaoyi commented Feb 5, 2025

I think once the @unroll SIP lands in Scala 3, we should be able to use case classes directly without needing these workarounds, and @unroll new fields to maintain compatibility. We'll need to verify that it works, but if it does it should remove a lot of the manual work necessary to keep our case classes backwards compatible

@lefou
Copy link
Member Author

lefou commented Feb 5, 2025

Ok, that must be new. I didn't know @unroll will also handle copy and unapply.

@lihaoyi
Copy link
Member

lihaoyi commented Feb 5, 2025

Yeah it handles new, copy, apply, and unapply (the last one only in Scala 3). It basically makes adding fields "free" w.r.t. binary compatibility as long as (a) they are added on the right and (b) they have default values. So we can hopefully stop all the annoying forwarders and workarounds and just use vanilla case classes .

@lefou
Copy link
Member Author

lefou commented Feb 5, 2025

So, how do I prevent an unapply to appear in a case class in the first place. Do I have to add a @unroll in the first version already?

@lihaoyi
Copy link
Member

lihaoyi commented Feb 5, 2025

In Scala 3 unapply is defined as def unapply(value: Foo): Foo, so there isn't any binary compatibility concern with the unapply method when adding a case class, unlike in Scala 2 where it returned an Option[TupleN[...]]. The signature doesn't change when you add fields, and so bytecode compiled against an older version of the case class should continue to work as long as the new version has a superset of the old version's fields

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants