Skip to content

Commit

Permalink
Replace AsFunction implicit class with Expr.reduce
Browse files Browse the repository at this point in the history
  • Loading branch information
nicolasstucki committed Sep 24, 2019
1 parent 10091bd commit 57b59b3
Show file tree
Hide file tree
Showing 26 changed files with 104 additions and 111 deletions.
14 changes: 5 additions & 9 deletions docs/docs/reference/metaprogramming/macros.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,24 +135,20 @@ expressiveness.

### From `Expr`s to Functions and Back

The `Expr` companion object contains an implicit `AsFunction` conversion that turns a tree
The `Expr` companion object contains a `reduce` conversion that turns a tree
describing a function into a function mapping trees to trees.
```scala
object Expr {
...
implicit class AsFunction[...](...) { ... }
def reduce[...](...)(...): ... =
}
```
This decorator gives `Expr` the `apply` operation of an applicative functor, where `Expr`s
over function types can be applied to `Expr` arguments. The definition
of `AsFunction(f).apply(x)` is assumed to be functionally the same as
The definition of `Expr.reduce(f)(x)` is assumed to be functionally the same as
`'{($f)($x)}`, however it should optimize this call by returning the
result of beta-reducing `f(x)` if `f` is a known lambda expression.

The `AsFunction` decorator distributes applications of `Expr` over function
arrows:
`Expr.reduce` distributes applications of `Expr` over function arrows:
```scala
AsFunction(_).apply: Expr[S => T] => (Expr[S] => Expr[T])
Expr.reduce(_).apply: Expr[(T1, ..., Tn) => R] => ((Expr[T1], ..., Expr[Tn]) => Expr[R])
```
Its dual, let’s call it `reflect`, can be defined as follows:
```scala
Expand Down
20 changes: 8 additions & 12 deletions library/src/scala/quoted/Expr.scala
Original file line number Diff line number Diff line change
Expand Up @@ -28,20 +28,16 @@ package quoted {
/** Converts a tuple `(T1, ..., Tn)` to `(Expr[T1], ..., Expr[Tn])` */
type TupleOfExpr[Tup <: Tuple] = Tuple.Map[Tup, [X] =>> (given QuoteContext) => Expr[X]]

implicit class AsFunction[F, Args <: Tuple, R](f: Expr[F])(given tf: TupledFunction[F, Args => R], qctx: QuoteContext) {
/** Beta-reduces the function appication. Generates the an expression only containing the body of the function */
def apply[G](given tg: TupledFunction[G, TupleOfExpr[Args] => Expr[R]]): G = {
import qctx.tasty._
tg.untupled(args => qctx.tasty.internal.betaReduce(f.unseal, args.toArray.toList.map(_.asInstanceOf[QuoteContext => Expr[_]](qctx).unseal)).seal.asInstanceOf[Expr[R]])
}
/** Beta-reduces the function appication. Generates the an expression only containing the body of the function */
def reduce[F, Args <: Tuple, R, G](f: Expr[F])(given tf: TupledFunction[F, Args => R], tg: TupledFunction[G, TupleOfExpr[Args] => Expr[R]], qctx: QuoteContext): G = {
import qctx.tasty._
tg.untupled(args => qctx.tasty.internal.betaReduce(f.unseal, args.toArray.toList.map(_.asInstanceOf[QuoteContext => Expr[_]](qctx).unseal)).seal.asInstanceOf[Expr[R]])
}

implicit class AsContextualFunction[F, Args <: Tuple, R](f: Expr[F])(given tf: TupledFunction[F, (given Args) => R], qctx: QuoteContext) {
/** Beta-reduces the function appication. Generates the an expression only containing the body of the function */
def apply[G](given tg: TupledFunction[G, TupleOfExpr[Args] => Expr[R]]): G = {
import qctx.tasty._
tg.untupled(args => qctx.tasty.internal.betaReduce(f.unseal, args.toArray.toList.map(_.asInstanceOf[QuoteContext => Expr[_]](qctx).unseal)).seal.asInstanceOf[Expr[R]])
}
/** Beta-reduces the function appication. Generates the an expression only containing the body of the function */
def reduceGiven[F, Args <: Tuple, R, G](f: Expr[F])(given tf: TupledFunction[F, (given Args) => R], tg: TupledFunction[G, TupleOfExpr[Args] => Expr[R]], qctx: QuoteContext): G = {
import qctx.tasty._
tg.untupled(args => qctx.tasty.internal.betaReduce(f.unseal, args.toArray.toList.map(_.asInstanceOf[QuoteContext => Expr[_]](qctx).unseal)).seal.asInstanceOf[Expr[R]])
}

/** Returns a null expresssion equivalent to `'{null}` */
Expand Down
2 changes: 1 addition & 1 deletion tests/pos/i6783.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import scala.quoted._

