tofu-tf/derevo
{ "createdAt": "2018-07-01T12:57:06Z", "defaultBranch": "master", "description": "Multiple instance derivations inside a single macro annotation", "fullName": "tofu-tf/derevo", "homepage": "https://manatki.org/docs/derevo.html", "language": "Scala", "name": "derevo", "pushedAt": "2025-03-04T22:07:22Z", "stargazersCount": 186, "topics": [], "updatedAt": "2025-10-19T08:43:30Z", "url": "https://github.com/tofu-tf/derevo"}derevo
Section titled “derevo”Multiple instance derivations inside a single macro annotation.
Basic Usage
Section titled “Basic Usage”If a library has support for derevo, you can simply write
@derive(SomeCaseClass, AnotherCaseClass, fooDerivation, barDerivation(param1, param2))case class Foo(...)where SomeCaseClass, AnotherCaseClass, fooDerivation, barDerivation are some objects, extending one of the InstanceDef traits (see module source).
For every element of the @derive macro, there will be generated implicit val or implicit def (when Foo has type parameters) providing the corresponding typeclass.
For simple type derivation, if Foo has type parameters, the instance will require an instance of the same or specified other typeclass for proper derivation.
Example:
@derive(encoder)case class Foo[@phantom A, B]!(...)
// `derive` generates:implicit def encoder$macro$1[A, B: Encoder] !: Encoder.AsObject[Foo[A, B]] = ...Foo can also be a newtype in form of estatico or supertagged libraries.
If you have problems with the initialization order you can optionally put the
insertInstancesHere() call to the body of your companion object to specify the place where implicit vals should be inserted.
Making your own derivation.
Section titled “Making your own derivation.”First, extend the object (the companion object for your type is the best option) from one of the InstanceDef traits.
Then implement the instance method for your object so it could derive the corresponding instance.
Example:
trait TypeClass[A] {...}
object TypeClass extends Derivaton[TypeClass] { def instance[A] !: TypeClass[A] = ...}Also, you can define additional methods def apply(...) or def foo(...) in your derivation object. This will allow instance creation as
@derive(TypeClass(...)) or @derive(TypeClass.foo(...)).
To support newtype derivation, extend your object with NewtypeDerivation trait. Alternatively, your object may have the newtype[R] method with a single type parameter that receives the underlying type and returns another object that has the instance method that works as described earlier.
Sometimes, the required constraint may differ from the provided typeclass, e.g. circe.Encoder which requires
Encoder for each field but provides Encoder.AsObject for the target case class.
In this case you should extend SpecificDerivation instead, like object foo extends SpecificDerivation[FromTc, ToTc, NT], where
FromTc[A]- typeclass that will be required for non-phantom type parametersToTc[A]- normal typeclass provided by the instanceNT[A]- type class that will be forwarded by the newtype derivation
Expression table
Section titled “Expression table”| Derivation line | Translation |
|---|---|
@derive(...foo, ...) | implicit val foo$macro: Foo[A] = foo.instance |
@derive(...foo(bar)) | implicit val foo$macro : Foo[A] = foo(bar) |
@derive(...foo.quux(bar)) | implicit val foo$macro : Foo[A] = foo.quux(bar) |
@derive(...foo, ...) when A is a newtype over B | implicit val foo$macro: Foo[A] = foo.newtype[B].instance |
Delegating to other macros
Section titled “Delegating to other macros”Derevo supports macro delegation when the corresponding macro function is provided by another library.
This works by adding the @delegating annotation to your delegator object:
@delegating("full.qualified.method.path")Then call macro Derevo.delegate, macro Derevo.delegateParams, macro Derevo.delegateParams2 or macro Derevo.delegateParams3 in the corresponding methods.
An example can be found here.
Installation
Section titled “Installation”For Scala 2.12 and older:
addCompilerPlugin("org.scalamacros" % "paradise" % "2.1.1" cross CrossVersion.full)For Scala 2.13:
scalacOptions += "-Ymacro-annotations"Supported integrations
Section titled “Supported integrations”libraryDependencies += "tf.tofu" %% "derevo-cats" % "latest version in badge"import derevo.deriveimport derevo.cats.{eqv, show, order, monoid}
import cats.Monoidimport cats.instances.string._import cats.instances.int._import cats.syntax.show._import cats.syntax.order._import cats.syntax.semigroup._import cats.syntax.monoid._
@derive(eqv, show, order, monoid)case class Foo(bar: String, baz: Int)@derive(eqv.universal)case class Bar(x: Int)
assert(Foo("111", 222) === Foo("111", 222))assert(Bar(1) === Bar(1))assert(show"${Foo("111", 222)}" === "Foo{bar=111,baz=222}")assert((Foo("111", 222) compare Foo("222", 333)) == -1)assert((Foo("1", 1) |+| Foo("2", 2)) == Foo("12", 3))assert(Monoid[Foo].empty == Foo("", 0))libraryDependencies += "tf.tofu" %% "derevo-cats-tagless" % "latest version in badge"import derevo.deriveimport derevo.tagless.{functor, flatMap, invariant, contravariant, functorK, invariantK, semigroupalK, applyK}
// TODOlibraryDependencies += "tf.tofu" %% "derevo-tethys" % "latest version in badge"import derevo.deriveimport derevo.tethys.{tethysReader, tethysWriter}
import tethys._import tethys.derivation.builder.{FieldStyle, WriterDerivationConfig}import tethys.jackson._
@derive( tethysReader, tethysWriter(WriterDerivationConfig.withFieldStyle(FieldStyle.lowerSnakecase)))final case class Bar(stringName: String, integerAge: Int)
assert(Bar("Cotique", 27).asJson == """{"string_name":"Cotique","integer_age":27}""")assert("""{"stringName":"Elya","integerAge":32}""".jsonAs[Bar] == Right(Bar("Elya", 32)))libraryDependencies += "tf.tofu" %% "derevo-circe" % "latest version in badge"import derevo.deriveimport derevo.circe.{decoder, encoder}
import io.circe._import io.circe.syntax._import io.circe.parser._
@derive(decoder, encoder)final case class Bar(stringName: String, integerAge: Int)
assert(Bar("KKK", 22).asJson.printWith(Printer.noSpaces) == """{"stringName":"KKK","integerAge":22}""")assert(parse("""{"stringName":"WWW","integerAge":20}""").flatMap(_.as[Bar]) == Right(Bar("WWW", 20)))libraryDependencies += "tf.tofu" %% "derevo-circe-magnolia" % "latest version in badge"import derevo.deriveimport derevo.circe.magnolia.{decoder, encoder}
import io.circe._import io.circe.syntax._import io.circe.parser._
@derive(decoder, encoder)final case class Bar(stringName: String, integerAge: Int)
assert(Bar("KKK", 22).asJson.printWith(Printer.noSpaces) == """{"stringName":"KKK","integerAge":22}""")assert(parse("""{"stringName":"WWW","integerAge":20}""").flatMap(_.as[Bar]) == Right(Bar("WWW", 20)))To change default io.circe.magnolia.configured.Configuration:
import derevo.deriveimport derevo.circe.magnolia.{customizableDecoder, customizableEncoder}
import io.circe._import io.circe.syntax._import io.circe.parser._
@derive(customizableEncoder, customizableDecoder)sealed trait SealedTrait
object SealedTrait { implicit val configuration:Configuration = Configuration.default.withDiscriminator("type")
@derive(encoder, decoder) case class Bar(bar: Int) extends SealedTrait
@derive(encoder, decoder) case class Baz(baz: String) extends SealedTrait}libraryDependencies += "tf.tofu" %% "derevo-ciris" % "latest version in badge"import derevo.deriveimport derevo.ciris.cirisDecoder
import com.typesafe.config.ConfigFactoryimport ciris.hocon._import ciris.hocon.instances._
@derive(cirisDecoder)case class DataConfig(name: String, addresses: List[String], mapping: Map[String, Int])
val source = hoconSource[DataConfig]!( ConfigFactory.parseString( """ |data { | name = AAA | addresses = [home, work, pub] | mapping.until = 1 | mapping.from = 2 | mapping.to = 3 |} """.stripMargin ), "data")
// Load in F[_] context, cats.effect.IO just for exampleimport cats.effect.IOimport scala.concurrent.ExecutionContext.global
implicit val cs = IO.contextShift(global)
assert(source.load[IO].unsafeRunSync() == DataConfig("AAA", List("pub", "home", "work"), Map("until" -> 1, "from" -> 2, "to" -> 3)))libraryDependencies += "tf.tofu" %% "derevo-pureconfig" % "latest version in badge"import derevo.deriveimport derevo.pureconfig.{pureconfigReader, pureconfigWriter}
import com.typesafe.config.ConfigFactoryimport pureconfig._import pureconfig.syntax._
@derive(pureconfigReader, pureconfigWriter)case class DataConfig(name: String, addresses: List[String], mapping: Map[String, Int])
val raw = ConfigFactory .parseString( """ |{ | name = AAA | addresses = [home, work, pub] | mapping.until = 1 | mapping.from = 2 | mapping.to = 3 |} """.stripMargin )
val parsed = ConfigSource.fromConfig(raw).load[DataConfig]
assert(parsed == Right(DataConfig("AAA", List("home", "work", "pub"), Map("until" -> 1, "from" -> 2, "to" -> 3))))assert(parsed.map(_.toConfig.atPath("data").getConfig("data")) == Right(raw))libraryDependencies += "tf.tofu" %% "derevo-rmongo" % "latest version in badge"import derevo.deriveimport derevo.reactivemongo.{bsonDocumentReader, bsonDocumentWriter}
// TODOlibraryDependencies += "tf.tofu" %% "derevo-scalacheck" % "latest version in badge" % Testimport derevo.deriveimport org.scalacheck.Arbitraryimport derevo.scalacheck.arbitrary
// for existing classessealed trait Bearcase class Beer(b: String, t: Boolean) extends Bearcase class Gear(g: Int) extends Bear
println(arbitrary.instance[Bear].arbitrary.sample)
// for classes in tests@derive(arbitrary)case class Test(x: String, y: List[Int])
println(implicitly[Arbitrary[Test]].arbitrary.sample)