2013-12-18 3 views
8

Я играл с новыми функциями макроса 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: symbol value i does not exist in$line38.$read$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw. error: symbol value i does not exist in scala.reflect.internal.FatalError: symbol value i does not exist in $line38.$read$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw.

, а затем довольно большой трассировки стека, что приводит к «Заброшенный разбитого сессии» уведомления.

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

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

+0

@KChalous Вы когда-нибудь могли исправить это сейчас, когда 'resetAllAttrs' был удален из Scala 2.11? У меня есть * точная * та же проблема, и я отчаянно пытаюсь ее исправить! –

+0

@AndrewBate Согласно онлайн-документации, существует «resetLocalAttrs», который все еще существует, который должен охватывать большинство случаев. Не знаю, если это один из них, но это стоит того. Ссылка: http://docs.scala-lang.org/overviews/macros/changelog211.html – KChaloux

+0

@AndrewBate и согласно проекту scalamacros на github, 'resetLocalAttrs' был переименован в' untypecheck'. Ссылка: https://github.com/scalamacros/resetallattrs – KChaloux

ответ

7

Если вы сомневаетесь, 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, чтобы получить все символы справа.

+0

Даже не подозревал, что это существовало! Очень хорошо. – KChaloux

+3

К сожалению, он был удален во 2.11. Я столкнулся с тем же вопросом и не смог его устранить с помощью resetLocalAttrs (устарело) или untypecheck. –

5

Оскар Бойкин указал on Twitter, что мой предыдущий ответ больше не работает, и это был не совсем полный ответ - он решает проблему, указанную 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 

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

+0

Где можно найти документацию/руководство для расширенных методов макросов, выходящих за рамки простого генерации текста? Подобно 'untypecheck',' context.info' и другим методам, которые работают с кодом как объекты scala с типами классов и т. Д. Вместо текстовых фрагментов. Я нашел http://docs.scala-lang.org/overviews/macros/usecases.html, но он содержит только методы, связанные с заменой текста. – ayvango

+0

@ayvango У меня нет хорошего ответа. У меня есть куча демонстрационных проектов и сообщений в блогах и ответы SO, и многие другие люди тоже делают это, но они никоим образом не индексируются. –