2012-12-01 15 views
9

Załóżmy, że mam tego makra:Sprawdzanie varargs typu przypisania makra Scala

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

object FooExample { 
    def foo[A](xs: A*): Int = macro foo_impl[A] 
    def foo_impl[A](c: Context)(xs: c.Expr[A]*) = c.literal(xs.size) 
} 

To działa zgodnie z oczekiwaniami „prawdziwy” varargs:

scala> FooExample.foo(1, 2, 3) 
res0: Int = 3 

Ale zachowanie z sekwencją przypisać Typ varargs jest mylące dla mnie (w Scala 2.10.0-RC3):

scala> FooExample.foo(List(1, 2, 3): _*) 
res1: Int = 1 

i pokazać, że nic podejrzanego się dzieje wi wyliczony typ:

scala> FooExample.foo[Int](List(1, 2, 3): _*) 
res2: Int = 1 

Spodziewałbym się tutaj błędu podczas kompilacji i tego właśnie chcę. Użyłem następujące podejście w większości makr mam napisane:

object BarExample { 
    def bar(xs: Int*): Int = macro bar_impl 
    def bar_impl(c: Context)(xs: c.Expr[Int]*) = { 
    import c.universe._ 
    c.literal(
     xs.map(_.tree).headOption map { 
     case Literal(Constant(x: Int)) => x 
     case _ => c.abort(c.enclosingPosition, "bar wants literal arguments!") 
     } getOrElse c.abort(c.enclosingPosition, "bar wants arguments!") 
    ) 
    } 
} 

a ten łapie problem w czasie kompilacji:

scala> BarExample.bar(3, 2, 1) 
res3: Int = 3 

scala> BarExample.bar(List(3, 2, 1): _*) 
<console>:8: error: bar wants literal arguments! 
       BarExample.bar(List(3, 2, 1): _*) 

to czuje się jak hack do mnie, chociaż- miesza się z jednym bitem sprawdzania poprawności (sprawdzając, czy argumenty są literałami) z innym (potwierdzając, że naprawdę mamy varargs). Mogę też wyobrazić sobie przypadki, w których nie potrzebuję argumentów, aby były literałami (lub gdy chcę, aby ich typ był ogólny).

wiem, mogę wykonać następujące czynności:

object BazExample { 
    def baz[A](xs: A*): Int = macro baz_impl[A] 
    def baz_impl[A](c: Context)(xs: c.Expr[A]*) = { 
    import c.universe._ 

    xs.toList.map(_.tree) match { 
     case Typed(_, Ident(tpnme.WILDCARD_STAR)) :: Nil => 
     c.abort(c.enclosingPosition, "baz wants real varargs!") 
     case _ => c.literal(xs.size) 
    } 
    } 
} 

Ale to jest brzydki sposób obchodzenia się bardzo proste (i ja przypuszczam szeroko konieczne) trochę walidacji argumentów. Czy jest tu jakaś sztuczka? Jaki jest najprostszy sposób, aby upewnić się, że foo(1 :: Nil: _*) w moim pierwszym przykładzie daje błąd podczas kompilacji?

+0

Kiedy piszesz "Spodziewałbym się tutaj błędu podczas kompilacji", czy mógłbyś wyjaśnić? Spodziewałbyś się, że jest to błąd, ponieważ jest to wymóg Twojej domeny? Lub powinien to być błąd dla wszystkich rodzajów makr vararg? –

+0

@EugeneBurmako: Obawiam się, że w przypadku askryptu 'xs.head' w rzeczywistości nie jest w rzeczywistości" c.Expr [A] "- bardziej przypomina" c.Expr [Seq [A]] ". Oto [kilka przykładów] (https://gist.github.com/4191360). –

Odpowiedz

1

Czy to działa zgodnie z oczekiwaniami?

+0

Dzięki, ale to w zasadzie taki sam jak mój "BarExample", i nie będzie działać w ogólnym przypadku. –