Skip to content

Commit

Permalink
Configure mdoc (#2)
Browse files Browse the repository at this point in the history
  • Loading branch information
iRevive committed Oct 5, 2021
1 parent ae27112 commit fb89f36
Show file tree
Hide file tree
Showing 7 changed files with 219 additions and 30 deletions.
88 changes: 74 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,43 +5,103 @@

A micro-library to derive a typeclass for Scala 3 [Union types](https://docs.scala-lang.org/scala3/reference/new-types/union-types.html).

## Quick start
## Getting started

To use `union-derivation` in an existing SBT project with Scala **3** or a later version, add the following dependency to your `build.sbt`:
To use `union-derivation` in an existing SBT project with Scala **3** or a later version, add the following configuration to your `build.sbt`:

```scala
libraryDependencies += "io.github.irevive" %% "union-derivation-core" % "0.0.1"
libraryDependencies += "io.github.irevive" %% "union-derivation-core" % "0.0.2"
scalacOptions += "-Yretain-trees" // important for the detection of an abstract method in a trait
```

## Usage example

### Typeclass definition

```scala
import io.github.irevive.union.derivation.*
import io.github.irevive.union.derivation.{IsUnion, UnionDerivation}

import scala.compiletime.{erasedValue, summonInline}
import scala.deriving.*

trait Show[A] {
def show(a: A): String
}

object Show {
object Show extends ShowLowPriority {

given Show[Int] = v => s"Int($v)"
given Show[Long] = v => s"Long($v)"
given Show[String] = v => s"String($v)"

inline def deriveUnion[A]: Show[A] = UnionDerivation.derive[Show, A]
inline given derived[A](using m: Mirror.Of[A]): Show[A] = { // (1)
val elemInstances = summonAll[m.MirroredElemTypes]
inline m match {
case s: Mirror.SumOf[A] => showSum(s, elemInstances)
case p: Mirror.ProductOf[A] => showProduct(p, elemInstances)
}
}

inline def summonAll[A <: Tuple]: List[Show[?]] =
inline erasedValue[A] match {
case _: EmptyTuple => Nil
case _: (t *: ts) => summonInline[Show[t]] :: summonAll[ts]
}

private def showA[A](a: A, show: Show[?]): String =
show.asInstanceOf[Show[A]].show(a)

private def showSum[A](s: Mirror.SumOf[A], elems: => List[Show[?]]): Show[A] =
new Show[A] {
def show(a: A): String = showA(a, elems(s.ordinal(a)))
}

private def showProduct[A](p: Mirror.ProductOf[A], elems: => List[Show[?]]): Show[A] =
new Show[A] {
def show(a: A): String = {
val product = a.asInstanceOf[Product]

product.productIterator
.zip(product.productElementNames)
.zip(elems.iterator)
.map { case ((field, name), show) => s"$name = ${showA[Any](field, show)}" }
.mkString(product.productPrefix + "(", ", ", ")")
}
}

}

object Main {
def main(args: Array[String]): Unit = {
type UnionType = Int | Long | String
val show: Show[UnionType] = Show.deriveUnion[UnionType] // or UnionDerivation.derive[Show, UnionType]
println(show.show(1))
println(show.show(2L))
println(show.show("3"))
}
trait ShowLowPriority {
inline given derivedUnion[A](using IsUnion[A]): Show[A] = UnionDerivation.derive[Show, A] // (2)
}
```

1) The derivation mechanism. Checkout [Scala 3 docs](https://docs.scala-lang.org/scala3/reference/contextual/derivation.html) for more details.
2) `derivedUnion` has `IsUnion` constraint, therefore the method can be applied only to Union types.

### Usage

```scala
type UnionType = Int | Long | String
final case class User(name: String, age: Long, flags: UnionType)

val unionShow: Show[UnionType] = summon[Show[UnionType]]
// unionShow: Show[UnionType] = repl.MdocSession$App$$Lambda$54373/0x000000084a7a2440@48ff6c1f
val userShow: Show[User] = summon[Show[User]]
// userShow: Show[User] = repl.MdocSession$$anon$9@2b08cb74

println(unionShow.show(1))
// Int(1)
println(unionShow.show(2L))
// Long(2)
println(unionShow.show("3"))
// String(3)
println(userShow.show(User("Pablo", 22, 12L)))
// User(name = String(Pablo), age = Long(22), flags = Long(12))
println(userShow.show(User("Pablo", 33, 1)))
// User(name = String(Pablo), age = Long(33), flags = Int(1))
```

## Generated code

The library creates a set of if-else statements for the known types of the union.
Expand Down
15 changes: 15 additions & 0 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,21 @@ lazy val examples = project
)
.dependsOn(core)

