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

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
"""
}
}