2012-11-11 13 views
6

Piszę wrapper do usługi sieciowej REST i chciałbym mieć mocno napisany interfejs API Scala.Czy możliwe jest posiadanie mapy [String, Any] z nazwą i wartościami nazwanych parametrów w Scali?

Oto co robię do tej pory:

def getMentions(count: Option[Int] = None, 
       sinceID: Option[TweetID] = None, 
       maxID: Option[TweetID] = None, 
       trimUser: Option[Boolean] = None, 
       contributorDetails: Option[Boolean] = None, 
       includeEntities: Option[Boolean] = None) : List[Tweet] = { 
val parameters = Map("count" -> count, 
        "since_id" -> sinceID, 
        "max_id" -> maxID, 
        "trim_user" -> trimUser, 
        "contributor_details" -> contributorDetails, 
        "include_entities" -> includeEntities) 
/* 
* Convert parameters, which is a Map[String,Any] to a Map[String,String] 
* (Removing Nones) and pass it to an object in charge of generating the request. 
*/ 
... 
} 

Podejście to działa, ale wymaga mnie ręcznie wygenerować mapę parameters. Gdybym mógł uzyskać dostęp do mapy reprezentującej parametry i ich wartości, to co robię byłoby znacznie czystsze.

Odpowiedz

11

Można to zrobić z odbiciem w środowisku wykonawczym i jestem pewien, że otrzymasz odpowiedzi informujące, jak, jeśli chcesz, ale jest to w rzeczywistości schludny przypadek użycia dla Scala 2.10's macros, więc tutaj idzie. Po pierwsze zakładamy, że mamy plik o nazwie ParamMapMaker.scala:

object ParamMapMaker { 
    def paramMap: Map[String, Any] = macro paramMapImpl 

    def paramMapImpl(c: scala.reflect.macros.Context) = { 
    import c.universe._ 

    val params = c.enclosingMethod match { 
     case DefDef(_, _, _, ps :: Nil, _, _) => 
     ps.map(p => 
      reify((
      c.Expr[String](Literal(Constant(p.name.decoded))).splice, 
      c.Expr[Any](Ident(p.symbol)).splice 
     )).tree 
     ) 
     case _ => c.abort(c.enclosingPosition, "Can't call paramMap here!") 
    } 

    c.Expr[Map[String, Any]](Apply(Select(Ident("Map"), "apply"), params)) 
    } 
} 

Zostawię węża obudowy przycisków map jako (łatwe) ćwiczenie dla czytelnika.

Mamy również plik testowy (o nazwie Test.scala):

object Test extends App { 
    def foo(hello: String, answer: Int) = ParamMapMaker.paramMap 

    println(foo("world", 42)) 
} 

Teraz skompilować oba te:

scalac -language:experimental.macros ParamMapMaker.scala 
scalac Test.scala 

A kiedy prowadzimy Test dostaniemy następujące:

Map(hello -> world, answer -> 42) 

Dobrą rzeczą jest to, że nie ma żadnego narzutu r odbicie w czasie. Gdybyśmy skompilować plik testowy z -Ymacro-debug-verbose, widzimy, że następujący kod został wygenerowany (w efekcie) dla korpusu foo w czasie kompilacji:

Map.apply[String, Any](
    scala.Tuple2.apply[String, String]("hello", hello), 
    scala.Tuple2.apply[String, Int]("answer", answer) 
) 

Dokładnie tak, jak by się spodziewać.

+1

Czy wiesz, czy można go używać z sbt? – mariosangiorgio

+3

Rozgryzłem to. Wymaga to po prostu zmiany wersji scala 'scalaVersion: =" 2.10.0-RC2 "' i zaimportować ' import language.experimental.macros' w plikach za pomocą funkcji – mariosangiorgio

Powiązane problemy