From fd545a9ae9f41a4bfdc2bf13a4554943f206f92b Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Thu, 9 Nov 2017 04:56:36 -0800 Subject: Make `Applicative` macros able to inject a configurable `Ctx` object, used in `Target` to inject the `dest` folder for the `T{...}` block to use --- core/src/main/scala/forge/define/Applicative.scala | 51 ++++++++++++++++------ core/src/main/scala/forge/define/Target.scala | 24 ++++++---- core/src/test/scala/forge/ApplicativeTests.scala | 10 +++-- 3 files changed, 60 insertions(+), 25 deletions(-) (limited to 'core') diff --git a/core/src/main/scala/forge/define/Applicative.scala b/core/src/main/scala/forge/define/Applicative.scala index dfa0cf84..b2034d13 100644 --- a/core/src/main/scala/forge/define/Applicative.scala +++ b/core/src/main/scala/forge/define/Applicative.scala @@ -6,28 +6,39 @@ import scala.annotation.compileTimeOnly import scala.collection.mutable import scala.reflect.macros.blackbox.Context +/** + * A generic Applicative-functor macro: translates calls to + * + * Applier.apply{ ... applyable1.apply() ... applyable2.apply() ... } + * + * into + * + * Applier.zipMap(applyable1, applyable2){ (a1, a2, ctx) => ... a1 ... a2 ... } + */ object Applicative { trait Applyable[+T]{ @compileTimeOnly("Target#apply() can only be used with a T{...} block") def apply(): T = ??? } - trait Applyer[W[_], T[_]]{ + trait Applyer[W[_], T[_], Ctx]{ + @compileTimeOnly("Target.ctx() can only be used with a T{...} block") + def ctx(): Ctx = ??? def underlying[A](v: W[A]): T[_] - def map[A, B](a: T[A], f: A => B): T[B] - def zipMap[R]()(f: () => R) = map(zip(), (_: Unit) => f()) - def zipMap[A, R](a: T[A])(f: A => R) = map(a, f) - def zipMap[A, B, R](a: T[A], b: T[B])(f: (A, B) => R) = map(zip(a, b), f.tupled) + def mapCtx[A, B](a: T[A])(f: (A, Ctx) => B): T[B] + def zipMap[R]()(cb: Ctx => R) = mapCtx(zip()){ (_, ctx) => cb(ctx)} + def zipMap[A, R](a: T[A])(f: (A, Ctx) => R) = mapCtx(a)(f) + def zipMap[A, B, R](a: T[A], b: T[B])(cb: (A, B, Ctx) => R) = mapCtx(zip(a, b)){case ((a, b), x) => cb(a, b, x)} def zipMap[A, B, C, R](a: T[A], b: T[B], c: T[C]) - (f: (A, B, C) => R) = map(zip(a, b, c), f.tupled) + (cb: (A, B, C, Ctx) => R) = mapCtx(zip(a, b, c)){case ((a, b, c), x) => cb(a, b, c, x)} def zipMap[A, B, C, D, R](a: T[A], b: T[B], c: T[C], d: T[D]) - (f: (A, B, C, D) => R) = map(zip(a, b, c, d), f.tupled) + (cb: (A, B, C, D, Ctx) => R) = mapCtx(zip(a, b, c, d)){case ((a, b, c, d), x) => cb(a, b, c, d, x)} def zipMap[A, B, C, D, E, R](a: T[A], b: T[B], c: T[C], d: T[D], e: T[E]) - (f: (A, B, C, D, E) => R) = map(zip(a, b, c, d, e), f.tupled) + (cb: (A, B, C, D, E, Ctx) => R) = mapCtx(zip(a, b, c, d, e)){case ((a, b, c, d, e), x) => cb(a, b, c, d, e, x)} def zipMap[A, B, C, D, E, F, R](a: T[A], b: T[B], c: T[C], d: T[D], e: T[E], f: T[F]) - (cb: (A, B, C, D, E, F) => R) = map(zip(a, b, c, d, e, f), cb.tupled) + (cb: (A, B, C, D, E, F, Ctx) => R) = mapCtx(zip(a, b, c, d, e, f)){case ((a, b, c, d, e, f), x) => cb(a, b, c, d, e, f, x)} def zipMap[A, B, C, D, E, F, G, R](a: T[A], b: T[B], c: T[C], d: T[D], e: T[E], f: T[F], g: T[G]) - (cb: (A, B, C, D, E, F, G) => R) = map(zip(a, b, c, d, e, f, g), cb.tupled) + (cb: (A, B, C, D, E, F, G, Ctx) => R) = mapCtx(zip(a, b, c, d, e, f, g)){case ((a, b, c, d, e, f, g), x) => cb(a, b, c, d, e, f, g, x)} def zip(): T[Unit] def zip[A](a: T[A]): T[Tuple1[A]] def zip[A, B](a: T[A], b: T[B]): T[(A, B)] @@ -38,8 +49,8 @@ object Applicative { def zip[A, B, C, D, E, F, G](a: T[A], b: T[B], c: T[C], d: T[D], e: T[E], f: T[F], g: T[G]): T[(A, B, C, D, E, F, G)] } - def impl[M[_], T: c.WeakTypeTag](c: Context) - (t: c.Expr[T]): c.Expr[M[T]] = { + def impl[M[_], T: c.WeakTypeTag, Ctx: c.WeakTypeTag](c: Context) + (t: c.Expr[T]): c.Expr[M[T]] = { import c.universe._ def rec(t: Tree): Iterator[c.Tree] = Iterator(t) ++ t.children.flatMap(rec(_)) @@ -50,6 +61,10 @@ object Applicative { // https://gist.github.com/olafurpg/596d62f87bf3360a29488b725fbc7608 val defs = rec(t.tree).filter(_.isDef).map(_.symbol).toSet + val ctxName = TermName(c.freshName("ctx")) + val ctxSym = c.internal.newTermSymbol(c.internal.enclosingOwner, ctxName) + c.internal.setInfo(ctxSym, weakTypeOf[Ctx]) + val transformed = c.internal.typingTransform(t.tree) { case (t @ q"$fun.apply()", api) if t.symbol == targetApplySym => @@ -71,13 +86,23 @@ object Applicative { c.internal.setFlag(tempSym, (1L << 44).asInstanceOf[c.universe.FlagSet]) bound.append((q"${c.prefix}.underlying($fun)", c.internal.valDef(tempSym))) tempIdent + case (t @ q"$prefix.ctx()", api) + if prefix.tpe.baseClasses.exists(_.fullName == "forge.define.Applicative.Applyer") => + + val tempIdent = Ident(ctxSym) + c.internal.setType(tempIdent, t.tpe) + c.internal.setFlag(ctxSym, (1L << 44).asInstanceOf[c.universe.FlagSet]) + tempIdent + case (t, api) => api.default(t) } val (exprs, bindings) = bound.unzip - val callback = c.typecheck(q"(..$bindings) => $transformed ") + val ctxBinding = c.internal.valDef(ctxSym) + + val callback = c.typecheck(q"(..$bindings, $ctxBinding) => $transformed ") val res = q"${c.prefix}.zipMap(..$exprs){ $callback }" diff --git a/core/src/main/scala/forge/define/Target.scala b/core/src/main/scala/forge/define/Target.scala index 9cb2dd1a..667fe467 100644 --- a/core/src/main/scala/forge/define/Target.scala +++ b/core/src/main/scala/forge/define/Target.scala @@ -29,7 +29,7 @@ abstract class Target[+T] extends Target.Ops[T] with Applyable[T]{ } -object Target extends Applicative.Applyer[Target, Target]{ +object Target extends Applicative.Applyer[Target, Target, Args]{ def underlying[A](v: Target[A]) = v type Cacher = forge.define.Cacher[Target[_]] @@ -39,18 +39,20 @@ object Target extends Applicative.Applyer[Target, Target]{ def evaluate(args: Args) = t0 } def apply[T](t: Target[T]): Target[T] = macro forge.define.Cacher.impl0[Target, T] - def command[T](t: T): Target[T] = macro Applicative.impl[Target, T] - def apply[T](t: T): Target[T] = macro impl[Target, T] - def impl[M[_], T: c.WeakTypeTag](c: Context) - (t: c.Expr[T]) - (implicit tt: c.WeakTypeTag[M[_]]): c.Expr[M[T]] = { - forge.define.Cacher.wrapCached(c)( - Applicative.impl(c)(t) + def command[T](t: T): Target[T] = macro Applicative.impl[Target, T, Args] + def apply[T](t: T): Target[T] = macro impl[Target, T, Args] + def impl[M[_], T: c.WeakTypeTag, Ctx: c.WeakTypeTag] + (c: Context) + (t: c.Expr[T]) + (implicit tt: c.WeakTypeTag[M[_]]): c.Expr[M[T]] = { + forge.define.Cacher.wrapCached[M, T](c)( + Applicative.impl[M, T, Ctx](c)(t) ) } abstract class Ops[+T]{ this: Target[T] => def map[V](f: T => V) = new Target.Mapped(this, f) + def mapDest[V](f: (T, Args) => V) = new Target.MappedDest(this, f) def filter(f: T => Boolean) = this def withFilter(f: T => Boolean) = this @@ -72,6 +74,10 @@ object Target extends Applicative.Applyer[Target, Target]{ def evaluate(args: Args) = f(args(0)) val inputs = List(source) } + class MappedDest[+T, +V](source: Target[T], f: (T, Args) => V) extends Target[V]{ + def evaluate(args: Args) = f(args(0), args) + val inputs = List(source) + } class Zipped[+T, +V](source1: Target[T], source2: Target[V]) extends Target[(T, V)]{ def evaluate(args: Args) = (args(0), args(1)) val inputs = List(source1, source2) @@ -106,7 +112,7 @@ object Target extends Applicative.Applyer[Target, Target]{ } } - def map[A, B](t: Target[A], f: A => B) = t.map(f) + def mapCtx[A, B](t: Target[A])(f: (A, Args) => B) = t.mapDest(f) def zip() = new Target.Target0(()) def zip[A](a: Target[A]) = a.map(Tuple1(_)) def zip[A, B](a: Target[A], b: Target[B]) = a.zip(b) diff --git a/core/src/test/scala/forge/ApplicativeTests.scala b/core/src/test/scala/forge/ApplicativeTests.scala index 1da21a5c..682414d3 100644 --- a/core/src/test/scala/forge/ApplicativeTests.scala +++ b/core/src/test/scala/forge/ApplicativeTests.scala @@ -7,13 +7,14 @@ import language.experimental.macros object ApplicativeTests extends TestSuite { implicit def optionToOpt[T](o: Option[T]): Opt[T] = new Opt(o) class Opt[T](val o: Option[T]) extends Applicative.Applyable[T] - object Opt extends define.Applicative.Applyer[Opt, Option]{ + object Opt extends define.Applicative.Applyer[Opt, Option, String]{ + val injectedCtx = "helloooo" def underlying[A](v: Opt[A]) = v.o - def apply[T](t: T): Option[T] = macro Applicative.impl[Option, T] + def apply[T](t: T): Option[T] = macro Applicative.impl[Option, T, String] type O[+T] = Option[T] - def map[A, B](a: O[A], f: A => B) = a.map(f) + def mapCtx[A, B](a: O[A])(f: (A, String) => B): Option[B] = a.map(f(_, injectedCtx)) def zip() = Some(()) def zip[A](a: O[A]) = a.map(Tuple1(_)) def zip[A, B](a: O[A], b: O[B]) = { @@ -53,6 +54,9 @@ object ApplicativeTests extends TestSuite { 'singleNone - assert(Opt("lol " + None()) == None) 'twoNones - assert(Opt("lol " + None() + None()) == None) } + 'context - { + assert(Opt(Opt.ctx() + Some("World")()) == Some("hellooooWorld")) + } 'capturing - { val lol = "lol " def hell(o: String) = "hell" + o -- cgit v1.2.3