Объединение переданного тела функции в выражение, переписанное макросом

Я играл с новыми функциями макросов Scala 2.11. Я хотел посмотреть, смогу ли я сделать следующее переписывание:

forRange(0 to 10) { i => println(i) }

// into

val iter = (0 to 10).iterator
while (iter.hasNext) {
  val i = iter.next
  println(i)
}

Я думаю, что я довольно близко подобрался к этому макросу:

def _forRange[A](c: BlackboxContext)(range: c.Expr[Range])(func: c.Expr[Int => A]): c.Expr[Unit] = {
  import c.universe._

  val tree = func.tree match {
    case q"($i: $t) => $body" => q"""
        val iter = ${range}.iterator
        while (iter.hasNext) {
          val $i = iter.next
          $body
        }
      """
    case _ => q""
  }

  c.Expr(tree)
}

Это дает следующий результат при вызове как forRange(0 to 10) { i => println(i) } (по крайней мере, это то, что функция show дает мне в результирующем дереве):

{
  val iter = scala.this.Predef.intWrapper(0).to(10).iterator;
  while$1(){
    if (iter.hasNext)
      {
        {
          val i = iter.next;
          scala.this.Predef.println(i)
        };
        while$1()
      }
    else
      ()
  }
}

Это похоже, что это должно работать, но есть конфликт между моей определенной вручную val i и i на которую ссылается в теле встроенной функции. Я получаю следующую ошибку:

ReplGlobal.abort: значение символа i не существует в $line38.$read$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw. ошибка: значение символа i не существует в scala.reflect.internal.FatalError: значение символа i не существует в $line38.$read$$iw$$iw$$iw$$iw$$iw$$iw$$iw $$вв.

А затем довольно большая трассировка стека, приводящая к уведомлению «Брошенный аварийный сеанс».

Я не могу сказать, является ли это проблемой моей логики (вы просто не можете соединить тело функции, которое ссылается на закрытую переменную), или это ошибка новой реализации. Сообщения об ошибках, безусловно, могли бы быть лучше. Это может усугубляться тем фактом, что я запускаю это на Repl.

Можно ли разделить функцию, отделив тело от закрытых терминов, и переписать ее, чтобы соединить логику непосредственно в результирующее дерево?


person KChaloux    schedule 18.12.2013    source источник
comment
@KChalous Вам когда-нибудь удавалось исправить это теперь, когда resetAllAttrs был удален из Scala 2.11? У меня точно такая же проблема, и я отчаянно пытаюсь ее решить!   -  person Andrew Bate    schedule 16.01.2015
comment
@AndrewBate Согласно онлайн-документации, все еще существует resetLocalAttrs, который должен охватывать большинство случаев. Не знаю, один ли это из них, но попробовать стоит. Ссылка: docs.scala-lang.org/overviews/macros/changelog211.html< /а>   -  person KChaloux    schedule 16.01.2015
comment
@KChalous Я исследовал немного больше, и в этом точном примере я думаю, что untypecheck достаточно. Однако, когда я пытаюсь сопоставить шаблон q"for ($i <- $collection) $body", мне нужен старый resetAllAttrs (по крайней мере, так кажется). Я использовал библиотеку resetAllAttrs для макросов Scala 2.11, и она работает, в то время как только untypecheck не работает, но я быстро сталкиваюсь с обычной проблемой, когда resetAllAttrs повреждает родительские деревья.   -  person Andrew Bate    schedule 16.01.2015
comment
@AndrewBate К сожалению, я определенно не эксперт в этом. Похоже, что по этой ссылке на github вы можете вернуть resetAllAttrs, но они удалили ее специально из-за упомянутой вами проблемы с повреждением дерева. Тем не менее, если вам это нужно, загляните на github.com/scalamacros/resetallattrs.   -  person KChaloux    schedule 16.01.2015
comment
Спасибо, я попробовал это, и это действительно испортило мои деревья во всем, кроме тривиального примера. Буду искать решение...   -  person Andrew Bate    schedule 19.01.2015


Ответы (2)


Если есть сомнения, resetAllAttrs:

import scala.language.experimental.macros
import scala.reflect.macros.BlackboxContext

def _forRange[A](c: BlackboxContext)(range: c.Expr[Range])(
  func: c.Expr[Int => A]
): c.Expr[Unit] = {
  import c.universe._

  val tree = func.tree match {
    case q"($i: $t) => $body" => q"""
        val iter = ${range}.iterator
        while (iter.hasNext) {
          val $i = iter.next
          ${c.resetAllAttrs(body)} // The only line I've changed.
        }
      """
    case _ => q""
  }

  c.Expr(tree)
}

А потом:

scala> def forRange[A](range: Range)(func: Int => A) = macro _forRange[A]
defined term macro forRange: [A](range: Range)(func: Int => A)Unit

scala> forRange(0 to 10) { i => println(i) }
0
1
2
3
4
5
6
7
8
9
10

В общем, когда вы берете дерево из одного места и шлепаете его куда-то еще, вероятно, потребуется использовать resetAllAttrs, чтобы получить все символы правильно.

person Travis Brown    schedule 18.12.2013
comment
Даже не знал, что такое существует! Очень хорошо. - person KChaloux; 19.12.2013
comment
К сожалению, это было удалено в 2.11. Я столкнулся с той же проблемой и не смог решить ее с помощью resetLocalAttrs (устарело) или untypecheck. - person Tomer Gabel; 19.04.2014

Оскар Бойкин указал в Твиттере, что мой предыдущий ответ больше не работает, и в любом случае это был не очень полный ответ— он решает проблему, указанную в OP на Scala 2.10, но не заботится о гигиене - например, если вы написали iter => println(iter), вы получите сбой во время компиляции.

Лучшей реализацией для версии 2.11 было бы использование Transformer для перезаписи дерева после отмены проверки типов:

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

def _forRange[A](c: Context)(r: c.Expr[Range])(f: c.Expr[Int => A]): c.Tree = {
  import c.universe._

  f.tree match {
    case q"($i: $_) => $body" =>
      val newName = TermName(c.freshName())
      val transformer = new Transformer {
        override def transform(tree: Tree): Tree = tree match {
          case Ident(`i`) => Ident(newName)
          case other => super.transform(other)
        }
      }

      q"""
        val iter = ${r.tree}.iterator
        while (iter.hasNext) {
          val $newName = iter.next
          ${ transformer.transform(c.untypecheck(body)) }
        }
      """
  }
}

def forRange[A](r: Range)(f: Int => A): Unit = macro _forRange[A]

Что работает следующим образом:

scala> forRange(0 to 10)((i: Int) => println(i))
0
1
2
3
4
5
6
7
8
9
10

Теперь не имеет значения, какое имя переменной мы используем в нашем литерале функции, так как оно все равно будет заменено новой переменной.

person Travis Brown    schedule 05.12.2015
comment
Где можно найти документацию/руководство по расширенным методам макросов, выходящим за рамки простой генерации текста? Например, untypecheck, context.info и другие методы, которые работают с кодом как с scala-объектами с классами типов и т. д., а не с фрагментами текста. Я нашел docs.scala-lang.org/overviews/macros/usecases. html, но он содержит только приемы, связанные с заменой текста. - person ayvango; 05.12.2015
comment
@ayvango У меня нет хорошего ответа на этот вопрос. У меня есть куча демонстрационных проектов, сообщений в блогах и ответов SO, и у многих других людей тоже есть, но они никак не индексируются. - person Travis Brown; 05.12.2015