Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add scaladoc targeted at newcomers to type-derivation #436

Merged
merged 1 commit into from
Nov 28, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.*

Expand Down
44 changes: 44 additions & 0 deletions src/core/interface.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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, _]],
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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],
Expand Down Expand Up @@ -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
Expand Down
35 changes: 33 additions & 2 deletions src/core/magnolia.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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],
Expand Down