5月から1年ぶりにScalaを使った開発が始まりました。
1年ぶりにScalaを触った感想は、「思った以上に忘れてる、ヤバイ」でした。
せっかくまたScala触れたのだから、今度はわすれないよう、日々のScala開発で感じたことやハマったことをメモしていこうと思います。
目次
ScalikeJDBCのTypeBinderでハマった話
PlayFrameworkの(QueryString|Path)Bindableをもっと便利にしたい
ScalikeJDBCのTypeBinderでハマった話
現在の仕事は、とあるシステムの内部APIをPlayFramework+Scalaで作成するというものです。 DBへのアクセスにScalikeJDBCを使っています。
現象
Scalaではなるべく型安全にしたいということで値オブジェクトを使っています。
一方、DBはレガシーなもので、nullableなカラムが数多くあります。
そのnullableなカラムの値をTypeBinder[Option[A]]を使って受け取るときに問題が起きました。
実際のコードとは少し違う1のですが、最小で問題を再現するTypeBinderは↓の通りです。
import scalikejdbc._ case class UserName(value: String) extends AnyVal implicit lazy val userNameBinder: Binders[UserName] = Binders.string.xmap(UserName, _.value)
これを使ってカラムのnull
を受けようとしたところ、期待していたNone
ではなくSome(UserName(null))
という大変ざんねんな結果になってしまいました。
原因
値オブジェクトのバインドにLowPriorityTypeBinderImplicits#option
によって生成されたTypeBinder[Option[UserName]]
が使われるからです。
implicit def option[A](implicit ev: TypeBinder[A]): TypeBinder[Option[A]] = new TypeBinder[Option[A]] { def apply(rs: ResultSet, columnIndex: Int): Option[A] = wrap(ev(rs, columnIndex)) def apply(rs: ResultSet, columnLabel: String): Option[A] = wrap(ev(rs, columnLabel)) private def wrap(a: => A): Option[A] = try Option(a) catch { case _: NullPointerException | _: UnexpectedNullValueException => None } }
UserName
の場合、先にTypeBinder[UserName]
を使って受けてからOption
でくるんでいるのでSome(UserName(null))
のようになります。
もし値オブジェクトの保持する値がLong
なら発生しなかったとでしょう。
解決方法
色々アホなことして遠回りしましたが2、ScalikeJDBCがLong
など他のAnyVal
を処理している方法を真似して、UserName
にバインドする時に文字列がnull
だったらNullPointerException
を吐くようにしました。
implicit lazy val userNameBinder: Binders[UserName] = Binders.string.xmap(throwExceptionIfNull(UserName), _.value) // Bindersのコンパニオンオブジェクトで使われているもの def throwExceptionIfNull[A, B](f: B => A): B => A = { a => if (a == null) throw new NullPointerException else f(a) }
PlayFrameworkの(QueryString|Path)Bindableをもっと便利にしたい
これは未解決というか感想レベルの話なんですけど、(QueryString|Path)Bindableって既存の(QueryString|Path)Bindableからの導出方法がtransform
しかなくて、Prism
を使って上手く導出出来ないかな?って思っています。
bind
の処理を合成するときにflatMap
みたいな感じでやりたいです。
まあ、今日は時間もないし、とりあえず良い案でるまで開発進めながら考えようという感じです。