lazy val docs = project
.in(file("modules/docs"))
.enablePlugins(MdocPlugin, DocusaurusPlugin)
.settings(commonSettings)
.settings(noPublishSettings)
.settings(
name := "union-derivation-docs",
mdocIn := baseDirectory.value / "src" / "main" / "mdoc" / "index.md",
mdocOut := file("README.md"),
mdocVariables := Map(
"VERSION" -> version.value.replaceFirst("\\+.*", "")
)
)
.dependsOn(core)

lazy val commonSettings = Seq(
scalaVersion := "3.0.2",
scalacOptions ++= Seq(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ object IsUnion {
val tpe: TypeRepr = TypeRepr.of[A]
tpe.dealias match {
case o: OrType => ('{ IsUnion.singleton.asInstanceOf[IsUnion[A]] }).asExprOf[IsUnion[A]]
case other => report.throwError(s"${tpe.show} is not a Union")
case other => report.throwError(s"${tpe.show} is not a Union")
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,17 +80,16 @@ object ShowDerivationSuite {
inline given derived[A](using m: Mirror.Of[A]): Show[A] = {
val elemInstances = summonAll[m.MirroredElemTypes]
inline m match {
case s: Mirror.SumOf[A] => showSum(s, elemInstances)
case s: Mirror.SumOf[A] => showSum(s, elemInstances)
case p: Mirror.ProductOf[A] => showProduct(p, elemInstances)
}
}

private inline def summonAll[A <: Tuple]: List[Show[?]] = {
private inline def summonAll[A <: Tuple]: List[Show[?]] =
inline erasedValue[A] match {
case _: EmptyTuple => Nil
case _: (t *: ts) => summonInline[Show[t]] :: summonAll[ts]
case _: (t *: ts) => summonInline[Show[t]] :: summonAll[ts]
}
}

private def showA[A](a: A, show: Show[?]): String =
show.asInstanceOf[Show[A]].show(a)
Expand All @@ -105,7 +104,9 @@ object ShowDerivationSuite {
def show(a: A): String = {
val product = a.asInstanceOf[Product]

product.productIterator.zip(product.productElementNames).zip(elems.iterator)
product.productIterator
.zip(product.productElementNames)
.zip(elems.iterator)
.map { case ((field, name), show) => s"$name = ${showA[Any](field, show)}" }
.mkString(product.productPrefix + "(", ", ", ")")
}
Expand Down
113 changes: 113 additions & 0 deletions modules/docs/src/main/mdoc/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
# union-derivation

[![Build Status](https://github.com/iRevive/union-derivation/workflows/CI/badge.svg)](https://github.com/iRevive/union-derivation/actions)
[![Maven Version](https://maven-badges.herokuapp.com/maven-central/io.github.irevive/union-derivation-core_3/badge.svg)](https://maven-badges.herokuapp.com/maven-central/io.github.irevive/union-derivation-core_3)

A micro-library to derive a typeclass for Scala 3 [Union types](https://docs.scala-lang.org/scala3/reference/new-types/union-types.html).

## Getting started

To use `union-derivation` in an existing SBT project with Scala **3** or a later version, add the following configuration to your `build.sbt`:

```scala
libraryDependencies += "io.github.irevive" %% "union-derivation-core" % "@VERSION@"
scalacOptions += "-Yretain-trees" // important for the detection of an abstract method in a trait
```

## Usage example

### Typeclass definition

```scala mdoc:silent
import io.github.irevive.union.derivation.{IsUnion, UnionDerivation}

import scala.compiletime.{erasedValue, summonInline}
import scala.deriving.*

trait Show[A] {
def show(a: A): String
}

object Show extends ShowLowPriority {

given Show[Int] = v => s"Int($v)"
given Show[Long] = v => s"Long($v)"
given Show[String] = v => s"String($v)"

inline given derived[A](using m: Mirror.Of[A]): Show[A] = { // (1)
val elemInstances = summonAll[m.MirroredElemTypes]
inline m match {
case s: Mirror.SumOf[A] => showSum(s, elemInstances)
case p: Mirror.ProductOf[A] => showProduct(p, elemInstances)
}
}

inline def summonAll[A <: Tuple]: List[Show[?]] =
inline erasedValue[A] match {
case _: EmptyTuple => Nil
case _: (t *: ts) => summonInline[Show[t]] :: summonAll[ts]
}

private def showA[A](a: A, show: Show[?]): String =
show.asInstanceOf[Show[A]].show(a)

private def showSum[A](s: Mirror.SumOf[A], elems: => List[Show[?]]): Show[A] =
new Show[A] {
def show(a: A): String = showA(a, elems(s.ordinal(a)))
}

private def showProduct[A](p: Mirror.ProductOf[A], elems: => List[Show[?]]): Show[A] =
new Show[A] {
def show(a: A): String = {
val product = a.asInstanceOf[Product]

product.productIterator
.zip(product.productElementNames)
.zip(elems.iterator)
.map { case ((field, name), show) => s"$name = ${showA[Any](field, show)}" }
.mkString(product.productPrefix + "(", ", ", ")")
}
}

}

trait ShowLowPriority {
inline given derivedUnion[A](using IsUnion[A]): Show[A] = UnionDerivation.derive[Show, A] // (2)
}
```

1) The derivation mechanism. Checkout [Scala 3 docs](https://docs.scala-lang.org/scala3/reference/contextual/derivation.html) for more details.
2) `derivedUnion` has `IsUnion` constraint, therefore the method can be applied only to Union types.

### Usage

```scala mdoc
type UnionType = Int | Long | String
final case class User(name: String, age: Long, flags: UnionType)

val unionShow: Show[UnionType] = summon[Show[UnionType]]
val userShow: Show[User] = summon[Show[User]]

println(unionShow.show(1))
println(unionShow.show(2L))
println(unionShow.show("3"))
println(userShow.show(User("Pablo", 22, 12L)))
println(userShow.show(User("Pablo", 33, 1)))
```

## Generated code

The library creates a set of if-else statements for the known types of the union.

The simplified version of the generated code:
```scala
val instance: Show[Int | String | Long] = UnionDerivation.derive[Show, Int | String | Long]

// expands into
val instance: Show[Int | String | Long] = { (value: Int | String | Long) =>
if (value.isInstanceOf[Int]) summon[Show[Int]].show(value)
else if (value.isInstanceOf[String]) summon[Show[String]].show(value)
else if (value.isInstanceOf[Long]) summon[Show[Long]].show(value)
else sys.error("Impossible")
}
```
2 changes: 1 addition & 1 deletion modules/examples/src/main/scala/Main.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ object Main {
type UnionType = Int | Long | String

val show: Show[UnionType] = summon[Show[UnionType]]
val userShow: Show[User] = summon[Show[User]]
val userShow: Show[User] = summon[Show[User]]

println(show.show(1))
println(show.show(2L))
Expand Down
18 changes: 9 additions & 9 deletions modules/examples/src/main/scala/Show.scala
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import io.github.irevive.union.derivation.IsUnion
import io.github.irevive.union.derivation.UnionDerivation
import io.github.irevive.union.derivation.{IsUnion, UnionDerivation}

import scala.deriving.*
import scala.compiletime.{erasedValue, summonInline}
import scala.deriving.*

trait Show[A] {
def show(a: A): String
Expand All @@ -21,17 +20,16 @@ object Show extends LowPriority {
inline given derived[A](using m: Mirror.Of[A]): Show[A] = {
val elemInstances = summonAll[m.MirroredElemTypes]
inline m match {
case s: Mirror.SumOf[A] => showSum(s, elemInstances)
case s: Mirror.SumOf[A] => showSum(s, elemInstances)
case p: Mirror.ProductOf[A] => showProduct(p, elemInstances)
}
}

private inline def summonAll[A <: Tuple]: List[Show[?]] = {
private inline def summonAll[A <: Tuple]: List[Show[?]] =
inline erasedValue[A] match {
case _: EmptyTuple => Nil
case _: (t *: ts) => summonInline[Show[t]] :: summonAll[ts]
case _: (t *: ts) => summonInline[Show[t]] :: summonAll[ts]
}
}

private def showA[A](a: A, show: Show[?]): String =
show.asInstanceOf[Show[A]].show(a)
Expand All @@ -46,7 +44,9 @@ object Show extends LowPriority {
def show(a: A): String = {
val product = a.asInstanceOf[Product]

product.productIterator.zip(product.productElementNames).zip(elems.iterator)
product.productIterator
.zip(product.productElementNames)
.zip(elems.iterator)
.map { case ((field, name), show) => s"$name = ${showA[Any](field, show)}" }
.mkString(product.productPrefix + "(", ", ", ")")
}
Expand All @@ -56,4 +56,4 @@ object Show extends LowPriority {

private trait LowPriority {
inline given derivedUnion[A](using IsUnion[A]): Show[A] = UnionDerivation.derive[Show, A]
}
}

0 comments on commit fb89f36

Please sign in to comment.