diff --git a/readme.md b/readme.md index 666e53b3..8621057d 100644 --- a/readme.md +++ b/readme.md @@ -25,7 +25,7 @@ enum Tree[+T] derives Print: case Branch(left: Tree[T], right: Tree[T]) case Leaf(value: T) ``` -and provided an given instance of `Print[Int]` is in scope, and a Magnolia derivation for the `Print` typeclass +and provided a given instance of `Print[Int]` is in scope, and a Magnolia derivation for the `Print` typeclass has been provided, we can automatically derive given typeclass instances of `Print[Tree[Int]]` on-demand, like so, ```scala @@ -34,7 +34,19 @@ Tree.Branch(Tree.Branch(Tree.Leaf(1), Tree.Leaf(2)), Tree.Leaf(3)).print Typeclass authors may provide Magnolia derivations in the typeclass's companion object, but it is easy to create your own. -The definition of a `Print` typeclass with generic derivation defined with Magnolia might look like this: +Creating a generic derivation with Magnolia requires implementing two methods on `magnolia1.Derivation`: + +* `join()` : create typeclasses for case classes ('product types') +* `split()` : create typeclasses for sealed-traits/enums ('sum types') + +### Example derivations + +There are many examples in the [`src/examples`](src/examples) folder. + +The definition of a `Print` typeclass with generic derivation might look like this +(note we're using the [Lambda syntax for Single Abstract Method types](https://www.scala-lang.org/news/2.12.0/#lambda-syntax-for-sam-types) +to instantiate the `Print` instances in `join` & `split` - that's possible because +`Print` has only a single abstract method, `print`): ```scala import magnolia1.* diff --git a/src/core/interface.scala b/src/core/interface.scala index c3055904..f6c45a26 100644 --- a/src/core/interface.scala +++ b/src/core/interface.scala @@ -21,7 +21,18 @@ object CaseClass: type PType + /** + * Gives the constructed typeclass for the parameter's type. Eg for a `case class Foo(bar: String, baz: Int)`, + * where this [[Param]] denotes 'baz', the `typeclass` field returns an instance of `Typeclass[Int]`. + */ def typeclass: Typeclass[PType] + + /** + * Get the value of this param out of the supplied instance of the case class. + * + * @param value an instance of the case class + * @return the value of this parameter in the case class + */ def deref(param: Type): PType /** Requires compilation with `-Yretain-trees` on. @@ -82,6 +93,15 @@ object CaseClass: end Param end CaseClass + +/** + * In the terminology of Algebraic Data Types (ADTs), case classes are known as 'product types'. + * + * @param params an array giving information about the parameters of the case class. Each [[Param]] element + * has a very useful [[CaseClass.Param.typeclass]] field giving the constructed typeclass for the + * parameter's type. Eg for a `case class Foo(bar: String, baz: Int)`, you can + * obtain `Typeclass[String]`, `Typeclass[Int]`. + */ abstract class CaseClass[Typeclass[_], Type]( val typeInfo: TypeInfo, val isObject: Boolean, @@ -169,6 +189,12 @@ abstract class CaseClass[Typeclass[_], Type]( end CaseClass +/** + * Represents a Sealed-Trait or a Scala 3 Enum. + * + * In the terminology of Algebraic Data Types (ADTs), sealed-traits/enums are termed + * 'sum types'. + */ case class SealedTrait[Typeclass[_], Type]( typeInfo: TypeInfo, subtypes: IArray[SealedTrait.Subtype[Typeclass, Type, _]], @@ -215,6 +241,17 @@ case class SealedTrait[Typeclass[_], Type]( override def toString: String = s"SealedTrait($typeInfo, IArray[${subtypes.mkString(",")}])" + /** + * Provides a way to recieve the type info for the explicit subtype that + * 'value' is an instance of. So if 'Type' is a Sealed Trait or Scala 3 + * Enum like 'Suit', the 'handle' function will be supplied with the + * type info for the specific subtype of 'value', eg 'Diamonds'. + * + * @param value must be instance of a subtype of typeInfo + * @param handle function that will be passed the Subtype of 'value' + * @tparam Return whatever type the 'handle' function wants to return + * @return whatever the 'handle' function returned! + */ def choose[Return](value: Type)(handle: Subtype[_] => Return): Return = @tailrec def rec(ix: Int): Return = if ix < subtypes.length then @@ -249,6 +286,10 @@ object SealedTrait: IArray.empty[Any] ) + /** + * @tparam Type the type of the Sealed Trait or Scala 3 Enum, eg 'Suit' + * @tparam SType the type of the subtype, eg 'Diamonds' or 'Clubs' + */ class Subtype[Typeclass[_], Type, SType]( val typeInfo: TypeInfo, val annotations: IArray[Any], @@ -284,6 +325,9 @@ object SealedTrait: asType ) + /** + * @return the already-constructed typeclass instance for this subtype + */ def typeclass: Typeclass[SType & Type] = callByNeed.value.asInstanceOf[Typeclass[SType & Type]] def cast: PartialFunction[Type, SType & Type] = this diff --git a/src/core/magnolia.scala b/src/core/magnolia.scala index 14686702..c6a57805 100644 --- a/src/core/magnolia.scala +++ b/src/core/magnolia.scala @@ -8,7 +8,22 @@ import Macro.* trait CommonDerivation[TypeClass[_]]: type Typeclass[T] = TypeClass[T] - def join[T](ctx: CaseClass[Typeclass, T]): Typeclass[T] + + /** + * Must be implemented by the user of Magnolia to construct a + * typeclass for case class `T` using the provided type info. + * E.g. if we are deriving `Show[T]` typeclasses, and `T` + * is a case class `Foo(...)`, we need to constuct `Show[Foo]`. + * + * This method is called 'join' because typically it will _join_ + * together the typeclasses for all the parameters of the case class, + * into a single typeclass for the case class itself. The field + * [[CaseClass.params]] can provide useful information for doing this. + * + * @param caseClass information about the case class `T`, its parameters, + * and _their_ typeclasses + */ + def join[T](caseClass: CaseClass[Typeclass, T]): Typeclass[T] inline def derivedMirrorProduct[A]( product: Mirror.ProductOf[A] @@ -61,7 +76,23 @@ end ProductDerivation trait Derivation[TypeClass[_]] extends CommonDerivation[TypeClass] with SealedTraitDerivation: - def split[T](ctx: SealedTrait[Typeclass, T]): Typeclass[T] + + /** + * This must be implemented by the user of Magnolia to construct a Typeclass + * for 'T', where 'T' is a Sealed Trait or Scala 3 Enum, using the provided + * type info. E.g. if we are deriving 'Show[T]' typeclasses, and T + * is an enum 'Suit' (eg with values Diamonds, Clubs, etc), we need to + * constuct 'Show[Suit]'. + * + * This method is called 'split' because it will ''split'' the different + * possible types of the SealedTrait, and handle each one to finally + * produce a typeclass capable of handling any possible subtype of the trait. + * + * A useful function for implementing this method is [[SealedTrait#choose]], + * which can take a value instance and provide information on the specific + * subtype of the sealedTrait which that value is. + */ + def split[T](sealedTrait: SealedTrait[Typeclass, T]): Typeclass[T] transparent inline def subtypes[T, SubtypeTuple <: Tuple]( m: Mirror.SumOf[T],