def testImpl(f: Expr[(Int, Int) => Int])(given QuoteContext): Expr[Int] = f('{1}, '{2})
def testImpl(f: Expr[(Int, Int) => Int])(given QuoteContext): Expr[Int] = Expr.reduce(f)('{1}, '{2})

inline def test(f: (Int, Int) => Int) = ${
testImpl('f)
Expand Down
2 changes: 1 addition & 1 deletion tests/run-macros/gestalt-optional-staging/Macro_1.scala
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ object Optional {
// FIXME fix issue #5097 and enable private
/*private*/ def mapImpl[A >: Null : Type, B >: Null : Type](opt: Expr[Optional[A]], f: Expr[A => B])(given QuoteContext): Expr[Optional[B]] = '{
if ($opt.isEmpty) new Optional(null)
else new Optional(${f('{$opt.value})})
else new Optional(${Expr.reduce(f)('{$opt.value})})
}

}
4 changes: 2 additions & 2 deletions tests/run-macros/i4734/Macro_1.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ object Macros {
while (i < size) {
${
for (j <- new UnrolledRange(0, unrollSize)) '{
val index = i + ${j}
val index = i + $j
val element = ($seq)(index)
${ f('element) } // or `($f)(element)` if `f` should not be inlined
${ Expr.reduce(f)('element) } // or `($f)(element)` if `f` should not be inlined
}
}
i += ${unrollSize}
Expand Down
2 changes: 1 addition & 1 deletion tests/run-macros/i4735/Macro_1.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ object Macro {
${
for (j <- new UnrolledRange(0, unrollSize)) '{
val element = ($seq)(i + ${j})
${f('element)} // or `($f)(element)` if `f` should not be inlined
${Expr.reduce(f)('element)} // or `($f)(element)` if `f` should not be inlined
}
}
i += ${unrollSize}
Expand Down
2 changes: 1 addition & 1 deletion tests/run-macros/i7008/macro_1.scala
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,5 @@ def mcrProxy(expr: Expr[Boolean])(given QuoteContext): Expr[Unit] = {
def mcrImpl[T](func: Expr[Seq[Box[T]] => Unit], expr: Expr[T])(given ctx: QuoteContext, tt: Type[T]): Expr[Unit] = {
import ctx.tasty._
val arg = Expr.ofSeq(Seq('{(Box($expr))}))
func(arg)
Expr.reduce(func)(arg)
}
4 changes: 2 additions & 2 deletions tests/run-macros/quote-inline-function/quoted_1.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ object Macros {
var i = $start
val j = $end
while (i < j) {
${f.apply('i)}
${Expr.reduce(f)('i)}
i += 1
}
while {
${f.apply('i)}
${Expr.reduce(f)('i)}
i += 1
i < j
} do ()
Expand Down
2 changes: 1 addition & 1 deletion tests/run-macros/quote-matcher-symantics-2/quoted_1.scala
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ object StringNum extends Symantics[String] {
def value(x: Int)(given QuoteContext): Expr[String] = Expr(x.toString)
def plus(x: Expr[String], y: Expr[String])(given QuoteContext): Expr[String] = '{ s"${$x} + ${$y}" } // '{ x + " + " + y }
def times(x: Expr[String], y: Expr[String])(given QuoteContext): Expr[String] = '{ s"${$x} * ${$y}" }
def app(f: Expr[String => String], x: Expr[String])(given QuoteContext): Expr[String] = f(x) // functions are beta reduced
def app(f: Expr[String => String], x: Expr[String])(given QuoteContext): Expr[String] = Expr.reduce(f)(x)
def lam(body: Expr[String] => Expr[String])(given QuoteContext): Expr[String => String] = '{ (x: String) => ${body('x)} }
}

Expand Down
6 changes: 3 additions & 3 deletions tests/run-macros/quote-matching-optimize-1/Macro_1.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@ object Macro {

def optimize(x: Expr[Any]): Expr[Any] = x match {
case '{ type $t; ($ls: List[`$t`]).filter($f).filter($g) } =>
optimize('{ $ls.filter(x => ${f('x)} && ${g('x)}) })
optimize('{ $ls.filter(x => ${Expr.reduce(f)('x)} && ${Expr.reduce(g)('x)}) })

case '{ type $t; type $u; type $v; ($ls: List[`$t`]).map[`$u`]($f).map[`$v`]($g) } =>
optimize('{ $ls.map(x => ${g(f('x))}) })
optimize('{ $ls.map(x => ${Expr.reduce(g)(Expr.reduce(f)('x))}) })

case '{ type $t; ($ls: List[`$t`]).filter($f).foreach[Unit]($g) } =>
optimize('{ $ls.foreach(x => if (${f('x)}) ${g('x)} else ()) })
optimize('{ $ls.foreach(x => if (${Expr.reduce(f)('x)}) ${Expr.reduce(g)('x)} else ()) })

case _ => x
}
Expand Down
6 changes: 3 additions & 3 deletions tests/run-macros/quote-matching-optimize-2/Macro_1.scala
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@ object Macro {

def optimize(x: Expr[Any]): Expr[Any] = x match {
case '{ ($ls: List[$t]).filter($f).filter($g) } =>
optimize('{ $ls.filter(x => ${f('x)} && ${g('x)}) })
optimize('{ $ls.filter(x => ${Expr.reduce(f)('x)} && ${Expr.reduce(g)('x)}) })

case '{ type $u; type $v; ($ls: List[$t]).map[`$u`]($f).map[`$v`]($g) } =>
optimize('{ $ls.map(x => ${g(f('x))}) })
optimize('{ $ls.map(x => ${Expr.reduce(g)(Expr.reduce(f)('x))}) })

case '{ ($ls: List[$t]).filter($f).foreach[$u]($g) } =>
optimize('{ $ls.foreach[Any](x => if (${f('x)}) ${g('x)} else ()) })
optimize('{ $ls.foreach[Any](x => if (${Expr.reduce(f)('x)}) ${Expr.reduce(g)('x)} else ()) })

case _ => x
}
Expand Down
2 changes: 1 addition & 1 deletion tests/run-macros/quote-unrolled-foreach/quoted_1.scala
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ object Macro {
println("<log> start loop")
${
@tailrec def loop(j: Int, acc: Expr[Unit]): Expr[Unit] =
if (j >= 0) loop(j - 1, '{ ${f('{$seq(i + ${j})})}; $acc })
if (j >= 0) loop(j - 1, '{ ${Expr.reduce(f)('{$seq(i + ${j})})}; $acc })
else acc
loop(unrollSize - 1, '{})
}
Expand Down
16 changes: 8 additions & 8 deletions tests/run-macros/tasty-seal-method/quoted_1.scala
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ object Asserts {
fn.tpe.widen match {
case Type.IsMethodType(_) =>
args.size match {
case 0 => fn.seal.cast[() => Int].apply()
case 1 => fn.seal.cast[Int => Int].apply('{0})
case 2 => fn.seal.cast[(Int, Int) => Int].apply('{0}, '{0})
case 3 => fn.seal.cast[(Int, Int, Int) => Int].apply('{0}, '{0}, '{0})
case 0 => Expr.reduce(fn.seal.cast[() => Int])()
case 1 => Expr.reduce(fn.seal.cast[Int => Int])('{0})
case 2 => Expr.reduce(fn.seal.cast[(Int, Int) => Int])('{0}, '{0})
case 3 => Expr.reduce(fn.seal.cast[(Int, Int, Int) => Int])('{0}, '{0}, '{0})
}
}
case _ => x
Expand All @@ -35,10 +35,10 @@ object Asserts {
case Apply(fn, args) =>
val pre = rec(fn)
args.size match {
case 0 => pre.seal.cast[() => Any].apply().unseal
case 1 => pre.seal.cast[Int => Any].apply('{0}).unseal
case 2 => pre.seal.cast[(Int, Int) => Any].apply('{0}, '{0}).unseal
case 3 => pre.seal.cast[(Int, Int, Int) => Any].apply('{0}, '{0}, '{0}).unseal
case 0 => Expr.reduce(pre.seal.cast[() => Any])().unseal
case 1 => Expr.reduce(pre.seal.cast[Int => Any])('{0}).unseal
case 2 => Expr.reduce(pre.seal.cast[(Int, Int) => Any])('{0}, '{0}).unseal
case 3 => Expr.reduce(pre.seal.cast[(Int, Int, Int) => Any])('{0}, '{0}, '{0}).unseal
}
case _ => term
}
Expand Down
2 changes: 1 addition & 1 deletion tests/run-macros/tasty-unsafe-let/quoted_1.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ object Macros {

import qctx.tasty.{let => letTerm}
letTerm(rhsTerm) { rhsId =>
body(rhsId.seal.asInstanceOf[Expr[T]]).unseal // Dangerous uncheked cast!
Expr.reduce(body)(rhsId.seal.asInstanceOf[Expr[T]]).unseal // Dangerous uncheked cast!
}.seal.cast[Unit]
}

Expand Down
4 changes: 2 additions & 2 deletions tests/run-staging/i3876-b.scala
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ object Test {
f
}

println(run(f2(x)))
println(withQuoteContext(f2(x).show))
println(run(Expr.reduce(f2)(x)))
println(withQuoteContext(Expr.reduce(f2)(x).show))
}
}
4 changes: 2 additions & 2 deletions tests/run-staging/i3876-c.scala
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ object Test {
f
}

println(run(f3(x)))
println(withQuoteContext(f3(x).show)) // TODO improve printer
println(run(Expr.reduce(f3)(x)))
println(withQuoteContext(Expr.reduce(f3)(x).show)) // TODO improve printer
}
}
4 changes: 2 additions & 2 deletions tests/run-staging/i3876-d.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ object Test {
def f4(given QuoteContext): Expr[Int => Int] = '{
inlineLambda
}
println(run(f4(x)))
println(withQuoteContext(f4(x).show))
println(run(Expr.reduce(f4)(x)))
println(withQuoteContext(Expr.reduce(f4)(x).show))
}

inline def inlineLambda <: Int => Int = x => x + x
Expand Down
4 changes: 2 additions & 2 deletions tests/run-staging/i3876-e.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ object Test {
def f4(given QuoteContext): Expr[Int => Int] = '{
inlineLambda
}
println(run(f4(x)))
println(withQuoteContext(f4(x).show))
println(run(Expr.reduce(f4)(x)))
println(withQuoteContext(Expr.reduce(f4)(x).show))
}

inline def inlineLambda <: Int => Int = x => x + x
Expand Down
4 changes: 2 additions & 2 deletions tests/run-staging/i3876.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ object Test {

def f(given QuoteContext): Expr[Int => Int] = '{ (x: Int) => x + x }

println(run(f(x)))
println(withQuoteContext(f(x).show))
println(run(Expr.reduce(f)(x)))
println(withQuoteContext(Expr.reduce(f)(x).show))
}
}
2 changes: 1 addition & 1 deletion tests/run-staging/i5144b.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import scala.quoted.staging._

object Test {
given Toolbox = Toolbox.make(getClass.getClassLoader)
def eval1(ff: Expr[Int => Int])(given QuoteContext): Expr[Int] = ff('{42})
def eval1(ff: Expr[Int => Int])(given QuoteContext): Expr[Int] = Expr.reduce(ff)('{42})

def peval1()(given QuoteContext): Expr[Unit] = '{
def f(x: Int): Int = ${eval1('f)}
Expand Down
6 changes: 3 additions & 3 deletions tests/run-staging/i6281.scala
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ object Test extends App {
}
// for reify, we need type tags for E and also strangely for L.
implicit def cons [E, L <: HList](given Effects[L])(given Type[E])(given Type[L])(given QuoteContext): Effects[E :: L] = new Effects[E :: L] {
def reify[A](given Type[A]) = m => '{ k => ${ Effects[L].reify[E] { m( a => Effects[L].reflect[E]('k(a))) } }}
def reflect[A](given Type[A]) = m => k => Effects[L].reflect[E] { m('{ a => ${ Effects[L].reify[E]( k('a)) } })}
def reify[A](given Type[A]) = m => '{ k => ${ Effects[L].reify[E] { m( a => Effects[L].reflect[E](Expr.reduce('k)(a))) } }}
def reflect[A](given Type[A]) = m => k => Effects[L].reflect[E] { Expr.reduce(m)('{ a => ${ Effects[L].reify[E]( k('a)) } })}
}
def Effects[L <: HList](given Effects[L]): Effects[L] = summon[Effects[L]]

