Is there any trick to use macros in the same file they are defined?
Asked Answered
R

3

10

I have the following code:

object Macros {

  import scala.language.experimental.macros
  import scala.reflect.macros.blackbox

  def hello(): Unit = macro hello_impl

  def hello_impl(c: blackbox.Context)(): c.Expr[Unit] = {
    import c.universe._
    reify {
      println("Hello World!")
    }
  }
}


object Main {

  def main(args: Array[String]): Unit = {
    Macros.hello()
  }

}

It throws the following compilation error:

Error:(21, 17) macro implementation not found: hello
(the most common reason for that is that you cannot use macro implementations in the same compilation run that defines them)
    Macros.hello()
                ^

My question is: is there a way to "fool" the compiler in order to use macro expansions in the same file they are defined? My motivation is the following: I like to code in Scala, and lately I was submitting some problems in online judge Codeforces and some Scala constructions turned out to be very slow. So, I want to create some macro expansions in order to execute those constructions fast. But I cannot submit more than one file.

Thanks!

Ravi answered 13/1, 2015 at 10:16 Comment(7)
I don't think this is possible. Not sure but it sounds strange to create a macro (which needs to be compiled) and then use it when it's not compiled. However you might be able to define them in different files? Not sure though. One other thing when doing CF with Scala is that you should avoid stuff like filters, flatMap etc, since they are slow on larger collections. Stick to arrays and you should get near-java performance on larger collections.Pruchno
I agree with you about the "stick-to-arrays" policy. But it turns out that for-comprehensions are incredibly slow (probably because they are transformed into a map-flatMap-filter sequence) even for arrays and it's a big pain to use while all around to iterate. For example, it's preferable to write in Java than to write this codeforces.com/contest/166/submission/8828271 only because the for-comprehensions cause it to TLE.Ravi
Well one thing I see with that code is that Scanner is super-slow. Write your own class using inputstreams instead. Check out this: kattis.csc.kth.se/doc/src/Kattio.java (My school's engine's custom-reader)Pruchno
I normally use a BufferedInputReader. But in this task it doesn't matter, since the input is a single integer.Ravi
What's your time result in java for that problem?Pruchno
I never submitted it in Java before. But the point is that substituting the for-comprehension for the while shouldn't make the difference.Ravi
I feel that I can't help you, I hope someone else can shred some light on this! CheersPruchno
W
8

At the moment, this is not possible in production releases of Scala 2.10 and 2.11. We might be able to achieve this with scala.meta, but that's well in the future.

Weepy answered 13/1, 2015 at 18:35 Comment(0)
O
1

Depends on the meaning of "use". In principle you can use a macro (hello) in another method (foo) in the same file if you make the 2nd method a macro too (the 1st method call must be inside a quasiquote q"...", not reify{...})

import scala.language.experimental.macros
import scala.reflect.macros.blackbox // libraryDependencies += scalaOrganization.value % "scala-reflect" % scalaVersion.value

object Macros {
  def hello(): Unit = macro hello_impl

  def hello_impl(c: blackbox.Context)(): c.Expr[Unit] = {
    import c.universe._
    reify {
      println("Hello World!")
    }
  }
}


object Main {
  def foo(): Unit = macro fooImpl

  def fooImpl(c: blackbox.Context)(): c.Tree = {
    import c.universe._
    q"Macros.hello()"
  }
}

https://scastie.scala-lang.org/DmytroMitin/rom8yKvmSUGVk0GwtGzKqQ

The problem is that runnable def main(args: Array[String]): Unit can't be a macro.

Another option is reflective toolbox. A macro can't be defined with toolbox (actual compiler is needed for that)

import scala.reflect.runtime
import scala.reflect.runtime.universe.Quasiquote
import scala.tools.reflect.ToolBox // libraryDependencies += scalaOrganization.value % "scala-compiler" % scalaVersion.value

val rm = runtime.currentMirror
val tb = rm.mkToolBox()
tb.compile(q"""
  import scala.language.experimental.macros
  import scala.reflect.macros.blackbox.Context
  def bar(): Unit = macro barImpl
  def barImpl(c: Context)(): c.Tree = {
    import c.universe._
    EmptyTree
  }
""")
//macro implementation reference has wrong shape. required:
//macro [<static object>].<method name>[[<type args>]] or
//macro [<macro bundle>].<method name>[[<type args>]]

But a macro can be run in the same file using toolbox

import scala.language.experimental.macros
import scala.reflect.macros.blackbox

object Macros {
  def hello(): Unit = macro hello_impl

  def hello_impl(c: blackbox.Context)(): c.Expr[Unit] = {
    import c.universe._
    reify {
      println("Hello World!")
    }
  }
}

import scala.reflect.runtime
import scala.reflect.runtime.universe.Quasiquote
import scala.tools.reflect.ToolBox

object Main {
  def main(args: Array[String]): Unit = {
    val rm = runtime.currentMirror
    val tb = rm.mkToolBox()
    tb.eval(q"Macros.hello()")
  }
}
// Hello World!

https://scastie.scala-lang.org/DmytroMitin/rom8yKvmSUGVk0GwtGzKqQ/5

Regarding Codeforces the issue is that the compiler must be in classpath and calls via toolbox are comparatively slow.

Possible to identify/use Scala macros using reflection or similar? (Scala 3)

Orthopter answered 13/10, 2022 at 18:30 Comment(0)
M
0

use a macro in the same file as it is defined? it is not possible, even not in Scala 3. I refer to the description in Scala 3: Defining and Using Macros . The macro implementation can be in the same project, but not in the same file as the macro usage.

See "Defining and Using Macros" chapter and "Suspended Files" info box.

Mcadams answered 7/1, 2023 at 17:41 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.