This small library / compiler plugin allows to automatically generate updated
methods for case classes in Scala.
The goal is to have a means of conveniently manipulating single fields of case class instances in an immutable fashion (i.e., returning a new object with the changed value). If the notion of "manipulation" were limited to mere assignments, then the copy method accomplishes just this. Take the following example:
case class Person(name: String, age: Int)
If we want to change the name of var p = Person("Jane Doe", 42)
, we can write p = p.copy(name = "John Doe")
. For realizing his birthday, we could write p = p.copy(age = p.age + 1)
, but here we need to repeat both p
and age
. Furthermore, this can quickly get cumbersome if p
were not a plain var
or val
, but a more complex expression.
The idea of the updated
method is to provide not a new value, but a value transformer (i.e., a function) to change the current value. We could then rewrite the "birthday" statement as p = p.updated(age = _ + 1)
. An example realization can be found in this Gist. However, with this library/plugin, this manual implementation is no longer necessary.
Once the compiler plugin is enabled, updated
methods will be automatically generated for all case classes in the respective compilation units (exceptions see below). No further user interaction is required.
To enable reuse of the generation logic that was originally written for a macro context only, the compiler plugin runs at a very early phase (before any resolution is performed). This makes it impossible to determine if a method with the same signature as the updated
method to be generated already exists in a case class: types in the signature might be unresolved aliases.
If the updated
method were added anyway, this would result in a compilation error; the consequence being that either existing code has to be changed, or the plugin has to be disabled for the whole project. Since both options are undesirable, the plugin takes a conservative approach: if a case class has a method that "looks like" it would conflict with the method to be generated, no updated
method is autogenerated (this can only be enforced using the
macro annotation approach). This is the case if it has a method with the same name and the same number of arguments as the generated method would have.
Add the following line to your build configuration (in build.sbt
or Build.scala
):
addCompilerPlugin("com.github.misberner.scalamacros" % "updated" % "0.0.3" cross CrossVersion.binary)
Add the following snippet to the configuration
section of scala-maven-plugin
in your POM:
<compilerPlugins>
<compilerPlugin>
<groupId>com.github.misberner.scalamacros</groupId>
<artifactId>updated_<YOUR.SCALA.VERSION></artifactId>
<version>0.0.3</version>
</compilerPlugin>
</compilerPlugins>
In order to allow a more fine-grained control over which case classes should be extended with an updated
method, the functionality is also available as a macro annotation (note: macro annotations require Macro Paradise; instructions for setting this up with either SBT or Maven can be found here). The usage is still very simple: just annotate the case class that should be extended with an updated
method with the com.github.misberner.scalamacros.updated.generateUpdated
annotation:
import com.github.misberner.scalamacros.updated.generateUpdated
@generateUpdated
case class MyClass(intAttr: Int, listAttr: List[String], mapAttr: Map[String,Int]) {
// ...
}
After enabling Macro Paradise (see above), add the following line to your build configuration (in build.sbt
or Build.scala
):
libraryDependencies += "com.github.misberner.scalamacros" % "updated" % "0.0.3" % provided cross CrossVersion.binary
After enabling Macro Paradise (see above), add the following snippet to the dependencies
section in your POM:
<dependency>
<groupId>com.github.misberner.scalamacros</groupId>
<artifactId>updated_<YOUR.SCALA.VERSION></artifactId>
<version>0.0.3</version>
<scope>provided</scope> <!-- non-transitive -->
</dependency>
Realizing this library would not have been possible without Adam Warski's excellent blog post/example on automatic generation of delegate