Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace AsFunction implicit class with Expr.reduce #7299

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 `betaReduce` conversion that turns a tree
describing a function into a function mapping trees to trees.
```scala
object Expr {
...
implicit class AsFunction[...](...) { ... }
def betaReduce[...](...)(...): ... = ...
}
```
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.betaReduce(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.betaReduce` distributes applications of `Expr` over function arrows:
```scala
AsFunction(_).apply: Expr[S => T] => (Expr[S] => Expr[T])
Expr.betaReduce(_): Expr[(T1, ..., Tn) => R] => ((Expr[T1], ..., Expr[Tn]) => Expr[R])
```
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe call it distribute? From wikipedia:

K, Distribution Axiom: □(p → q) → (□p → □q).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

distribite is not precise, it only captures a generic property in the types. It would not describe what the method actually does.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also reduce itself is not a distribution, reduce(_) is the a distribution.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with @liufengyun. Reduce is counter intuitive.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you have another suggestion?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The following sentence does not read well, given the literal meaning of beta-reduction:

The Expr companion object contains a betaReduce conversion that turns a tree
describing a function into a function mapping trees to trees.

Its dual, let’s call it `reflect`, can be defined as follows:
```scala
Expand Down
35 changes: 23 additions & 12 deletions library/src/scala/quoted/Expr.scala
Original file line number Diff line number Diff line change
Expand Up @@ -28,20 +28,31 @@ 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]])
}
/** `Expr.betaReduce(f)(x1, ..., xn)` is functionally the same as `'{($f)($x1, ..., $xn)}`, however it optimizes this call
* by returning the result of beta-reducing `f(x1, ..., xn)` if `f` is a known lambda expression.
*
* `Expr.betaReduce` distributes applications of `Expr` over function arrows
* ```scala
* Expr.betaReduce(_): Expr[(T1, ..., Tn) => R] => ((Expr[T1], ..., Expr[Tn]) => Expr[R])
* ```
*/
def betaReduce[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]])
}
nicolasstucki marked this conversation as resolved.
Show resolved Hide resolved

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]])
}
/** `Expr.betaReduceGiven(f)(x1, ..., xn)` is functionally the same as `'{($f)(given $x1, ..., $xn)}`, however it optimizes this call
* by returning the result of beta-reducing `f(given x1, ..., xn)` if `f` is a known lambda expression.
*
* `Expr.betaReduceGiven` distributes applications of `Expr` over function arrows
* ```scala
* Expr.betaReduceGiven(_): Expr[(given T1, ..., Tn) => R] => ((Expr[T1], ..., Expr[Tn]) => Expr[R])
* ```
* Note: The
*/
def betaReduceGiven[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})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed, by reading this reduce confuses a lot.

def testImpl(f: Expr[(Int, Int) => Int])(given QuoteContext): Expr[Int] = Expr.betaReduce(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.betaReduce(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.betaReduce(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.betaReduce(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.betaReduce(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.betaReduce(f)('i)}
i += 1
}
while {
${f.apply('i)}
${Expr.betaReduce(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.betaReduce(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.betaReduce(f)('x)} && ${Expr.betaReduce(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.betaReduce(g)(Expr.betaReduce(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.betaReduce(f)('x)}) ${Expr.betaReduce(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.betaReduce(f)('x)} && ${Expr.betaReduce(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.betaReduce(g)(Expr.betaReduce(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.betaReduce(f)('x)}) ${Expr.betaReduce(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.betaReduce(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.betaReduce(fn.seal.cast[() => Int])()
case 1 => Expr.betaReduce(fn.seal.cast[Int => Int])('{0})
case 2 => Expr.betaReduce(fn.seal.cast[(Int, Int) => Int])('{0}, '{0})
case 3 => Expr.betaReduce(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.betaReduce(pre.seal.cast[() => Any])().unseal
case 1 => Expr.betaReduce(pre.seal.cast[Int => Any])('{0}).unseal
case 2 => Expr.betaReduce(pre.seal.cast[(Int, Int) => Any])('{0}, '{0}).unseal
case 3 => Expr.betaReduce(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.betaReduce(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.betaReduce(f2)(x)))
println(withQuoteContext(Expr.betaReduce(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.betaReduce(f3)(x)))
println(withQuoteContext(Expr.betaReduce(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.betaReduce(f4)(x)))
println(withQuoteContext(Expr.betaReduce(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.betaReduce(f4)(x)))
println(withQuoteContext(Expr.betaReduce(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.betaReduce(f)(x)))
println(withQuoteContext(Expr.betaReduce(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.betaReduce(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.betaReduce('k)(a))) } }}
def reflect[A](given Type[A]) = m => k => Effects[L].reflect[E] { Expr.betaReduce(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.betaReduce('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.betaReduce(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.betaReduce(f2)('n)} }
def f2(given QuoteContext): Expr[Int => Int] = '{ n => ${Expr.betaReduce(f3)('n)} }
def f3(given QuoteContext): Expr[Int => Int] = '{ n => ${Expr.betaReduce(f4)('n)} }
def f4(given QuoteContext): Expr[Int => Int] = '{ n => n }
}
Loading