Skip to content

Term.betaReduce not working for curried context functions #17506

@prolativ

Description

@prolativ
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.

Activity

added
stat:needs triageEvery issue needs to have an "area" and "itype" label
and removed
stat:needs triageEvery issue needs to have an "area" and "itype" label
on May 15, 2023
jchyb

jchyb commented on Jun 28, 2023

@jchyb
Contributor

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

nicolasstucki commented on Jun 28, 2023

@nicolasstucki
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)
added this to the 3.5.0 milestone on May 10, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Relationships

None yet

    Participants

    @nicolasstucki@prolativ@Kordyjan@jchyb

    Issue actions

      `Term.betaReduce` not working for curried context functions · Issue #17506 · scala/scala3