Coder Social home page Coder Social logo

data-class's Introduction

data-class

Build Status Maven Central

data-class allows to create classes almost like case-classes, but with no public unapply or copy methods, making it easier to add fields to them while maintaining binary compatiblity.

Usage

Setup

Add to your build.sbt,

libraryDependencies += "io.github.alexarchambault" %% "data-class" % "0.2.1"

The latest version is Maven Central.

The macro paradise plugin is needed up to scala 2.12, and the right compiler option needs to be used from 2.13 onwards:

lazy val isAtLeastScala213 = Def.setting {
  import Ordering.Implicits._
  CrossVersion.partialVersion(scalaVersion.value).exists(_ >= (2, 13))
}
libraryDependencies ++= {
  if (isAtLeastScala213.value) Nil
  else Seq(compilerPlugin("org.scalamacros" % "paradise" % "2.1.1" cross CrossVersion.full))
}
scalacOptions ++= {
  if (isAtLeastScala213.value) Seq("-Ymacro-annotations")
  else Nil
}

Lastly, if you know what you are doing, you can manage to have data-class be a compile-time only dependency.

API

Use a @data annotation instead of a case modifier, like

import dataclass.data

@data class Foo(n: Int, s: String)

This annotation adds a number of features, that can also be found in case classes:

  • sensible equals / hashCode / toString implementations,
  • apply methods in the companion object for easier creation,
  • extend the scala.Product trait (itself extending scala.Equal), and implement its methods,
  • extend the scala.Serializable trait.

It also adds things that differ from case classes:

  • add final modifier to the class,
  • for each field, add a corresponding with method (field count: Int generates a method withCount(count: Int) returning a new instance of the class with count updated).

Most notably, it does not generate copy or unapply methods, making binary compatibility much more tractable upon adding new fields (see below).

In the example above, the @data macro generates code like the following (modulo macro hygiene):

final class Foo(val n: Int, val s: String) extends Product with Serializable {

  def withN(n: Int) = new Foo(n = n, s = s)
  def withS(s: String) = new Foo(n = n, s = s)

  override def toString: String = {
    val b = new StringBuilder("Foo(")
    b.append(String.valueOf(n))
    b.append(", ")
    b.append(String.valueOf(s))
    b.append(")")
    b.toString
  }

  override def canEqual(obj: Any): Boolean = obj != null && obj.isInstanceOf[Foo]
  override def equals(obj: Any): Boolean = this.eq(obj.asInstanceOf[AnyRef]) || canEqual(obj) && {
    val other = obj.asInstanceOf[Foo]
    n == other.n && s == other.s
  })

  override def hashCode: Int = {
    var code = 17 + "Foo".##
    code = 37 * code + n.##
    code = 37 * code + s.##
    37 * code
  }

  private def tuple = (this.n, this.s)

  override def productArity: Int = 2
  override def productElement(n: Int): Any = n match {
    case 0 => this.n
    case 1 => this.s
    case n => throw new IndexOutOfBoundsException(n.toString)
  }
}

object Foo {
  def apply(n: Int, s: String): Foo = new Foo(n, s)
}

shapeless

By default, the classes annotated with @data now have a shape that shapeless.Generic handles:

import dataclass.data

@data class Foo(n: Int, d: Double)

import shapeless._
Generic[Foo] // works

Note that with shapeless 2.3.3 and prior versions, Generic derivation may fail if the body of the @data class contains vals or lazy vals, see shapeless issue #934.

Adding fields

In order to retain binary compatibility when adding fields, one should:

  • annotate the first added field with dataclass.since,
  • provide default values for the added fields, like
import dataclass._

@data class Foo(n: Int, d: Double, @since s: String = "", b: Boolean = false)

The @since annotation makes the @data macro generate apply methods compatible with those without the new fields.

The example above generates the following apply methods in the companion object of Foo:

object Foo {
  def apply(n: Int, d: Double): Foo = new Foo(n, d, "", false)
  def apply(n: Int, d: Double, s: String, b: Boolean) = new Foo(n, d, s, b)
}

The @since annotation accepts an optional string argument - a version can be passed for example - and it can be used multiple times, like

import dataclass._

@data class Foo(
  n: Int,
  d: Double,
  @since("1.1")
  s: String = "",
  b: Boolean = false,
  @since("1.2")
  count: Option[Int] = None,
  info: Option[String] = None
)

This generates the following apply methods in the companion object of Foo:

object Foo {
  def apply(n: Int, d: Double): Foo = new Foo(n, d, "", false, None, None)
  def apply(n: Int, d: Double, s: String, b: Boolean) = new Foo(n, d, s, b, None, None)
  def apply(n: Int, d: Double, s: String, b: Boolean, count: Option[Int], info: Option[String]) = new Foo(n, d, s, b, count, info)
}

Related work

  • contraband relies on code generation from JSON or a custom schema language to generate classes that can be evolved in a binary compatible way
  • stalagmite generates case classes with custom features via some macros (but doesn't aim at helping maintaining binary compatibility)

data-class's People

Contributors

alexarchambault avatar dependabot[bot] avatar jtjeferreira avatar oyvindberg avatar regadas avatar scala-steward avatar sethtisue avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

data-class's Issues

Add back unapply and copy, but marked as private

It would be safe to still generate the unapply and copy methods, but with a private modifier. They could be used / helpful from the class body, but wouldn't belong to the class public API, so couldn't break binary compatibility.

Is scala3 support possible for this library?

I really like what this library offers, but I'm wondering if it's going to be possible to implement any of the behaviors at all for scala 3, given the restrictions on macros.

Are there plans or a roadmap, or is it intractable without a lot of changes?

Type parameters

Check that those are fine, and mention the shortcomings around them because of the use of with methods instead of copy.

Using since on field type changes

So I'm not sure how possible this is but I'd love to use since-like functionality at versioning case class fields when the type changes. My gut tells me this is a much more complicated issue than what data-class is trying to solve, but I figured there might be a recommendation or an idea from those maintaining this lib on how to approach that issue.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.