• Stars
    star
    52
  • Rank 560,474 (Top 12 %)
  • Language
    Scala
  • License
    GNU Lesser Genera...
  • Created over 6 years ago
  • Updated 2 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Principled, fast, typeclass derivation.

scalaz-deriving makes it easy to derive typeclass instances for your data types. The benefits are:

  • much faster compiles
  • simpler implicit rules (less time fighting the compiler)
  • easy to write derivation logic for your own typeclasses

There are two independent and complementary parts to this library:

  • a @deriving annotation to easily add implicit typeclass instances to companion objects. This macro is compatible with magnolia, shapeless generic derivation, and hand-rolled derivers (e.g. play-json). @deriving does not depend on scalaz.
  • scalaz-deriving, a principled way for typeclass authors to define typeclass derivations, plus derivations for some scalaz-core typeclasses (e.g. Equal, Monoid).

NOTE: Bug reports and feature requests can be filed in the "Pull Request" tab above. Issues filed in the issue tracker are considered documentary at best. This is maintained but unsupported free software; tickets will be resolved only if someone writes the code. (That someone can be you!)

General questions can be asked in https://gitter.im/scalaz/scalaz 💖 See the CODE_OF_CONDUCT for more.

Table of Contents

Compiler Plugin

@deriving

The @deriving annotation simplifies the semi-auto pattern, whereby implicit evidence is explicitly added to data type companions, rather than being inferred at the point of use (known as full-auto). In short,

@scalaz.annotation.deriving(Encoder, Decoder)
case class Bar(s: String, b: Boolean)

expands to

object Bar {
  implicit val _deriving_encoder: Encoder[Bar] = scalaz.Deriving.gen[Encoder, Bar]
  implicit val _deriving_decoder: Decoder[Bar] = scalaz.Deriving.gen[Decoder, Bar]
}

The annotation is compatible with the @newtype annotation by estatico

@newtype
@deriving(Encoder, Decoder)
case class Bar(s: String)

expanding into

@newtype
case class Bar(s: String)
object Bar {
  implicit val _deriving_encoder: Encoder[Bar] = deriving
  implicit val _deriving_decoder: Decoder[Bar] = deriving
}

The annotation also supports type parameters, using implicit def rather than implicit val, and can be used on sealed classes, or object.

You can provide your own project-specific wirings in a deriving.conf file, which will also be available for users of your library if it is published.

The config file is plain text with one line per wiring, formatted: fqn.TypeClass=fqn.DerivedTypeClass.method, comments start with #.

If you wish to use @deriving with a custom deriver defined in the same project as the deriver, add your resources directory to the compiler classpath, e.g.

  // WORKAROUND: https://github.com/sbt/sbt/issues/1965
  def resourcesOnCompilerCp(config: Configuration): Setting[_] =
    managedClasspath in config := {
      val res = (resourceDirectory in config).value
      val old = (managedClasspath in config).value
      Attributed.blank(res) +: old
    }

and call with, e.g. resourcesOnCompilerCp(Compile).

@xderiving

A variant @xderiving works only on classes with one parameter (including those that extend AnyVal), making use of an .xmap that the typeclass may provide directly or via an instance of scalaz.InvariantFunctor, e.g.

@scalaz.annotation.xderiving(Encoder, Decoder)
class Foo(val s: String)

expands into

object Foo {
  implicit val _deriving_encoder: Encoder[Foo] = implicitly[Encoder[String]].xmap(new Foo(_), _.s)
  implicit val _deriving_decoder: Decoder[Foo] = implicitly[Decoder[String]].xmap(new Foo(_), _.s)
}

scalaz-deriving

scalaz-deriving adds new typeclasses to scalaz:

Typeclass method given signature returns
Applicative apply2 F[A1], F[A2] (A1, A2) => Z F[Z]
Alt (new) altly2 F[A1], F[A2] (A1 \/ A2) => Z F[Z]
Divisible divide2 F[A1], F[A2] Z => (A1, A2) F[Z]
Decidable (new) choose2 F[A1], F[A2] Z => (A1 \/ A2) F[Z]

and scalaz.Deriving, which supports arbitrarily large case class and sealed trait ADTs and works out of the box with the @deriving annotation.

As a typeclass author you only need to implement Deriving for your typeclass.

If your typeclass can implement Decidable or Alt and satisfy their laws, you can:

  1. wrap your Decidable or Alt with ExtendedInvariantAlt (lowest cognitive overhead).
  2. directly implement the generic arbitrary variants Decidablez / Altz (highest performance, more complex).

If your typeclass cannot satisfy the Decidable or Alt laws, write a fresh LabelledEncoder or LabelledDecoder, which will also give you access to field names.

As an extra convenience, if @deriving is used on a sealed class it is not necessary to add the annotation to the known subtypes!

The following derivations are provided out-of-the-box for scalaz-core typeclasses:

  1. Equal / Order
  2. Semigroup / Monoid

and opt-in extras for

  1. scalaz.Show
  2. org.scalacheck.Arbitrary

Learn by example in scalaz-deriving/src/test/scala/examples and a detailed tutorial in the chapter "Typeclass Derivation" of Functional Programming for Mortals with Scalaz.

Installation

IntelliJ Users

@deriving and @xderiving will work out-of-the box since 2018.1.18.

Maven Central

val derivingVersion = "<version>"
libraryDependencies ++= Seq(
  // the @deriving and @xderiving plugin and macro
  "org.scalaz" %% "deriving-macro" % derivingVersion,
  compilerPlugin("org.scalaz" %% "deriving-plugin" % derivingVersion cross CrossVersion.full),

  // the scalaz-deriving Altz / Decidablez / Deriving API and macros
  "org.scalaz" %% "scalaz-deriving" % derivingVersion,

  // instances for Show and Arbitrary
  "org.scalaz" %% "scalaz-deriving-magnolia" % derivingVersion,
  "org.scalaz" %% "scalaz-deriving-scalacheck" % derivingVersion,

  // shapeless alternatives to Deriving. See below for additional actions.
  "org.scalaz" %% "scalaz-deriving-shapeless" % derivingVersion
)

where <version> is the latest on maven central.

If you are supplying a deriving.conf file, make sure the following is added to your project settings, so that the deriving.conf is present on the compilation classpath:

(Compile / compile) := ((Compile / compile).dependsOn(Compile / copyResources)).value,

To use the opt-in shapeless alternatives, which can sometimes improve runtime performance (and sometimes slow it down), either manually call the DerivingEqual.gen etc from your companions, or create a deriving.conf containing the wirings following the instructions above. Recall that shapeless derivations require annotations on every element of an ADT, not just the top element.

Caveats

scalaz-deriving does not and will not support typeclasses with contravariant or covariant type parameters (e.g. [-A] and [+A]). Fundamentally, Scala's variance is broken and should be avoided. Enforce this in your builds with the DisableSyntax lint.

When adding the @deriving annotation to a sealed class: 1) the derivation will be repeated if there are multiple sealed layers (which might slow down compiles), 2) the implicit scope of the subtype's companion is not searched, 3) only works for =scalaz-deriving= and magnolia, it doesn't work for shapeless derivers.

The macro that generates the iotaz representation does not support exotic language features or renaming type parameters in GADTs. This will be addressed as iota becomes more mature.

Sometimes the scaladoc compiler can get confused and publishing will fail. We recommend that you simply disable scaladocs: nobody reads them and the source is always a better reference anyway:

sources in (Compile, doc) := Nil