Sprayパラメータ変換


最近sprayで何かを作って、入門したばかりで、docを一度くらい通り過ぎて、最も柔軟なのはそのrouting-DSLです.もちろんscalaの強力な特性のため、Javaとは異なるものが多く、ソースコードはかなり骨が折れるように見えますが、特に私のようなscalaが使って間もない初心者には.
今日出会った問題はどのようにパラメータを所望のタイプに変換するかです.例えば?age=25&birth=1988-01-23で、IntとDateパラメータとして受信できます.すぐにドキュメントに方法が見つかりました.次のように書きます.
case class Color(red: Int, green: Int, blue: Int)

val route =
  path("color") {
    parameters('red.as[Int], 'green.as[Int], 'blue.as[Int]) { (red, green, blue) =>
      val color = Color(red, green, blue)
      doSomethingWith(color) // route working with the Color instance
    }
  }

しかし私が自分で書いたとき、Dateを書くとコンパイルエラーになることに気づきました.
val route: Route =
    path("stat" / Rest) { path =>
      parameters('age.as[Int], 'birth.as[java.util.Date]) { (age, birth) =>
        get {
          complete {
            path + age.toString + birth
          }
        }
      }
    }
[info] Compiling 1 Scala source to D:\study\spray-template\target\scala-2.11\classes...
[error] D:\study\spray-template\src\main\scala\com\example\MyService.scala:60: too many arguments for method parameters: (pdm: spr
ay.routing.directives.ParamDefMagnet)pdm.Out
[error]       parameters('age.as[Int], 'birth.as[java.util.Date]) { (age, birth) =>
[error]                 ^

エラーは何を言っているのか全く分からないが、古典的な方法では、エラーメッセージをgoogleしても満足のいく答えが見つからない.そして私は自分で考え始めました.sprayやscalaはあなたが勝手にタイプAを書くことができるほど強いわけではありません.as[A]はstringをよくすることができます.きっと何かmagicのことが背後で黙って起こっているに違いありません.
そこでまずctrl+b(ideaのショートカットキー、ideaを使ってからeclipseに戻りたくなくなりました.これは広告ではありません)をクリックして、case class Name Receptacleで、私が想像していた直接戻りタイプAではありません.引き続きparametersを見て、ポイントを入れてから:
def parameters(pdm: ParamDefMagnet): pdm.Out = pdm()

それから私は馬鹿になって、それからimplicitのもので、何を言っているのか全然分かりません!そしてdocで探してみたらThe Magnet Pattern
そして希望を見たようで、点開、長いE文ですね.心が臆病です.しかし、その後も読み続け、簡単に言えばscalaの次の従来のmethod overloadingのタイプ消去などによる不足を解決するための設計モデルでしょう.そして'age.as[Int]はNameReceptacle[Int]タイプを返し、「リロード」の方法を見つけました
implicit def forNR[T](implicit fsod: FSOD[T]) = extractParameter[NameReceptacle[T], T] { nr ⇒
    filter(nr.name, fsod)
  }
はimplicitのFSODが必要であることを見ることができますか?これです
import spray.httpx.unmarshalling.{ FromStringOptionDeserializer ⇒ FSOD, _ }
type FromStringOptionDeserializer[T] = Deserializer[Option[String], T]

明らかに、sprayは確かにタイプAを勝手に与えるほど強い理由はありません.stringをAに変えることができます.このことをするにはDeserializerが必要だったのですが、implicitなので、表示して書く必要はありません.様子を見るとas[Int]はsprayのデフォルトで、StringをIntのimplicit関数に変換するDeserializer[Option[String]を定義しているはずです.Dateは間違いを報告してはいけません.Deserializer[Option[String]、Dateがないのではないでしょうか.自分で定義してみましょう.
implicit def string2Date = new Deserializer[Option[String], Date] {
    override def apply(v1: Option[String]): Deserialized[Date] = v1 match {
      case None => Left(ContentExpected)
      case Some(str) => {
        val sdf = new SimpleDateFormat("yyyy-MM-dd")
        try {
          val date = sdf.parse(str)
          Right(date)
        } catch {
          case _: Throwable => Left(MalformedContent("format yyyy-MM-dd"))
        }
      }
    }
  }

再コンパイル、大丈夫、実行もok!
その後、object Deserializerには、通常の関数f:A=>BをDeserializerに「昇格」する方法があることがわかりました.
implicit def fromFunction2Converter[A, B](implicit f: A ⇒ B) =
    new Deserializer[A, B] {
      def apply(a: A) = {
        try Right(f(a))
        catch {
          case NonFatal(ex) ⇒ Left(MalformedContent(ex.toString, ex))
        }
      }
    }

これにより
implicit def str2Date(str: String) = {
    val sdf = new SimpleDateFormat("yyyy-MM-dd")
      sdf.parse(str)
  }

ずいぶん爽やかに見えました!
最後に小さなテクニックを言ったが、最初は分からなかった.as[Int]はどんなタイプで、1つの方法はctrl+b点を入れて見て、もう1つ、あなたは1つを定義することができます
val x = 'age.as[Int]

マウスをx,alt+enterに移動し、「add type annotation to value definition」を選択すると
val x: NameReceptacle[Int] = 'age.as[Int]

これは場合によっては便利です.
scalaは確かに強くて柔軟で、学習曲線が高くて、努力を続けます.....