perspective.derivation.inlineHkdGeneric.scala Maven / Gradle / Ivy
The newest version!
package perspective.derivation
import scala.language.implicitConversions
import scala.annotation.{compileTimeOnly, tailrec}
import scala.compiletime.*
import scala.deriving.*
import scala.quoted.*
import scala.reflect.ClassTag
import cats.syntax.all.*
import cats.{Applicative, Functor, Monoid}
import perspective.*
/**
* A type like [[HKDGeneric]] but where as much as possible is defined inline,
* and tries to generate as simple bytecode as possible. Might be more error
* prone, and produce bigger classes using this, but the bytecode is much
* simpler.
*
* @tparam A
* The type being abstracted over.
*/
//noinspection ScalaUnusedSymbol
sealed trait InlineHKDGeneric[A]:
/** A representation of [[A]] supporting higher kinded types. */
type Gen[_[_]]
/** The top type for the inner type of [[Index]] and [[Gen]]. */
type ElemTop
/** The index of the [[Gen]] type. */
type Index <: Any { type X <: ElemTop }
type IndexAux[X0 <: ElemTop] = Index { type X = X0 }
/** A wrapper for [[Index]] where we want no bound on what [[X]] can be. */
class IdxWrapper[X](val idx: IndexAux[X & ElemTop])
given [X]: Conversion[IdxWrapper[X], IndexAux[X & ElemTop]] = _.idx
given [X]: Conversion[IndexAux[X & ElemTop], IdxWrapper[X]] = new IdxWrapper(_)
/** The name of the [[A]] type. */
type TypeName <: String
/** The name of the [[A]] type. */
inline def typeName: TypeName
type Size <: Int
/** The sizer of the type. */
inline def size: Size
/**
* The name of the fields of [[A]] type. Field in this case can mean either
* the children of a sum type, or the fields of a product type.
*/
type Names <: String
/**
* The name of the fields of [[A]] type. Field in this case can mean either
* the children of a sum type, or the fields of a product type.
*/
inline def names: Gen[Const[Names]]
/** Validates a string as a name if it matches the name of a field. */
inline def stringToName(s: String): Option[Names]
/** Returns the index of the field a name corresponds to. */
inline def nameToIndex(name: Names): Index
/** A tuple representation of [[A]]. */
type TupleRep <: Tuple
/** Converts [[Gen]] to the tuple representation. */
inline def genToTuple[F[_]](gen: Gen[F]): Helpers.TupleMap[TupleRep, F]
/** Converts the tuple representation to Gen. */
inline def tupleToGen[F[_]](tuple: Helpers.TupleMap[TupleRep, F]): Gen[F]
/** Converts [[Gen]] to the scala tuple representation. */
inline def genToScalaTuple[F[_]](gen: Gen[F]): Tuple.Map[TupleRep, F]
/** Converts the scala tuple representation to Gen. */
inline def scalaTupleToGen[F[_]](tuple: Tuple.Map[TupleRep, F]): Gen[F]
inline def summonInstances[F[_]]: Gen[F]
val idClassTag: ClassTag[ElemTop]
given ClassTag[ElemTop] = idClassTag
//
// Typeclass ops
//
// Functor
extension [A[_]](fa: Gen[A])
inline def mapK[B[_]](inline f: A :~>: B): Gen[B]
inline def mapConst[B](inline f: A :~>#: B): Gen[Const[B]] =
mapK(f)
inline def voidK: Gen[Const[Unit]] = asK(ValueK.const(()))
inline def asK[B[_]](inline b: ValueK[B]): Gen[B] =
mapK([Z] => (_: A[Z]) => b[Z]())
inline def widen[B[D] >: A[D]]: Gen[B] = fa.asInstanceOf[Gen[B]]
extension [A[_], B[_]](f: A :~>: B) inline def liftK: Gen[A] :#~>#: Gen[B] = [C] => (fa: Gen[A]) => fa.mapK(f)
// Apply
extension [A[_], B[_]](ff: Gen[[D] =>> A[D] => B[D]])
inline def ap(fa: Gen[A]): Gen[B] =
ff.map2K(fa)([Z] => (f: A[Z] => B[Z], a: A[Z]) => f(a))
extension [A[_]](fa: Gen[A])
inline def map2K[B[_], Z[_]](fb: Gen[B])(inline f: [X] => (A[X], B[X]) => Z[X]): Gen[Z]
inline def map2Const[B[_], Z](fb: Gen[B])(inline f: [X] => (A[X], B[X]) => Z): Gen[Const[Z]] =
fa.map2K(fb)(f)
inline def tupledK[B[_]](fb: Gen[B]): Gen[Tuple2K[A, B]] =
fa.map2K(fb)([Z] => (a: A[Z], b: B[Z]) => (a, b))
// Applicative
extension [A[_]](a: ValueK[A]) inline def pure: Gen[A]
inline def unitK: Gen[Const[Unit]] = ValueK.const(()).pure
// Foldable
extension [A[_]](fa: Gen[A])
inline def foldLeftK[B](inline b: B)(inline f: B => A :~>#: B): B
inline def foldMapK[B](inline f: A :~>#: B)(using B: Monoid[B]): B
extension [A](fa: Gen[Const[A]]) inline def toListK: List[A]
// Traverse
extension [A[_]](fa: Gen[A])
inline def traverseK[G[_], B[_]](
inline f: A :~>: Compose2[G, B]
)(using inline G: Applicative[G], classTag: ClassTag[B[ElemTop]]): G[Gen[B]]
inline def traverseIdK[G[_]](inline f: A :~>: G)(using inline G: Applicative[G]): G[Gen[Id]] =
traverseK(f)
inline def sequenceIdK(using inline G: Applicative[A]): A[Gen[Id]] =
fa.sequenceK
extension [G[_], A[_]](fga: Gen[Compose2[G, A]]) inline def sequenceK(using inline G: Applicative[G]): G[Gen[A]]
// Distributive
extension [G[_]: Functor, A[_]](gfa: G[Gen[A]])
inline def distributeK[B[_]](inline f: Compose2[G, A] :~>: B): Gen[B] =
gfa.cosequenceK.mapK(f)
inline def distributeConst[B](inline f: Compose2[G, A] :~>#: B): Gen[Const[B]] =
distributeK[Const[B]](f)
inline def cosequenceK: Gen[Compose2[G, A]]
extension [G[_]: Functor, A](ga: G[A])
inline def collectK[B[_]](inline f: A => Gen[B]): Gen[Compose2[G, B]] =
ga.map(f).cosequenceK
// Monad
extension [A[_]](ffa: Gen[Const[Gen[A]]]) inline def flattenK: Gen[A] = ffa.flatMapK(FunctionK.identity)
extension [A[_]](fa: Gen[A]) inline def flatMapK[B[_]](inline f: A :~>#: Gen[B]): Gen[B]
// Representable
inline def tabulateK[B[_]](inline f: (i: Index) => B[i.X], inline unrolling: Boolean = false)(
using classTag: ClassTag[B[ElemTop]]
): Gen[B]
inline def tabulateConst[B](inline f: Index => B)(using ClassTag[B]): Gen[Const[B]] = tabulateK(f)
inline def tabulateFoldLeft[B](inline start: B, inline unrolling: Boolean = false)(inline f: (B, Index) => B): B
inline def tabulateTraverseK[G[_], B[_]](
inline f: (i: Index) => G[B[i.X]],
inline unrolling: Boolean = false
)(using inline G: Applicative[G], classTag: ClassTag[B[ElemTop]]): G[Gen[B]]
inline def tabulateTraverseIdK[G[_]](inline f: (i: Index) => G[i.X])(using inline G: Applicative[G]): G[Gen[Id]] =
tabulateTraverseK(f)
inline def indicesK: Gen[IdxWrapper] = tabulateK(index => IdxWrapper(index))
extension [B[_]](fa: Gen[B]) inline def indexK(rep: Index): B[rep.X]
//noinspection ScalaUnusedSymbol,DuplicatedCode
object InlineHKDGeneric:
type Aux[A, Gen0[_[_]]] = InlineHKDGeneric[A] {
type Gen[B[_]] = Gen0[B]
}
transparent inline given derived[A](
using m0: Mirror.Of[A],
typeLength: TypeLength[m0.MirroredElemTypes]
): InlineHKDGeneric[A] =
inline m0 match
case m: Mirror.ProductOf[A] { type MirroredElemTypes = m0.MirroredElemTypes } =>
InlineHKDProductGeneric
.derived[A](using m, typeLength, summonInline[ClassTag[Helpers.TupleUnion[m.MirroredElemTypes, Nothing]]])
case m: Mirror.SumOf[A] { type MirroredElemTypes = m0.MirroredElemTypes } =>
InlineHKDSumGeneric
.derived[A](
using m,
typeLength,
summonInline[ClassTag[Helpers.TupleUnionLub[m.MirroredElemTypes, A, Nothing]]]
)
end derived
extension [A](arr: IArray[A])
private inline def quickArrayIndex(i: Int): A =
arr.asInstanceOf[Array[A]](i)
@compileTimeOnly("lateInlineMatch must be used within a supported InlineHKDGeneric method")
def lateInlineMatch[A](a: A): A = sys.error("impossible")
@compileTimeOnly("productElementIdExact must be used within a supported InlineHKDGeneric method")
def productElementIdExact[A, ElemTop](a: A, idx: Int { type X <: ElemTop }): idx.X = sys.error("impossible")
extension [A](arr: Array[A]) private[derivation] inline def asIArray: IArray[A] = arr.asInstanceOf[IArray[A]]
private[derivation] inline def iteratorToArray[A[_], ElemTop](inline itIn: Iterator[A[ElemTop]], inline size: Int)(
using ClassTag[A[ElemTop]]
): Array[A[ElemTop]] = {
val it = itIn
val arr: Array[A[ElemTop]] = new Array[A[ElemTop]](size)
var i: Int = 0
while (it.hasNext) {
arr(i) = it.next()
i += 1
}
arr
}
private class TreeMaps[Q <: Quotes](using val q: Q) {
import q.reflect.*
class ProductElementIdExactExpander[Fields <: Tuple: Type] extends TreeMap {
override def transformTerm(tree: Term)(owner: Symbol): Term =
try {
tree.asExpr match {
case '{ InlineHKDGeneric.productElementIdExact[a2, elemTop]($a, $idx) } =>
transformExact(a, idx)
case _ =>
super.transformTerm(tree)(owner)
}
} catch {
case e: Exception =>
// Tried to convert partially applied type to Expr. Ignoring it
super.transformTerm(tree)(owner)
}
def transformExact[A](a: Expr[A], idxExpr: Expr[Int]): Term = {
val ownerSym = idxExpr.asTerm.symbol.owner
def findConstantIdx(tpe: TypeRepr): Option[Int] = tpe match {
case AndType(a, b) => findConstantIdx(a).orElse(findConstantIdx(b))
case ConstantType(IntConstant(i)) => Some(i)
case Refinement(a, _, _) => findConstantIdx(a)
case TermRef(_, _) => findConstantIdx(ownerSym.typeRef.memberType(tpe.termSymbol))
case t =>
None
}
val idx = findConstantIdx(idxExpr.asTerm.tpe.widenTermRefByName)
.getOrElse(idxExpr.valueOrAbort)
val field = Helpers.valuesOfConstantTuple(TypeRepr.of[Fields], Nil) match {
case Some(seq) => seq(idx).asExprOf[String].valueOrAbort
case None => report.errorAndAbort("productElementIdExact called with non constant fields type")
}
Select.unique(a.asTerm, field)
}
}
class RefReplacer(oldRef: q.reflect.Symbol, newRef: q.reflect.Ref) extends TreeMap {
override def transformTerm(tree: Term)(owner: Symbol): Term =
tree match {
case Ident(id) if id == oldRef.name => newRef
case _ => super.transformTerm(tree)(owner)
}
}
class LateInlineMatchExpander extends TreeMap {
override def transformTerm(tree: Term)(owner: Symbol): Term = {
tree.asExpr match {
case '{ InlineHKDGeneric.lateInlineMatch[a]($a) } =>
transformMatch(a, owner)
case _ =>
try {
super.transformTerm(tree)(owner)
} catch {
case _: Exception =>
// FIXME: Have no idea why this happens. Just ignoring it for now.
tree
}
}
}
def transformMatch[A: Type](aExpr: Expr[A], owner: Symbol): Term = aExpr.asTerm match {
case m @ Match(scrutinee, cases) =>
val tpe = scrutinee.tpe.widenTermRefByName
cases.foreach {
case CaseDef(_, Some(_), _) => report.errorAndAbort("Cases in match can not have guards")
case CaseDef(Bind(_, Typed(Ident(_), _)), _, _) =>
case CaseDef(Bind(_, Ident(_)), _, _) =>
case caseDef => report.errorAndAbort("Invalid case in match inside lateInlineMatch", caseDef.pos)
}
val hasDefaultCase = cases.exists {
case CaseDef(Bind(_, Ident(_)), _, _) => true
case _ => false
}
if !hasDefaultCase then report.errorAndAbort("Match must have a default case", m.pos)
val (bind, rhs) = cases
.collectFirst {
case CaseDef(bind @ Bind(_, typed @ Typed(Ident(_), _)), _, rhs) if tpe <:< typed.symbol.typeRef =>
(bind, rhs)
}
.getOrElse {
cases.collectFirst { case CaseDef(bind @ Bind(_, Ident(_)), _, rhs) =>
(bind, rhs)
}.get
}
ValDef.let(owner, bind.name + "Replaced", scrutinee) { newRef =>
val replacer = new RefReplacer(bind.symbol, newRef)
replacer.transformTerm(rhs)(owner)
}
case _ => report.errorAndAbort("Body of lateInlineMatch must be a match", aExpr)
}
}
}
private def processUnrollingDefs[Fields <: Tuple: Type, A: Type](e: Expr[A])(
using q: Quotes
): Expr[A] =
import q.reflect.*
val treeMaps = new TreeMaps[q.type]()
val productElementIdExactExpander = new treeMaps.ProductElementIdExactExpander[Fields]()
val lateInlineMatchExpander = new treeMaps.LateInlineMatchExpander()
val r1 = productElementIdExactExpander.transformTerm(e.asTerm)(Symbol.spliceOwner)
val r2 = lateInlineMatchExpander.transformTerm(r1)(Symbol.spliceOwner)
r2.asExprOf[A]
end processUnrollingDefs
private def tabulateKImpl[A[_]: Type, ElemTop: Type, Fields <: Tuple: Type](
sizeExpr: Expr[Int],
f: Expr[(i: Int { type X <: ElemTop }) => A[i.X]],
unrolling: Expr[Boolean],
classTagExpr: Expr[ClassTag[A[ElemTop]]]
)(
using q: Quotes
): Expr[IArray[A[ElemTop]]] = {
import q.reflect.*
val doUnrolling = unrolling.valueOrAbort
lazy val noUnrolling = '{
given ClassTag[A[ElemTop]] = $classTagExpr
val arr = new Array[A[ElemTop]]($sizeExpr)
var i: Int = 0
while (i < $sizeExpr) {
arr(i) = ${ Expr.betaReduce('{ $f(i.asInstanceOf[Int { type X = ElemTop }]) }) }
i += 1
}
arr.asIArray
}
lazy val assigns = Seq.tabulate(sizeExpr.valueOrAbort) { i => (arr: Expr[Array[A[ElemTop]]]) =>
val ie = Expr(i)
'{
$arr($ie) = ${ Expr.betaReduce('{ $f($ie.asInstanceOf[Int { type X = ElemTop }]) }) }
}
}
lazy val withUnrolling = '{
given ClassTag[A[ElemTop]] = $classTagExpr
val arr = new Array[A[ElemTop]]($sizeExpr)
${ Expr.block(assigns.map(f => f('arr)).toList, '{ arr.asIArray }) }
}
if doUnrolling then processUnrollingDefs[Fields, IArray[A[ElemTop]]](withUnrolling) else noUnrolling
}
private def tabulateFoldLeftImpl[B: Type, Fields <: Tuple: Type, ElemTop: Type](
sizeExpr: Expr[Int],
startExpr: Expr[B],
unrolling: Expr[Boolean],
f: Expr[(B, Int { type X <: ElemTop }) => B]
)(using q: Quotes): Expr[B] = {
import q.reflect.*
val doUnrolling = unrolling.valueOrAbort
lazy val noUnrolling = '{
var res: B = $startExpr
var i: Int = 0
while (i < $sizeExpr) {
res = ${
Expr.betaReduce('{ $f(res, i.asInstanceOf[Int { type X <: ElemTop }]) })
}
i += 1
}
res
}
lazy val withUnrolling = Seq.tabulate(sizeExpr.valueOrAbort)(identity).foldLeft(startExpr) { case (acc, i) =>
ConstantType(IntConstant(i)).asType match {
case '[n] => Expr.betaReduce('{ $f($acc, ${ Expr(i) }.asInstanceOf[Int & n { type X = ElemTop }]) })
}
}
if doUnrolling then processUnrollingDefs[Fields, B](withUnrolling) else noUnrolling
}
private inline def defaultValue[A]: A =
val default = inline erasedValue[A] match {
case _: AnyRef => null
case _: Byte => 0.toByte
case _: Short => 0.toShort
case _: Int => 0
case _: Long => 0L
case _: Char => 0.toChar
case _: Float => 0F
case _: Double => 0D
case _: Boolean => false
case _: Unit => ()
}
default.asInstanceOf[A]
private def traverseKImpl[ElemTop: Type, F <: Tuple: Type, A[_]: Type, G[_]: Type, B[_]: Type](
fa: Expr[IArray[A[ElemTop]]],
f: Expr[A :~>: Compose2[G, B]],
G: Expr[Applicative[G]],
sizeExpr: Expr[Int],
classTagExpr: Expr[ClassTag[B[ElemTop]]]
)(using q: Quotes): Expr[G[IArray[B[ElemTop]]]] =
import q.reflect.*
val isFunctionIdentity = f match {
case '{ FunctionK.identity } => true
case _ => false
}
def fIfNotIdentity(obj: Expr[A[ElemTop]]): Expr[G[B[ElemTop]]] =
if isFunctionIdentity then obj.asExprOf[G[B[ElemTop]]]
else '{ $f($obj) }
def fElem(i: Expr[Int]): Expr[G[B[ElemTop]]] =
fIfNotIdentity('{ $fa.quickArrayIndex($i) })
def traverseId = {
val mapFun = '{ (i: Int { type X <: ElemTop }) =>
${ fElem('{ i }) }.asInstanceOf[B[i.X]]
}
tabulateKImpl[B, ElemTop, F](sizeExpr, mapFun, Expr(false), classTagExpr).asExprOf[G[IArray[B[ElemTop]]]]
}
def traverseEither[E: Type] = '{
given scala.reflect.ClassTag[B[ElemTop]] = $classTagExpr
var error: E = defaultValue[E]
var gotError = false
val arr = new Array[B[ElemTop]]($sizeExpr)
var i: Int = 0
while (i < $sizeExpr && !gotError) {
val res = ${ fElem('i) }.asInstanceOf[Either[E, B[ElemTop]]]
res match {
case Right(v) =>
arr(i) = v
case Left(e) =>
gotError = true
error = e
}
i += 1
}
val ret: Either[E, IArray[B[ElemTop]]] = if gotError then Left(error) else Right(arr.asIArray)
ret.asInstanceOf[G[IArray[B[ElemTop]]]]
}
def traverseOption = '{
given scala.reflect.ClassTag[B[ElemTop]] = $classTagExpr
var gotError = false
val arr = new Array[B[ElemTop]]($sizeExpr)
var i: Int = 0
while (i < $sizeExpr && !gotError) {
val res = ${ fElem('i) }.asInstanceOf[Option[B[ElemTop]]]
if (res.isEmpty) {
gotError = true
} else {
arr(i) = res.get
}
i += 1
}
val ret: Option[IArray[B[ElemTop]]] = if gotError then None else Some(arr.asIArray)
ret.asInstanceOf[G[IArray[B[ElemTop]]]]
}
G match {
case '{ cats.Invariant.catsInstancesForId } => traverseId
case '{ cats.catsInstancesForId } => traverseId
case '{ cats.instances.either.catsStdInstancesForEither[e] } => traverseEither[e]
case '{ cats.Invariant.catsMonadErrorForEither[e] } => traverseEither[e]
case '{ cats.instances.option.catsStdInstancesForOption } => traverseOption
case '{ cats.Invariant.catsInstancesForOption } => traverseOption
case _ =>
'{
val it = $fa.iterator
var acc: G[List[B[ElemTop]]] = $G.pure(List.empty[B[ElemTop]])
while (it.hasNext) {
val obj = it.next()
acc = $G.map2(${ fIfNotIdentity('obj) }, acc)((v, a) => v :: a)
}
$G.map(acc)(a => iteratorToArray(a.reverseIterator, $sizeExpr)(using $classTagExpr).asIArray)
}
}
end traverseKImpl
private def tabulateTraverseKImpl[ElemTop: Type, Fields <: Tuple: Type, G[_]: Type, B[_]: Type](
f: Expr[(i: Int { type X <: ElemTop }) => G[B[i.X]]],
unrolling: Expr[Boolean],
G: Expr[Applicative[G]],
sizeExpr: Expr[Int],
classTagExpr: Expr[ClassTag[B[ElemTop]]]
)(using q: Quotes): Expr[G[IArray[B[ElemTop]]]] =
import q.reflect.*
val doUnrolling = unrolling.valueOrAbort
val size = sizeExpr.valueOrAbort
def traverseId =
tabulateKImpl[B, ElemTop, Fields](
sizeExpr,
f.asExprOf[(i: Int { type X <: ElemTop }) => B[i.X]],
unrolling,
classTagExpr
).asExprOf[G[IArray[B[ElemTop]]]]
def fApply(i: Expr[Int])(using q: Quotes): Expr[G[B[ElemTop]]] =
Expr.betaReduce('{ $f($i.asInstanceOf[Int { type X = ElemTop }]) })
def traverseEither[E: Type] = {
lazy val withoutUnrolling = '{
given scala.reflect.ClassTag[B[ElemTop]] = $classTagExpr
var error: E = defaultValue[E]
var gotError = false
val arr = new Array[B[ElemTop]]($sizeExpr)
var i: Int = 0
while (i < $sizeExpr && !gotError) {
val res = ${ fApply('i) }.asInstanceOf[Either[E, B[ElemTop]]]
res match {
case Right(v) =>
arr(i) = v
case Left(e) =>
gotError = true
error = e
}
i += 1
}
val ret: Either[E, IArray[B[ElemTop]]] = if gotError then Left(error) else Right(arr.asIArray)
ret.asInstanceOf[G[IArray[B[ElemTop]]]]
}
lazy val withUnrolling = '{
val arr = new Array[B[ElemTop]]($sizeExpr)
val ret = ${
List.tabulate(size)(i => Expr(i)).foldRight('{ Right(arr.asIArray): Either[E, IArray[B[ElemTop]]] }) {
(i, acc) =>
'{
${ fApply(i) }.asInstanceOf[Either[E, B[ElemTop]]] match {
case Right(v) =>
arr($i) = v
$acc
case Left(e) =>
Left(e)
}
}
}
}
ret.asInstanceOf[G[IArray[B[ElemTop]]]]
}
if doUnrolling then processUnrollingDefs[Fields, G[IArray[B[ElemTop]]]](withUnrolling)
else withoutUnrolling
}
def traverseOption = {
lazy val withoutUnrolling = '{
given scala.reflect.ClassTag[B[ElemTop]] = $classTagExpr
var gotError = false
val arr = new Array[B[ElemTop]]($sizeExpr)
var i: Int = 0
while (i < $sizeExpr && !gotError) {
val res = ${ fApply('i) }.asInstanceOf[Option[B[ElemTop]]]
if (res.isEmpty) {
gotError = true
} else {
arr(i) = res.get
}
i += 1
}
val ret: Option[IArray[B[ElemTop]]] = if gotError then None else Some(arr.asIArray)
ret.asInstanceOf[G[IArray[B[ElemTop]]]]
}
lazy val withUnrolling = '{
val arr = new Array[B[ElemTop]]($sizeExpr)
val ret = ${
List.tabulate(size)(i => Expr(i)).foldRight('{ Some(arr.asIArray): Option[IArray[B[ElemTop]]] }) { (i, acc) =>
'{
${ fApply(i) }.asInstanceOf[Option[B[ElemTop]]] match {
case Some(v) =>
arr($i) = v
$acc
case None => None
}
}
}
}
ret.asInstanceOf[G[IArray[B[ElemTop]]]]
}
if doUnrolling then processUnrollingDefs[Fields, G[IArray[B[ElemTop]]]](withUnrolling)
else withoutUnrolling
}
G match {
case '{ cats.Invariant.catsInstancesForId } => traverseId
case '{ cats.catsInstancesForId } => traverseId
case '{ cats.instances.either.catsStdInstancesForEither[e] } => traverseEither[e]
case '{ cats.Invariant.catsMonadErrorForEither[e] } => traverseEither[e]
case '{ cats.instances.option.catsStdInstancesForOption } => traverseOption
case '{ cats.Invariant.catsInstancesForOption } => traverseOption
case _ =>
lazy val withoutUnrolling = '{
var acc: G[List[B[ElemTop]]] = $G.pure(List.empty[B[ElemTop]])
var i: Int = 0
while (i < $sizeExpr) {
acc = $G.map2(${ fApply('i) }, acc)((v, a) => v :: a)
i += 1
}
$G.map(acc)(a => iteratorToArray(a.reverseIterator, $sizeExpr)(using $classTagExpr).asIArray)
}
lazy val withUnrolling = {
val mapped = List.tabulate(size)(i => Expr(i)).foldLeft('{ $G.pure(List.empty[B[ElemTop]]) }) { (acc, i) =>
'{ $G.map2(${ fApply(i) }, $acc)((v, a) => v :: a) }
}
'{ $G.map($mapped)(a => iteratorToArray(a.reverseIterator, $sizeExpr)(using $classTagExpr).asIArray) }
}
if doUnrolling then processUnrollingDefs[Fields, G[IArray[B[ElemTop]]]](withUnrolling)
else withoutUnrolling
}
end tabulateTraverseKImpl
sealed trait InlineHKDGenericTypeclassOps[A, F <: Tuple] extends InlineHKDGeneric[A]:
override type Gen[F[_]] = IArray[F[ElemTop]]
override type Index = Int { type X <: ElemTop }
override inline def nameToIndex(name: this.Names): Index =
// TODO: Macro match
val n = names
val res = tabulateFoldLeft(Nil: List[(this.Names, Index)]) { (acc, idx) =>
val name = n.indexK(idx)
(name, idx) :: acc
}
res.find(_._1 == name).get._2
extension [A[_]](fa: Gen[A])
override inline def foldLeftK[B](inline b: B)(inline f: B => A :~>#: B): B =
var i: Int = 0
var res: B = b
while (i < size) {
res = f(res)(fa(i))
i += 1
}
res
end foldLeftK
override inline def foldMapK[B](inline f: A :~>#: B)(using B: Monoid[B]): B =
foldLeftK(B.empty)(b => [Z] => (az: A[Z]) => b.combine(f(az)))
extension [A](fa: Gen[Const[A]])
override inline def toListK: List[A] =
fa.toList
extension [A[_]](fa: Gen[A])
// Traverse
override inline def traverseK[G[_], B[_]](inline f: A :~>: Compose2[G, B])(
using inline G: Applicative[G],
classTag: ClassTag[B[ElemTop]]
): G[Gen[B]] =
${ traverseKImpl[ElemTop, F, A, G, B]('fa, 'f, 'G, 'size, 'classTag) }
end traverseK
extension [G[_], A[_]](fga: Gen[Compose2[G, A]])
override inline def sequenceK(using inline G: Applicative[G]): G[Gen[A]] =
fga.traverseK[G, A](FunctionK.identity)
extension [A[_]](fa: Gen[A])
// Representable
override inline def indexK(rep: Index): A[rep.X] =
fa.quickArrayIndex(rep).asInstanceOf[A[rep.X]]
override inline def tabulateK[A[_]](inline f: (i: Index) => A[i.X], inline unrolling: Boolean)(
using classTag: ClassTag[A[ElemTop]]
): Gen[A] =
${ tabulateKImpl[A, ElemTop, F]('size, 'f, 'unrolling, 'classTag) }
end tabulateK
override inline def tabulateFoldLeft[B](inline start: B, inline unrolling: Boolean)(inline f: (B, Index) => B): B =
${ tabulateFoldLeftImpl[B, F, ElemTop]('size, 'start, 'unrolling, 'f) }
end tabulateFoldLeft
override inline def tabulateTraverseK[G[_], B[_]](inline f: (i: Index) => G[B[i.X]], inline unrolling: Boolean)(
using inline G: Applicative[G],
classTag: ClassTag[B[ElemTop]]
): G[Gen[B]] =
${ tabulateTraverseKImpl[ElemTop, F, G, B]('f, 'unrolling, 'G, 'size, 'classTag) }
// Monad
extension [A[_]](fa: Gen[A])
override inline def flatMapK[B[_]](inline f: A :~>: Const[Gen[B]]): Gen[B] =
tabulateK(r => f(fa.indexK(r)).indexK(r))
// Distributive
extension [G[_]: Functor, A[_]](gfa: G[Gen[A]])
override inline def cosequenceK: Gen[Compose2[G, A]] =
tabulateK(r => gfa.map(fa => fa.indexK(r)))
// Applicative
extension [A[_]](a: ValueK[A])
override inline def pure: Gen[A] =
tabulateK(r => a[r.X]())
// Apply
extension [A[_]](fa: Gen[A])
override inline def map2K[B[_], Z[_]](fb: Gen[B])(inline f: [X] => (A[X], B[X]) => Z[X]): Gen[Z] =
tabulateK(r => f(fa.indexK(r), fb.indexK(r)))
// Functor
extension [A[_]](fa: Gen[A])
override inline def mapK[B[_]](inline f: A :~>: B): Gen[B] =
tabulateK(r => f(fa.indexK(r)))
end InlineHKDGenericTypeclassOps
private[perspective] type Names[ElemLabels <: Tuple] = Helpers.TupleUnionLub[ElemLabels, String, Nothing]
private[perspective] inline def stringToName[ElemLabels <: Tuple](
s: String
): Option[Names[ElemLabels]] =
val namesSet = Helpers.constValueTupleToSet[ElemLabels, String]
Option.when(namesSet(s))(s.asInstanceOf[Names[ElemLabels]])
end InlineHKDGeneric
/**
* A type like [[HKDProductGeneric]] but where as much as possible is defined
* inline, and tries to generate as simple bytecode as possible. Might be more
* error prone, and produce bigger classes using this, but the bytecode is much
* simpler.
* @tparam A
* The type being abstracted over.
*/
trait InlineHKDProductGeneric[A] extends InlineHKDGeneric[A]:
/** Convert a value of [[A]] to the higher kinded representation. */
inline def to(a: A): Gen[Id]
/** Convert a value of the higher kinded representation to [[A]]. */
inline def from(gen: Gen[Id]): A
extension (a: A)
/** Equivalent to `to(a).indexK(index)`, but faster. */
inline def productElementId(index: Index): index.X
/**
* Like [[productElementId]], but gives back the exact type. Can only be
* used in unrolled calls.
*/
transparent inline def productElementIdExact(idx: Index): idx.X
/**
* Like an inline match, but delays the expansion slightly. The value inside
* must be a normal match. Can only be used in unrolled calls.
*/
inline def lateInlineMatch[B](inline a: B): B = InlineHKDGeneric.lateInlineMatch(a)
object InlineHKDProductGeneric:
transparent inline def apply[A](using gen: InlineHKDProductGeneric[A]): InlineHKDProductGeneric.Aux[A, gen.Gen] = gen
type Aux[A, Gen0[_[_]]] = InlineHKDProductGeneric[A] {
type Gen[B[_]] = Gen0[B]
}
transparent inline given derived[A](
using m: Mirror.ProductOf[A],
typeLength: TypeLength[m.MirroredElemTypes],
idClassTag: ClassTag[Helpers.TupleUnion[m.MirroredElemTypes, Nothing]]
): DerivedImpl[A, m.MirroredElemTypes, m.MirroredElemLabels, m.MirroredLabel, typeLength.Length] =
new DerivedImpl[A, m.MirroredElemTypes, m.MirroredElemLabels, m.MirroredLabel, typeLength.Length](
using m
)
class DerivedImpl[
A,
ElemTypes <: Tuple,
ElemLabels <: Tuple,
TypeName0 <: String,
Size0 <: Int
](
using val m: Mirror.ProductOf[A] {
type MirroredElemTypes = ElemTypes; type MirroredElemLabels = ElemLabels; type MirroredLabel = TypeName0
},
val idClassTag: ClassTag[Any]
) extends InlineHKDProductGeneric[A]
with InlineHKDGeneric.InlineHKDGenericTypeclassOps[A, ElemLabels]:
override type Gen[F[_]] = IArray[F[ElemTop]]
// override opaque type Index = Int { type X <: ElemTop }
override type ElemTop = Any
override type TypeName = TypeName0
override inline def typeName: TypeName = constValue[TypeName0]
override type Size = Size0
override inline def size: Size0 = constValue[Size0]
opaque type Names <: String = String
override inline def names: Gen[Const[Names]] =
Helpers.constValueTupleToIArray[ElemLabels, String].asInstanceOf[Gen[Const[Names]]]
override inline def stringToName(s: String): Option[Names] =
InlineHKDGeneric.stringToName[ElemLabels](s).asInstanceOf[Option[Names]] // In theory harmless cast
override type TupleRep = ElemTypes
override inline def genToTuple[F[_]](gen: Gen[F]): Helpers.TupleMap[TupleRep, F] =
Tuple.fromIArray(gen).asInstanceOf[Helpers.TupleMap[TupleRep, F]]
override inline def tupleToGen[F[_]](tuple: Helpers.TupleMap[TupleRep, F]): Gen[F] =
InlineHKDGeneric.asIArray(
InlineHKDGeneric.iteratorToArray(tuple.productIterator.asInstanceOf[Iterator[F[ElemTop]]], size)
)
override inline def genToScalaTuple[F[_]](gen: Gen[F]): Tuple.Map[TupleRep, F] =
Tuple.fromIArray(gen).asInstanceOf[Tuple.Map[TupleRep, F]]
override inline def scalaTupleToGen[F[_]](tuple: Tuple.Map[TupleRep, F]): Gen[F] =
InlineHKDGeneric.asIArray(
InlineHKDGeneric.iteratorToArray(tuple.productIterator.asInstanceOf[Iterator[F[ElemTop]]], size)
)
override inline def to(a: A): Gen[Id] =
InlineHKDGeneric.asIArray(
InlineHKDGeneric.iteratorToArray(
a.asInstanceOf[Product].productIterator.asInstanceOf[Iterator[Id[ElemTop]]],
size
)(using idClassTag)
)
override inline def from(a: Gen[Id]): A =
inline a match
case arr: IArray[Object] => m.fromProduct(ArrayProduct(arr))
case _ => m.fromProduct(ArrayProduct(a.map(_.asInstanceOf[Object])))
extension (a: A)
override inline def productElementId(index: Index): index.X =
a.asInstanceOf[Product].productElement(index).asInstanceOf[index.X]
override transparent inline def productElementIdExact(idx: Index): idx.X =
InlineHKDGeneric.productElementIdExact[A, ElemTop](a, idx)
override inline def summonInstances[F[_]]: Gen[F] =
Helpers.summonAllToIArray[ElemTypes, F].asInstanceOf[Gen[F]]
end DerivedImpl
/**
* A type like [[HKDSumGeneric]] but where as much as possible is defined
* inline, and tries to generate as simple bytecode as possible. Might be more
* error prone, and produce bigger classes using this, but the bytecode is much
* simpler.
* @tparam A
* The type being abstracted over.
*/
trait InlineHKDSumGeneric[A] extends InlineHKDGeneric[A]:
override type ElemTop <: A
/**
* Returns the index of a value. Because of soundness, this method can not be
* used if X = A. In that case, use [[indexOfA]] instead.
*/
inline def indexOf[X <: ElemTop](x: X): IndexAux[X]
/** Same as [[indexOf]] but also works for values of type A. */
inline def indexOfA(a: A): Index = indexOf(a.asInstanceOf[ElemTop])
/**
* Same as [[indexOfA]] but also essentially cats the value to the unknown
* type, allowing further operations on it that requires that it is a subtype
* of A.
*/
inline def indexOfACasting(a: A): InlineHKDSumGeneric.IndexOfACasting[Index, ElemTop]
/**
* Widen the higher kinded representation to a [[Const]] type of the top
* type.
*/
extension [F[+_]](gen: Gen[F])
inline def widenConst: Gen[Const[F[A]]] =
// This is safe. We can't use the widen method as it can't know about the contents of Gen, we do
gen.asInstanceOf[Gen[Const[F[A]]]]
/**
* Convert a value of [[A]] to the higher kinded representation. It will be
* Some in only one field, corresponding to the subtype passed in, and None
* in all the others.
*/
inline def to(a: A): Gen[Option] =
val index = indexOfA(a)
// This cast is safe as we know A = Z
tabulateK(i => if i == index then Some(a.asInstanceOf[i.X]) else None)
/**
* Convert a value of the higher kinded representation to [[A]]. Will only
* return Some if only one of the fields is Some and the rest is None.
*/
inline def from(a: Gen[Option]): Option[A]
//noinspection ScalaUnusedSymbol,DuplicatedCode
object InlineHKDSumGeneric:
trait IndexOfACasting[Index <: Any { type X <: ElemTop }, ElemTop] {
type X0 <: ElemTop
val index: Index { type X = X0 }
val value: X0
}
object IndexOfACasting {
class IndexOfACastingImpl[Index <: Any { type X <: ElemTop }, ElemTop, X1 <: ElemTop](
val index: Index { type X = X1 },
val value: X1
) extends IndexOfACasting[Index, ElemTop] {
type X0 = X1
}
}
def apply[A](using gen: InlineHKDSumGeneric[A]): InlineHKDSumGeneric.Aux[A, gen.Gen] = gen
type Aux[A, Gen0[_[_]]] = InlineHKDSumGeneric[A] {
type Gen[B[_]] = Gen0[B]
}
transparent inline given derived[A](
using m: Mirror.SumOf[A],
typeLength: TypeLength[m.MirroredElemTypes],
idClassTag: ClassTag[Helpers.TupleUnionLub[m.MirroredElemTypes, A, Nothing]]
): DerivedImpl[A, m.MirroredElemTypes, m.MirroredElemLabels, m.MirroredLabel, typeLength.Length] =
new DerivedImpl[A, m.MirroredElemTypes, m.MirroredElemLabels, m.MirroredLabel, typeLength.Length](
using m,
idClassTag
)
class DerivedImpl[A, ElemTypes <: Tuple, ElemLabels <: Tuple, TypeName0 <: String, Size0 <: Int](
using val m: Mirror.SumOf[A] {
type MirroredElemTypes = ElemTypes
type MirroredElemLabels = ElemLabels
type MirroredLabel = TypeName0
},
val idClassTag: ClassTag[Helpers.TupleUnionLub[ElemTypes, A, Nothing]]
) extends InlineHKDSumGeneric[A]
with InlineHKDGeneric.InlineHKDGenericTypeclassOps[A, ElemLabels]:
override type Gen[F[_]] = IArray[F[ElemTop]]
override type ElemTop = Helpers.TupleUnionLub[ElemTypes, A, Nothing]
override type TypeName = m.MirroredLabel
override inline def typeName: TypeName = constValue[m.MirroredLabel]
override type Size = Size0
override inline def size: Size0 = constValue[Size0]
opaque type Names <: String = String
override inline def names: Gen[Const[Names]] =
Helpers.constValueTupleToIArray[ElemLabels, String].asInstanceOf[Gen[Const[Names]]]
override inline def stringToName(s: String): Option[Names] =
InlineHKDGeneric.stringToName[ElemLabels](s)
override type TupleRep = ElemTypes
override inline def genToTuple[F[_]](gen: Gen[F]): Helpers.TupleMap[TupleRep, F] =
Tuple.fromIArray(gen).asInstanceOf[Helpers.TupleMap[TupleRep, F]]
override inline def tupleToGen[F[_]](tuple: Helpers.TupleMap[TupleRep, F]): Gen[F] =
InlineHKDGeneric.asIArray(
InlineHKDGeneric.iteratorToArray(tuple.productIterator.asInstanceOf[Iterator[F[ElemTop]]], size)
)
override inline def genToScalaTuple[F[_]](gen: Gen[F]): Tuple.Map[TupleRep, F] =
Tuple.fromIArray(gen).asInstanceOf[Tuple.Map[TupleRep, F]]
override inline def scalaTupleToGen[F[_]](tuple: Tuple.Map[TupleRep, F]): Gen[F] =
InlineHKDGeneric.asIArray(
InlineHKDGeneric.iteratorToArray(tuple.productIterator.asInstanceOf[Iterator[F[ElemTop]]], size)
)
inline def from(a: Gen[Option]): Option[A] =
val res = a.widenConst.flatten
if res.length == 1 then Some(res.head)
else None
end from
inline def indexOf[X <: ElemTop](x: X): IndexAux[X] =
m.ordinal(x).asInstanceOf[IndexAux[X]]
inline def indexOfACasting(a: A): InlineHKDSumGeneric.IndexOfACasting[Index, ElemTop] =
val idx = indexOfA(a)
new InlineHKDSumGeneric.IndexOfACasting.IndexOfACastingImpl[Index, ElemTop, idx.X](idx, a.asInstanceOf[idx.X])
override inline def summonInstances[F[_]]: Gen[F] =
Helpers.summonAllToIArray[ElemTypes, F].asInstanceOf[Gen[F]]
end DerivedImpl