Expand All @@ -45,7 +45,7 @@ object Test extends App {
val effects = cons[Boolean, RS2](given cons[Int, String :: HNil](given cons[String, HNil](given empty)))
println(effects.reify[Int] { m }.show)

val res : Expr[Stm[Int, RS]] = '{ k => ${ Effects[RS2].reify[Boolean] { m(a => Effects[RS2].reflect[Boolean]('k(a))) }}}
val res : Expr[Stm[Int, RS]] = '{ k => ${ Effects[RS2].reify[Boolean] { m(a => Effects[RS2].reflect[Boolean](Expr.reduce('k)(a))) }}}
println(res.show)
}

Expand Down
2 changes: 1 addition & 1 deletion tests/run-staging/quote-ackermann-1.scala
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ object Test {
def ackermann(m: Int)(given QuoteContext): Expr[Int => Int] = {
if (m == 0) '{ n => n + 1 }
else '{ n =>
def `ackermann(m-1)`(n: Int): Int = ${ackermann(m - 1)('n)} // Expr[Int => Int] applied to Expr[Int]
def `ackermann(m-1)`(n: Int): Int = ${Expr.reduce(ackermann(m - 1))('n)} // Expr[Int => Int] applied to Expr[Int]
def `ackermann(m)`(n: Int): Int =
if (n == 0) `ackermann(m-1)`(1) else `ackermann(m-1)`(`ackermann(m)`(n - 1))
`ackermann(m)`(n)
Expand Down
6 changes: 3 additions & 3 deletions tests/run-staging/quote-fun-app-1.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ object Test {
println(f(43))
}

def f1(given QuoteContext): Expr[Int => Int] = '{ n => ${f2('n)} }
def f2(given QuoteContext): Expr[Int => Int] = '{ n => ${f3('n)} }
def f3(given QuoteContext): Expr[Int => Int] = '{ n => ${f4('n)} }
def f1(given QuoteContext): Expr[Int => Int] = '{ n => ${Expr.reduce(f2)('n)} }
def f2(given QuoteContext): Expr[Int => Int] = '{ n => ${Expr.reduce(f3)('n)} }
def f3(given QuoteContext): Expr[Int => Int] = '{ n => ${Expr.reduce(f4)('n)} }
def f4(given QuoteContext): Expr[Int => Int] = '{ n => n }
}
Loading

0 comments on commit 57b59b3

Please sign in to comment.