You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
60 lines
2.5 KiB
60 lines
2.5 KiB
package green.thisfieldwas.embracingnondeterminism.data
|
|
|
|
import scala.annotation.StaticAnnotation
|
|
import scala.language.experimental.macros
|
|
import scala.reflect.macros.whitebox
|
|
|
|
/** Generates the following two extension methods for the Tuple{2-22} types:
|
|
* - (F[A], F[B], ...).tupled = F[(A, B, ...)]
|
|
* - (F[A], F[B], ...).mapN((A, B, ...) => Out) = F[Out]
|
|
*/
|
|
class GenerateTupleSyntax extends StaticAnnotation {
|
|
|
|
def macroTransform(annottees: Any*): Any = macro GenerateTupleSyntax.impl
|
|
}
|
|
|
|
object GenerateTupleSyntax {
|
|
|
|
def impl(c: whitebox.Context)(annottees: c.Tree*): c.Tree = {
|
|
import c.universe._
|
|
val trees: Seq[c.Tree] = 1.to(21).map { n =>
|
|
val tupleRange = 1.to(n + 1)
|
|
val tupleOpsClass = TypeName(s"Tuple${n + 1}Ops")
|
|
val typeDefF = TypeDef(
|
|
Modifiers(),
|
|
TypeName("F"),
|
|
List(TypeDef(Modifiers(), TypeName("_"), List(), TypeBoundsTree(EmptyTree, EmptyTree))),
|
|
TypeBoundsTree(EmptyTree, EmptyTree),
|
|
)
|
|
val tupleTypeDefs =
|
|
tupleRange.map(n => TypeDef(Modifiers(), TypeName(s"T$n"), List(), TypeBoundsTree(EmptyTree, EmptyTree)))
|
|
val tupleContextArgs = tupleRange.map(n => AppliedTypeTree(Ident(TypeName("F")), List(Ident(TypeName(s"T$n")))))
|
|
val tupleTypeArgs = tupleRange.map(n => TypeName(s"T$n"))
|
|
val tupleMembers = tupleRange.map(n => Select(q"t", TermName(s"_$n")))
|
|
|
|
q"""
|
|
implicit class $tupleOpsClass[$typeDefF, ..$tupleTypeDefs](val t: (..$tupleContextArgs)) extends AnyVal {
|
|
def tupled(implicit F: Applicative[F]): F[(..$tupleTypeArgs)] = mapN(${TermName(s"Tuple${n + 1}")}.apply)
|
|
def mapN[Out](f: (..$tupleTypeArgs) => Out)(implicit F: Applicative[F]): F[Out] =
|
|
${tupleMembers.tail.foldLeft(q"F.ap(F.pure(f.curried))(${tupleMembers.head})") { (acc, tupleMember) =>
|
|
q"F.ap($acc)($tupleMember)"
|
|
}}
|
|
}
|
|
"""
|
|
}
|
|
|
|
val classes = annottees.map {
|
|
case ClassDef(modifiers, typeName, typeDefs, template) =>
|
|
ClassDef(modifiers, typeName, typeDefs, Template(template.parents, template.self, template.body ++ trees))
|
|
case ModuleDef(modifiers, termName, template) =>
|
|
ModuleDef(modifiers, termName, Template(template.parents, template.self, template.body ++ trees))
|
|
case _ =>
|
|
c.abort(c.enclosingPosition, s"Encountered unexpected annottee type: only classes and objects are supported")
|
|
}
|
|
|
|
q"""
|
|
import green.thisfieldwas.embracingnondeterminism.control.Applicative
|
|
..$classes
|
|
"""
|
|
}
|
|
}
|
|
|