Il problema è che il codice si mescola a tempo di compilazione e di concetti di runtime.
La variabile "elenco" che si sta utilizzando è un valore in fase di compilazione (ovvero dovrebbe essere iterato durante la fase di compilazione) e si chiede a reify di mantenerlo fino al runtime (mediante splicing derivato valori). Questo enigma di livello intermedio porta alla creazione di un cosiddetto termine libero.
Insomma, termini liberi sono stub che fanno riferimento ai valori da fasi precedenti. Ad esempio, il seguente frammento:
val x = 2
reify(x)
sarà compilato come segue:
val free$x1 = newFreeTerm("x", staticClass("scala.Int").asTypeConstructor, x);
Ident(free$x1)
intelligente, eh? Il risultato mantiene il fatto che x è un Ident, conserva il suo tipo (caratteristiche in fase di compilazione), ma, comunque, si riferisce anche al suo valore (una caratteristica run-time). Ciò è reso possibile dall'ambito del lessico.
Ma se si tenta di restituire questo albero da una macro di espansione (che è inline nel sito di chiamata di una macro), le cose saltare in aria. Il sito di chiamata della macro molto probabilmente non avrà x nel suo ambito lessicale, quindi non sarebbe in grado di riferirsi al valore di x.
Ciò che è peggio. Se il frammento di cui sopra è scritto all'interno di una macro, allora x esiste solo durante la compilazione, cioè nella JVM che esegue il compilatore. Ma quando il compilatore termina, non c'è più.
Tuttavia, i risultati di espansione di macro che contengono un riferimento alla x dovrebbero essere eseguito in fase di esecuzione (molto probabilmente, in una JVM diversa). Per dare un senso a ciò, è necessaria la persistenza in più fasi, ovvero la capacità di serializzare in qualche modo valori arbitrari in fase di compilazione e di deserializzarli durante il runtime. Non so come farlo in un linguaggio compilato come Scala.
Si noti che in alcuni casi cross-stadio persistenza è possibile.Ad esempio, se x è un campo di un oggetto statico:
object Foo { val x = 2 }
import Foo._
reify(x)
Allora non finirebbe come termine libero, ma sarebbe reificato in modo semplice:
Select(Ident(staticModule("Foo")), newTermName("x"))
Questo è un concetto interessante che è stato anche discusso nel discorso di SPJ agli Scala Days 2012: http://skillsmatter.com/podcast/scala/haskell-cloud.
Per verificare che alcune espressioni non contengano termini liberi, in Haskell aggiungono una nuova primitiva built-in al compilatore, il costruttore di tipo Static
. Con i macro, possiamo farlo naturalmente usando reify (che di per sé è solo una macro). Vedi la discussione qui: https://groups.google.com/forum/#!topic/scala-internals/-42PWNkQJNA.
Okay ora abbiamo visto qual è esattamente il problema con il codice originale, quindi come possiamo farlo funzionare?
Purtroppo dovremo ripiegare alla costruzione AST manuale, perché reificare ha tempo difficile esprimere alberi dinamici. Il caso d'uso ideale per reify in macrology sta avendo un template statico con i tipi di buchi noti al momento della compilazione della macro. Fai un passo indietro e dovrai ricorrere a costruire alberi a mano.
Linea di fondo è che si deve andare con il seguente (funziona con recentemente rilasciato 2.10.0-M4, consultare la guida alla migrazione a scala-lingua per vedere esattamente cosa è cambiato: http://groups.google.com/group/scala-language/browse_thread/thread/bf079865ad42249c):
import scala.reflect.makro.Context
object Macros {
def join_impl(c: Context)(a: c.Expr[Int]): c.Expr[List[Int]] = {
import c.universe._
import definitions._
a.tree match {
case Block(list, ret) =>
c.Expr((list :+ ret).foldRight(Ident(NilModule): Tree)((el, acc) =>
Apply(Select(acc, newTermName("$colon$colon")), List(el))))
}
}
def join(a: Int): List[Int] = macro join_impl
}
Ci sarà qualcosa di più semplice in scala 2.11? Le virgolette saranno utili? (Non ho studiato le macro nel profondo, ma ogni volta che provo io continuo a sbattere contro questo) – HRJ
Quasiquotes stanno per aiutare un po ': https://gist.github.com/densh/6209261 –