2012年7月24日火曜日

永続化の前後をクラスで表現する

※本エントリの内容は「たぶんコレでいいんじゃない?むしろ違ったら教えて下さい」的な内容です。

DBにエンティティを永続化するというシチュエーションを考える。
例えばPersonというエンティティとしよう。

case class Person(name: String, age: Int)
view raw 1.scala hosted with ❤ by GitHub


これをDBに保存するなら、PrimaryKeyが必要だ。ま、普通はIDを付けるでしょう。

case class Person(id: Int, name: String, age: Int)
view raw 2.scala hosted with ❤ by GitHub




さて、問題は、「ID採番前のPersonはどう表現するの?」という話。

案1. id = 0 とか、負の数を「採番前」ということにする

冗談です。

案2. idの型をOptionにする

case class Person(id: Option[Int], name: String, age: Int)
view raw 3.scala hosted with ❤ by GitHub


確かに「IDの有無」を表現はできてるが、これだと例えば「これから採番するPersonのリスト」のような型が作りにくいです。

案3. オブジェクト指向っぽく行けばいいんじゃない?

いろいろ考えたんだけど落ち着いたのがここ。あんまカッコイイ答えじゃないんだけどね。
ただ、「case classにする」ってところだけは守るという制約でScalaで書こうと思うと、やっぱりちょっと考えないといけない。

sealed trait Person {
val name: String
val age: Int
}
case class IdentifiedPerson(id: Int, name: String, age: Int) extends Person
case class UnIdentifiedPerson(name: String, age: Int) extends Person
view raw 4.scala hosted with ❤ by GitHub


で、こうなった。id以外のメンバは何回も書いてるのが正直気に入らない。でも、使うときは多分これが一番気持ちよく使えると思う。

もっと良い書き方募集。

2012年7月17日火曜日

Windowsガジェット環境でのsubstr

Windowsガジェット(最初はVistaサイドバーガジェットとか呼ばれてたもの)って、もはや生みの親であるMicrosoftからも「Windows8が出たら要らないよね」って言われちゃったかわいそうな子だけれども、でもなんだかんだ言ってもWindows7使ってるうちは何かと便利なツールなわけで、結構馬鹿にできないと思うんです。

というわけで、to-banからJSON引っ張って今日の"当番"をWindowsガジェットに表示させておくと捗るぞ、と思って手を出した。

が、すっげーアホくさいところでハマったので、記録しておく。

それがString.substr関数。

これ、第一引数に負の数を渡すと、現在の標準では文字列の後ろから数えてくれるんだけど、Windowsガジェット環境(や超古いブラウザ)では負の数を渡すと何もしてくれなくなる。

// Chrome、FF、IE9
"4321".substr(-2); // → "21"
// Windowsガジェット
"4321".substr(-2); // → "4321"
view raw substr.js hosted with ❤ by GitHub


厄介なのは、IE9が入ってるWindows環境でも、ガジェット上ではガジェット環境での挙動を示すということ。「IEで動けばガジェットでも大体動くだろー」とタカをくくってるとハマる(体験談)

2012年7月16日月曜日

PlayでurlFormEncodedなデータを受け取る

Playでformからの入力を受け取る場合は、ファイルアップロードが必要な場合を除けば、大抵の場合はurlFormEncodedを通してデータを受け取る事になると思う(Ajaxとかいう解はとりあえず考えない)。例えば以下の様なコードになる。

import scalaz._, Scalaz._
object PersonController extends Controller {
def create = Action(parse.urlFormEncoded) { implicit req =>
val personOpt: Option[Person] = for {
name <- req.body.get("name") map {
case Seq(s) => s.some
case _ => none
}
age <- req.body.get("age") map {
case Seq(s) => s.parseInt.toOption
case _ => none
}
person <- Person.create(name, age)
} yield person
// あとはResultを作る
}
}
view raw before.scala hosted with ❤ by GitHub


言うまでもなく、Requestからデータを取り出す部分が無駄に長い。
これは、urlFormEncodedのbodyの型がMap[String, Seq[String]]だから。もちろんプロトコル上はこの型が妥当なのだが、大抵の場合はValueは1つだけ値があれば十分なので、Seqから1個取り出すのが冗長に感じられるわけだ。

じゃあ、ということで、以下の様な関数を作ってやればとりあえず短くはなる。

def param(req: Request[Map[String, Seq[String]]], key: String): Option[String] =
req.body.get(key) map {
case Seq(s) => s.some
case _ => none
}


短くはなるんだけど、ただ単純に処理を切り出しただけ。使ってみるとわかるけど、あんまりScalaっぽくない。
せっかくScala使ってるんだから、型クラス使いたいよね!

というわけで、こうなった。

import scalaz._, Scalaz._
trait ControllerHelper {
// Seq[String]から型Tのデータを抽出する型クラス
trait Extractor[T] {
def extract(data: Seq[String]): Option[T]
}
// Extractorを簡単に生成できるようにする
def extractor[T](f: Seq[String] => Option[T]): Extractor[T] = new Extractor[T] {
def extract(data: Seq[String]) = f(data)
}
// 他の型のExtractorとの連携も簡単にできるように
def extractorBy[T, B](f: B => Option[T])(implicit extB: Extractor[B]): Extractor[T] =
extractor[T] { extB.extract(_) >>= f }
// こいつがControllerで実際に呼び出す関数
def paramOpt[T](key: String)(implicit req: Request[Map[String, Seq[String]]], ext: Extractor[T]): Option[T] =
req.body.get(key) >>= ext.extract
// OptionじゃなくてValidationで受け取りたい時用
def paramVld[E, T](key: String)(err: => E)(implicit req: Request[Map[String, Seq[String]]], ext: Extractor[T]): Validation[E, T] =
paramOpt[T](key).toSuccess(err)
// よく使う型のExtractorは予め作っておきやしょう
implicit lazy val StringExtractor = extractor {
case Seq(s) => s.some
case _ => none
}
// Extractor[String]とString => Option[Int]を組み合わせてExtractor[Int]を作る
implicit lazy val IntExtractor = extractorBy[Int, String] { _.parseInt.toOption }
}


paramOpt関数がControllerから呼び出す関数。
こいつはimplicit parameterでExtractor[T]を受け取っていて、そのExtractorのextract関数を利用してデータ抽出を行う。
paramOpt関数自信も型パラメータを持っていて、そこに指定された型に合ったExtractorが選択されて使われるわけだ。
extractorByみたいな関数も用意しておけば、IntExtractorの様に、既存のExtractorと型変換用関数を組み合わせて新たなExtractorを構成するのも簡単だ。

Extractor[T]を使って最初のPersonの例を書きなおすとこうなる。

object PersonController extends Controller with ControllerHelper {
def create = Action(parse.urlFormEncoded) { implicit req =>
val personOpt: Option[Person] = for {
name <- paramOpt[String]("name")
age <- paramOpt[Int]("age")
person <- Person.create(name, age)
} yield person
// あとはResultを作る
}
}
view raw after.scala hosted with ❤ by GitHub


スッキリ!



追記:

extractorByの中身、何をやってるかって言うと、A => Option[B] と B => Option[C] を組み合わせて A => Option[C] を作ってるんだよね。で、


って呟いたら、反応されてた。



やっべ、kleisliわかんねぇ。勉強します。。。