2012-11-11 13 views
6

Sto scrivendo un wrapper per un servizio web REST e mi piacerebbe avere delle API Scala fortemente tipizzate.È possibile avere una mappa [String, Any] con il nome e i valori dei parametri denominati in Scala?

Quello che segue è quello che sto facendo finora:

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. 
*/ 
... 
} 

Questo approccio funziona, ma mi richiede di generare manualmente la mappa parameters. Se potessi accedere a una mappa che rappresenta i parametri e i loro valori, quello che sto facendo sarebbe molto più pulito.

risposta

11

Si potrebbe fare questo con la riflessione di runtime, e sono sicuro che otterrete risposte che ti dicono come, se vuoi, ma questo è in realtà un caso d'uso pulito per Scala 2.10's macros, quindi ecco qui. In primo luogo supponiamo di avere un file chiamato 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)) 
    } 
} 

Lascio serpente involucro le chiavi della mappa come un (facile) esercizio per il lettore.

Abbiamo anche un file di test (denominato Test.scala):

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

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

Ora compiliamo entrambi questi:

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

E quando si corre Test avremo la seguente:

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

La cosa bella di questo è che non c'è nessuno dei costi generali di riflessione untime. Se si compila il file di prova con -Ymacro-debug-verbose, vediamo che il seguente codice è stato generato (in effetti) per il corpo del foo al momento della compilazione:

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

Esattamente come ci si aspetterebbe.

+1

Sai se è possibile usarlo con sbt? – mariosangiorgio

+3

L'ho capito. È sufficiente cambiare la scala versione 'scalaVersion: =" 2.10.0-RC2 "' e importare ' import language.experimental.macros' nei file usando la funzione – mariosangiorgio

Problemi correlati