Questions tagged [magnolia-scala]

Magnolia is a Scala library that provides a generic macro for automatic materialization of typeclasses for datatypes composed from case classes (products) and sealed traits (coproducts). It supports recursively-defined datatypes out-of-the-box, and incurs no significant time-penalty during compilation. If derivation fails, error messages are detailed and informative.

Magnolia

About

Magnolia is a generic macro for automatic materialization of typeclasses for datatypes composed from case classes (products) and sealed traits (coproducts). It supports recursively-defined datatypes out-of-the-box, and incurs no significant time-penalty during compilation. If derivation fails, error messages are detailed and informative.

Features

  • derives typeclasses for case classes, case objects and sealed traits
  • offers a lightweight, non-macro syntax for writing derivations
  • works with recursive and mutually-recursive definitions
  • supports parameterized ADTs (GADTs), including in recursive types
  • supports typeclasses whose generic type parameter is used in either covariant and contravariant positions
  • caches implicit searches for compile-time efficiency
  • prints an error stack to help debugging when derivation fails
  • provides access to case class default parameter values
  • offers predictable resolution of prioritized implicits
  • does not require additional type annotations, like Lazy[T]

Usage

Given an ADT such as,

sealed trait Tree[+T]
case class Branch[+T](left: Tree[T], right: Tree[T]) extends Tree[T]
case class Leaf[+T](value: T) extends Tree[T]

and provided an implicit instance of Show[Int] is in scope, and a Magnolia derivation for the Show typeclass has been provided, we can automatically derive an implicit typeclass instance of Show[Tree[Int]] on-demand, like so,

Branch(Branch(Leaf(1), Leaf(2)), Leaf(3)).show

Typeclass authors may provide Magnolia derivations in the Typeclass's companion object, but it is easy to create your own.

The derivation typeclass for a Show typeclass might look like this:

import language.experimental.macros, magnolia._

object ShowDerivation {
  type Typeclass[T] = Show[T]

  def combine[T](ctx: CaseClass[Show, T]): Show[T] = new Show[T] {
    def show(value: T): String = ctx.parameters.map { p =>
      s"${p.label}=${p.typeclass.show(p.dereference(value))}"
    }.mkString("{", ",", "}")
  }

  def dispatch[T](ctx: SealedTrait[Show, T]): Show[T] =
    new Show[T] {
      def show(value: T): String = ctx.dispatch(value) { sub =>
        sub.typeclass.show(sub.cast(value))
      }
    }

  implicit def gen[T]: Show[T] = macro Magnolia.gen[T]
}

The gen method will attempt to construct a typeclass for the type passed to it. Importing ShowDerivation.gen from the example above will make generic derivation for Show typeclasses available in the scope of the import. The macro Magnolia.gen[T] binding must be made in a static object, and the type constructor, Typeclass, and the methods combine and dispatch must be defined in the same object.

If you control the typeclass you are deriving for, the companion object of the typeclass makes a good choice for providing the implicit derivation methods described above.

Debugging

Deriving typeclasses is not always guaranteed to succeed, though. Many datatypes are complex and deeply-nested, and failure to derive a typeclass for a single parameter in one of the leaf nodes will cause the entire tree to fail.

Magnolia tries to be informative about why failures occur, by providing a "stack trace" showing the path to the type which could not be derived.

For example, when attempting to derive a Show instance for Entity, given the following hypothetical datatypes,

sealed trait Entity
case class Person(name: String, address: Address) extends Entity
case class Organization(name: String, contacts: Set[Person]) extends Entity
case class Address(lines: List[String], country: Country)
case class Country(name: String, code: String, salesTax: Boolean)

the absence, for example, of a Show[Boolean] typeclass instance would cause derivation to fail, but the reason might not be obvious, so instead, Magnolia will report the following compile error:

