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

Term.betaReduce not working for curried context functions #17506

Closed
prolativ opened this issue May 15, 2023 · 3 comments · Fixed by #18121
Closed

Term.betaReduce not working for curried context functions #17506

prolativ opened this issue May 15, 2023 · 3 comments · Fixed by #18121
Assignees
Labels
area:metaprogramming:reflection Issues related to the quotes reflection API itype:bug
Milestone

Comments

@prolativ
Copy link
Contributor

Compiler version

3.3.1-RC1-bin-20230514-b0ccf40-NIGHTLY and before

Minimized code

Macro.scala

import scala.quoted.*

class Foo
class Bar

inline def fooCurriedExpr(f: Foo ?=> Bar ?=> Int): Int = ${ fooCurriedExprImpl('f) }

def fooCurriedExprImpl(f: Expr[Foo ?=> Bar ?=> Int])(using Quotes) =
  val applied = '{ ${f}(using new Foo)(using new Bar) }
  Expr.betaReduce(applied)

inline def fooCurriedReflect(f: Foo ?=> Bar ?=> Int): Int = ${ fooCurriedReflectImpl('f) }

def fooCurriedReflectImpl(f: Expr[Foo ?=> Bar ?=> Int])(using Quotes) =
  val applied = '{ ${f}(using new Foo)(using new Bar) }
  import quotes.reflect.*
  Term.betaReduce(applied.asTerm).get.asExprOf[Int]

inline def fooTupledExpr(f: (Foo, Bar) ?=> Int): Int = ${ fooTupledExprImpl('f) }

def fooTupledExprImpl(f: Expr[(Foo, Bar) ?=> Int])(using Quotes) =
  val applied = '{ ${f}(using new Foo, new Bar) }
  Expr.betaReduce(applied)

inline def fooTupledReflect(f: (Foo, Bar) ?=> Int): Int = ${ fooTupledReflectImpl('f) }

def fooTupledReflectImpl(f: Expr[(Foo, Bar) ?=> Int])(using Quotes) =
  val applied = '{ ${f}(using new Foo, new Bar) }
  import quotes.reflect.*
  Term.betaReduce(applied.asTerm).get.asExprOf[Int]

MacroTest.scala:

@main def run() =
  println(fooCurriedExpr(123))
  println(fooCurriedReflect(123))
  println(fooTupledExpr(123))
  println(fooTupledReflect(123))

Output

[error] MacroTest.scala:3:11
[error] Exception occurred while executing macro expansion.
[error] java.util.NoSuchElementException: None.get
[error]         at scala.None$.get(Option.scala:627)
[error]         at scala.None$.get(Option.scala:626)
[error]         at Macro$package$.fooCurriedReflectImpl(Macro.scala:17)
[error] 
[error]   println(fooCurriedReflect(123))
[error]           ^^^^^^^^^^^^^^^^^^^^^^

After commenting out println(fooCurriedReflect(123)) the code compiles successfully.

Expectation

The code should compile as is. Term.betaReduce should successfully reduce an application of a curried context function (returning Some rather than None) just a it does for a tupled context function.

@prolativ prolativ added itype:bug stat:needs triage Every issue needs to have an "area" and "itype" label area:metaprogramming:reflection Issues related to the quotes reflection API and removed stat:needs triage Every issue needs to have an "area" and "itype" label labels May 15, 2023
@jchyb
Copy link
Contributor

jchyb commented Jun 28, 2023

I did some quick experiments and I discovered two things:

  • both Term.betaReduce and Expr.betaReduce are unable to beta reduce the curried function. The difference lies only in how this behavior is handled in their APIs.
  • This behavior is not exclusive to context functions, for regular functions it seems to work the exact same (curried are not able to be beta reduced).

My test (mostly just printlns with inline parameters added for readability to the previous minimization):

Macro.scala

import scala.quoted.*

class Foo
class Bar

inline def fooCurriedExpr(inline f: Foo ?=> Bar ?=> Int): Int = ${ fooCurriedExprImpl('f) }

def fooCurriedExprImpl(f: Expr[Foo ?=> Bar ?=> Int])(using Quotes) =
  val applied = '{ ${f}(using new Foo)(using new Bar) }
  println("CtxFun curried,          initial: " + applied.show)
  val reduced = Expr.betaReduce(applied)
  println("CtxFun curried, after betaReduce: " + reduced.show)
  reduced

inline def fooTupledExpr(inline f: (Foo, Bar) ?=> Int): Int = ${ fooTupledExprImpl('f) }

def fooTupledExprImpl(f: Expr[(Foo, Bar) ?=> Int])(using Quotes) =
  val applied = '{ ${f}(using new Foo, new Bar) }
  println("CtxFun tupled,          initial: " + applied.show)
  val reduced = Expr.betaReduce(applied)
  println("CtxFun tupled, after betaReduce: " + reduced.show)
  reduced

// added non context function
inline def fooCurriedFun(inline f: Foo => Bar => Int): Int = ${fooCurriedFunImpl('f)}

def fooCurriedFunImpl(f: Expr[Foo => Bar => Int])(using Quotes) =
  val applied = '{ ${f}(new Foo)(new Bar) }
  println("Fun curried,          initial: " + applied.show)
  val reduced = Expr.betaReduce(applied)
  println("Fun curried, after betaReduce: " + reduced.show)
  reduced

MacroTest.scala

@main def run() =
  fooCurriedExpr(123)
  fooTupledExpr(123)

  fooCurriedFun((f: Foo) => (b: Bar) => (123))

Output

CtxFun curried,          initial: ((evidence$3: Foo) ?=> ((evidence$4: Bar) ?=> 123)).apply(using new Foo()).apply(using new Bar())
CtxFun curried, after betaReduce: ((evidence$3: Foo) ?=> ((evidence$4: Bar) ?=> 123)).apply(using new Foo()).apply(using new Bar())
CtxFun tupled,          initial: ((evidence$7: Foo, evidence$8: Bar) ?=> 123).apply(using new Foo(), new Bar())
CtxFun tupled, after betaReduce: {
  val evidence$7: Foo = new Foo()
  val evidence$8: Bar = new Bar()
  123
}
Fun curried,          initial: ((f: Foo) => ((b: Bar) => 123)).apply(new Foo()).apply(new Bar())
Fun curried, after betaReduce: ((f: Foo) => ((b: Bar) => 123)).apply(new Foo()).apply(new Bar())

@nicolasstucki
Copy link
Contributor

Interesting. The behavior is consistent and correct, but we do have room for improvement.

The core bera reduction logic seems to be able to reduce such expressions. For example the following code is reduced after the bata reduction phase.

class Foo
class Bar

val f1: Foo ?=> Bar ?=> Int = ???
def test1 = (123 : Foo ?=> Bar ?=> Int)(using new Foo)(using new Bar)

val f2: (Foo, Bar) ?=> Int = ???
def test2 = (123: (Foo, Bar) ?=> Int)(using new Foo, new Bar)

@nicolasstucki
Copy link
Contributor

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area:metaprogramming:reflection Issues related to the quotes reflection API itype:bug
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants