6

REST Web サービスのラッパーを作成していて、厳密に型指定された Scala API が必要です。

以下は、私がこれまでに行っていることです。

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

このアプローチは機能していますが、parametersマップを手動で生成する必要があります。パラメータとその値を表す Map にアクセスできれば、私がやっていることはずっときれいになります。

4

1 に答える 1

12

ランタイム リフレクションを使用してこれを行うことができます。必要に応じて、その方法を説明する回答が得られると確信していますが、これは実際にはScala 2.10 のマクロの適切な使用例なので、ここで説明します。まず、次の名前のファイルがあるとします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))
  }
}

読者のための (簡単な) 演習として、マップ キーのスネーク ケースはそのままにしておきます。

テスト ファイル (名前はTest.scala)もあります。

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

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

次に、これらの両方をコンパイルします。

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

実行するTestと、次のようになります。

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

これの優れた点は、ランタイム リフレクションのオーバーヘッドがないことです。を使用してテスト ファイルをコンパイルすると、コンパイル時-Ymacro-debug-verboseに の本体に対して次のコードが (実際には) 生成されていることがわかります。foo

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

まさに私たちが期待するとおりです。

于 2012-11-11T21:40:12.040 に答える