could not derive Show instance for type Boolean
    in parameter 'salesTax' of product type Country
    in parameter 'country' of product type Address
    in parameter 'address' of product type Person
    in chained implicit of type Set[Person]
    in parameter 'contacts' of product type Organization
    in coproduct type Entity

This "derivation stack trace" will only be displayed when invoking a derivation method, e.g. Show.gen[Entity], directly. When the method is invoked through implicit search, to reduce spurious error messages (when Magnolia's derivation fails, but implicit search still finds a valid implicit) the errors are not shown.

Current status

Magnolia is currently experimental. It has been shown to work for a variety of test cases, though it has not had the same exposure to real-world datatypes that, for example, Shapeless has had.

The API for defining derivations has been shown to be adequate for the test cases, and is not expected to change significantly, but some convenience methods may be provided in the future.

Flower right In terms of production-readiness, the macro will not produce code which fails to typecheck. But it can still refuse to generate a derivation, so users should be cautious of becoming reliant upon it until it has received more thorough testing.

However, should Magnolia fail, in all cases it should be possible to write typeclass instances manually, and have these take precedence.

Macro-based generic derivation is known to be quite heavy on compile times, and significant effort has been invested in trying to minimize the work Magnolia does in deriving a typeclass. Preliminary testing comparing Magnolia with Shapeless suggests that Magnolia offers between a 4x and 15x performance improvement over Shapeless, depending on the structure of the ADT being derived.

The runtime performance of Magnolia-generated code has not been tested, and while some work has gone into minimizing the amount of runtime heap allocations from generate typeclasses, it is known that it will generate some ephemeral garbage, and this is an area which is expected to see some improvement in subsequent versions.

7 questions
3
votes
1 answer

Blending Magnolia with Circe's trick for automatic derivation

I've got a typeclass and want to provide semi-automatic and automatic derivation to users. I have a working implementation based on Magnolia and it works really well. There's a trait providing definitions for Typeclass[A], combine[A] and…
1
vote
0 answers

Magnolia: Type derivation fails in case of nested type classes

I am trying to create a serializable trait that has a dependency on type class. package dsl import zio.schema._ sealed trait Random[A] { def generate: A } object Random { case object RandomDouble extends Random[Double] { override def…
cauchy
  • 1,123
  • 1
  • 9
  • 19
1
vote
1 answer

ScalaCheck Arbitrary case class with random data generation (Magnolia)

Using a basic example I'm attempting to randomly generate a bunch of Person (case class Person(name: String, age: Int) instances using this library for random data generation. The problem I'm running into is when creating an Arbitrary that has bound…
mvee
  • 263
  • 2
  • 15
1
vote
1 answer

How does a Config Descriptor look for a Map with ZIO Config / Magnolia

I have the following Case Class: case class MyClass(name: String, params: Map[String, String]) I couldn't figure out how to create a Config Description for this. Also automatic derivation with Magnolia did not work. Error:(70, 44) could not find…
pme
  • 14,156
  • 3
  • 52
  • 95
0
votes
1 answer

Implicitly getting Schema from class with type Alias

I'm currently using sttp version 3.3.14 with tapir version 0.18.0-M15 and I'm having trouble with the Schemas of certain case classes. More specifically, case classes that contain type aliases. Here is a simple custom Codec for Either: import…
André Machado
  • 726
  • 6
  • 21
0
votes
0 answers

Magnolia Publishing DAM content from author process killing the pod

Background of the issue : We are using Magnolia CMS with customized UI. As a first step of using Magnolia, we are migrating old content including documents to Magnolia. Content migration is working fine. Issue with DAM. We have more than 50 GB of…
0
votes
1 answer

How to fix an issue "could not find implicit value for evidence parameter of type" when using magnolia

I'm trying to make a CSV (with headers) parser that extracts a row into a case class. I want the extraction to rely on the header to affect (instead of relying on the case class parameters being in the same order as in the CSV) the value to the…
nefas
  • 1,120
  • 7
  • 16