softwaremill / magnolia

Easy, fast, transparent generic derivation of typeclass instances
https://softwaremill.com/open-source/
Apache License 2.0
754 stars 115 forks source link

Typeclass Different from the Derived type #510

Open agaro1121 opened 5 months ago

agaro1121 commented 5 months ago

This question is for Scala3

Is there any way for the Typeclass to be different than the derived type?

For example:

trait Thing[A]
trait OtherThing[A]

object Thing extends Derivation[???] {
  type Typeclass[A] = OtherThing[A]

   override def join[T](caseClass: CaseClass[OtherThing, T]): Thing[T] = ???
   override def split[T](sealedTrait: SealedTrait[OtherThing, T]): Thing[T] = ???
}

// usage

Thing.derive[SomeA]

We do something similar to this in Scala 2 to generate an Gen[List[A]] that holds exactly one instance of an arbitrary for every sub type in a sealed trait. This is extremely useful for testing.

Here's the implementation that works in Scala 2:

object FullList {
  type Typeclass[A] = Arbitrary[A]

  def join[A](ctx: CaseClass[Arbitrary, A]): Gen[List[A]] =
    ctx
      .constructMonadic(p => Gen.lzy(p.typeclass))
      .map(a => List(a))

  def split[A](ctx: SealedTrait[Arbitrary, A]): Gen[List[A]] =
    ctx.subtypes.toList
      .foldLeft(Gen.const(List.empty[A])) { (acc, next) =>
        acc.flatMap { xs => Gen.lzy(next.typeclass.arbitrary).map(_ :: xs) }
      }

  def gen[A]: Gen[List[A]] = macro Magnolia.gen[A]
}