build.sbt の変更を検知する sbt プラグインを作りました
git でブランチを切り替えたらうまくビルドできなくなって困ったけど、build.sbt が変わっているのに sbt の reload をするのを忘れていただけだった、ということがたまに起こります。これを防ぐために、.sbt や project/.scala が変更されていたら警告を表示する sbt プラグインを作りました。
tototoshi/sbt-build-files-watcher
作りました、というかよしださんの https://gist.github.com/xuwei-k/6278769 をだいたいパクった感じです。
インストールはプロジェクトごとに設定するよりはグローバルの設定にすると便利だと思います。
~/.sbt/0.13/plugins/build.sbt
addSbtPlugin("com.github.tototoshi" % "sbt-build-files-watcher" % "0.1.1")
~/.sbt/0.13/build.sbt
showMessageOnBuildFilesChanged
sbt-git などを使っている場合は showMessageOnBuildFilesChanged
が sbt-git のshowCurrentGitBranch
とぶつかるので messageOnBuildFilesChanged
という関数を使って shellPrompt
キーを設定します。
shellPrompt := { state => messageOnBuildFilesChanged(state) + GitCommand.prompt(state) }
これで .sbt や project/.scala を編集すると reload しろという警告が出るようになります。めでたし。
Flyway は複数人での開発に向かないという誤解について
“データベースマイグレーションについて考えないといけないことや諦めないといけないことが結構あるでよ”
http://t.co/BYna6w5luj
期待して記事見たが、複数人開発時におけるバージョン番号の衝突について説明がなかったのが残念。紙面が限られてるししゃあない。
— 早すぎる最適化オジサン (@makotokuwata) 2014, 12月 29
flyway って、V1_ みたいな prefix をつけるけど、integer を increment するのって、ブランチきって平行で開発してる場合どうなるの?という気がしている
— tokuhirom (@tokuhirom) 2014, 7月 30
flywayのマイグレーションについて見てたけど、これ、バージョン番号をファイル名につける感じなのですかね。まだよく見てないが。Railsのマイグレーションと比べて複数人開発に強くないですねぇ。
— でこくん (@dekokun) 2013, 3月 26
というように、Flyway は複数人開発に向かないという噂をたまに聞くのですが、多分誤解です。
誤解が生まれるのは公式ドキュメントで Flyway のマイグレーションスクリプトのファイル名として、V1__Add_new_table.sql
みたいなファイル名が例に出されており、これが V2__
、V3__
のような名前をつける必要があるという印象を与えるためかと思われます。
ドキュメントを良く読むと、バージョンのルールは
- One or more numeric parts
- Separated by a dot (.) or an underscore (_)
- Underscores are replaced by dots at runtime
- Leading zeroes are ignored in each part
なので、もう少し柔軟です。だから rails のようにタイムスタンプベースのファイル名付けちゃえば良いです。
sql ├── V20150127114055__create_user_table.sql ├── V20150127114322__add_country_column.sql └── V20150127114323__add_age_column.sql
このようにタイムスタンプベースのバージョン付けを行うとブランチをマージしたときなどに順番が狂うという問題が発生しますが、 outOfOrder
という設定があるのでこいつを on にしてやれば OK です。
WEB+DB PRESS VOL.84 の Flyway の記事にもなかったので書いてみました。
play-json で snake_case な json を camelCase な case class にマッピングする
play-json を使えば json を case class にマッピングすることは簡単にできますが、json のキーがそのまま case class のフィールドに対応するため、snake_case な json API を case class にマッピングするためには、case class のフィールドも snake_case にする必要があります。Scala は camelCase にするのが主流ですから少し気持ち悪いですね。
snake_case な json を camelCase な case class にマッピングするためには、いつも使っている Json.format[T]
のラッパーを作る必要があります。作りました。
tototoshi/play-json-naming · GitHub
build.sbt に
libraryDependencies += "com.github.tototoshi" %% "play-json-naming" % "0.1.0"
して使ってください。
使い方は、JsonNaming.snakecase
を呼び出すだけの単機能ライブラリです。
import com.github.tototoshi.play.json.JsonNaming case class Name(firstName: String, lastName: String) case class User(id: Int, nameData: Name) // Json.format[T] をラップする implicit val nameFormat = JsonNaming.snakecase(Json.format[Name]) implicit val userFormat = JsonNaming.snakecase(Json.format[User]) val jsonString = """{"id":1,"name_data":{"first_name":"Toshiyuki","last_name":"Takahashi"}}""" Json.parse(jsonString).validate[User] Json.toJson(User(1, Name("Toshiyuki", "Takahashi")))
イメージとしては
+---------------+ | json string | +---------------+ ⇅ +---------------+ | JsValue | +---------------+ ⇅ JsonNaming.snakecase +---------------+ | JsValue | +---------------+ ⇅ +---------------+ | case class | +---------------+
こんなかんじで、json の内部表現である JsValue に対して変換をかけています。
snake_case 以外にもよくある規則があれば取り込みますので pull-req ください。
PHP のトレイトに気をつける
普段 Scala でトレイトを使いまくってるけれども PHP にも 5.4 からトレイトが入った。
trait の良いところは多重継承のできない言語で多重継承っぽいことができることだ。 use, use とつけていけば、いくらでも追加できる。DRY に書けてよいことだ。
対して悪いところはいとも簡単に複雑で暗黙的な依存関係が生まれることだ。
例えばこんなの
<?php trait Greeting { public function say() { if ($this->location == 'ja') { echo 'こんにちは' . PHP_EOL; } else { echo 'Hello' . PHP_EOL; } } } class Location { } class US extends Location { use Greeting; private $location = 'us'; } class Japan extends Location { use Greeting; private $location = 'ja'; } $us = new US(); $us->say(); $ja = new Japan(); $ja->say();
US、Japan ともに Location クラスを継承している。 それぞれに挨拶をするメソッドを追加したいけれどもこれは継承で実装するのが適切だろうか? よくわからない。よし、トレイトを作ろう。という経緯で上のコードは書かれた、という設定。
このコードのどこが微妙か。それは Greeting トレイト が US, Japan クラスのフィールドに依存していることだ。しかも暗黙的に。
こういうコードは読みにくい。Greeting トレイト だけを読んでも $this->location
ってなに?という気持ちになるし、US、Japan クラスだけを読んでも、まさかその private フィールドに Greeting トレイトが依存しているとは思うまい。
これだけでもひどいが、もう少しやりすぎ感を出してみよう。
<?php trait Greeting { public function say() { if ($this->location == 'ja') { echo 'こんにちは' . PHP_EOL; } else { echo 'Hello' . PHP_EOL; } } } trait JapanLocation { private $location = 'ja'; } class Japan { use JapanLocation; use Greeting; } $ja = new Japan(); $ja->say();
Japan クラスは実装を持っていない。Greeting と JapanLocation を mix-in してやることでオブジェクトを合成している。Greeting は Japan に mix-in されているが、それだけでは動作しない、JapanLocation も一緒に mix-in してやることで初めて動作する。わかりづらい。Greeting トレイトを使うきは Location トレイトも mix-in してくださいね!ということはドキュメントにしっかり書こう。あと JapanLocation の $location
は PhpStorm が未使用の変数として警告してくるけど消しちゃだめだよ、これは一見意味のない変数に見えるけれど、実は Greeting メソッドと一緒に mix-in するときに使われるんだ、ってこともドキュメントにちゃんと書いておかなきゃね。
とまあ、そんなのはやりすぎに見えて説得力がないけれど、 trait を使いまくるとこういうところに行き着くだろう。 それに実を言うとこれは Scala ではよく見るパターンだ。
trait Location { val location: String } trait JapanLocation extends Location { val location: String = "ja" } trait Greeting { self: Location => def say(): Unit = { if (location == "ja") { println("こんにちは") } else { println("Hello") } } } object Japan extends JapanLocation with Greeting
このコードの何が PHP と違うのかと言うと、次の1行にある。
trait Greeting { self: Location =>
self: Location =>
という記述は self-type annotation
と呼ばれ、
この Greeting トレイトは Location 型でもありますよ。つまり Location 型のクラスやトレイトにのみ mix-in できますよ、という意味になる。使いかたを間違えるとコンパイルエラーになる。
このように Scala ではクラス間の関係を明示することができるし、しなければコンパイルが通らない。ドキュメントなどではなく、コードとして表現できる。Scala 最高だった。
Scala ではこのように小さな trait を組み合わせて大きなオブジェクトを合成するパターンは、ケーキ作りになぞらえて、Cake Pattern と呼ばれる。Cake Pattern を使うと実装の一部となっている trait を差し替えることができるのでコンパイル時 DI のような使われ方もしている。
とは言え、いくら型の制約があると言っても、やりすぎると当然コードは読みづらくなるし、コンパイルが遅くなるとか、パス依存型が絡んでややこしくなるなどといった問題はあるので、最近では「Cake Patternはアンチパターンだ」とする流れもある。
話がずれた。Scala の話じゃない。PHP ではどうすれば良いか。そんなのは簡単、というか今まで(<PHP5.3)どおりやれば良い。
<?php class Greeting { public static function say($location) { if ($location == 'ja') { echo 'こんにちは' . PHP_EOL; } else { echo 'Hello' . PHP_EOL; } } }
結局これだけでよかった。この実装はシンプルだ。 このファイルだけ見れば良いし、呼び出し元から見ても say は場所に依存するんだな、ということがわかる。 このクラス単体で動作し、テストも書きやすい。リファクタリングも PhpStorm で自動的にできる。
トレイトの使いどころ
フレームワーク側で便利な trait を提供したりするのはそこまで悪くはないと思う。ドキュメントなりサンプルコードなりがしっかりあって、ユーザーの前提知識となっていれば良い。(例えば Ruby の Enumerable みたいなの)
それから古いフレームワークのコードを見ていて、継承関係がやたら複雑で、トレイトがあったらもっとマシになるなあと思うことはあるので、多重継承がない言語で多重継承っぽいことをやりたい、というところでは使えば良い。そういうものなので。
それ以外の、今までスタティックメソッドやら委譲・集約とかで済んでたものをトレイトにする、というのはやらないほうがよい。
まとめ
トレイトは柔軟で便利ではあるけれども、特に PHP のように型の制約のない言語で使用すると、 複雑で暗黙的なオブジェクト間の関係を生み出しコードはひどく読みづらくなる。 密結合で壊れやすく、変更が難しいコードになる。
トレイトはどうしても多重継承したい、という場面で使う。しかしそもそもを言えば「継承より委譲・集約を選ぶ」べきである。
暗黙的な知識を要求するトレイトを使うときはきちんとドキュメントを書くなり知識の共有を行うなどする。
結局は trait が悪いというよりは書き方の問題であるけれども、trait が意図せずにクラス間の結合を生み出す危険性が高いことは確か。話がややこしくなるから Ruby の module には触れなかったけど、同じような辛い場面には遭遇する。trait をたくさん使いたかったら Scala を書けば良い。
漏水工事した
9月、入院エンジョイしてたら漏水が発覚して引っ張りだされました。
自宅が漏水してるらしく一時外出…
— Toshiyuki Takahashi (@tototoshi) September 9, 2014
うち(3階)からの漏水で2階の部屋の天井が落ち、1階の駐車場まで雨漏りしていました。
自分だけでなく家まで内視鏡検査することになってしまった…
— Toshiyuki Takahashi (@tototoshi) September 9, 2014
内視鏡検査の結果、引っ越し前のリフォームのときに床下の配管の締めが甘く、それからずっと漏水していたらしいことがわかりました。どうしてこんなになるまで...早く言おうよ2階の人... (リフォーム業者の不手際なので私は無実です)
うちも少しやられてしまったので、修理しました。
以下、修理の手順です。
穴を開けて床下のパイプを直します。 どこがおかしいのかわからないので、とりあえず穴を開けます。 当たりが出れば直せます。
良くみたら壁が床から水を吸い上げてカビてきてたのではがしました。
手頃な穴を開けて、一週間くらい空気を送りこんで床下を乾かします。 乾かしたあとで消毒もします。
工事の途中でインターネットの線を破壊されちゃって辛かった。
穴をふさぎ、壁を貼りました。
壁紙をはりました。
以上です。
だいたい3ヶ月くらいかかります。みなさんも是非漏水してみてくださいね!
Play 2.4 の Module の作り方と Plugin からの移行について
Play 2.4 では今までの Plugin の仕組みが deprecated となり、 新たに Module という仕組みが導入されています。
Module はこれまた新たに導入された Runtime Dependency Injection の上に乗っかっています。 Play では Guice をデフォルトの DI 実装として利用しますが、 Guice 以外の実装を利用するユーザーもいるだろうということで、 ライブラリとして提供するモジュールについてはそこだけ Guice ではなく、Play 独自の Module という仕組みに沿った形で実装します。
アプリケーションの起動時と終了時にメッセージを表示する Play Module を実装してみましょう。
まずは処理の本体です。
package example import play.api._ import javax.inject._ import play.api.inject._ import scala.concurrent._ import scala.concurrent.ExecutionContext.Implicits.global @Singleton class Hello @Inject() (lifecycle: ApplicationLifecycle) { lifecycle.addStopHook(() => Future.successful { println("Goodbye") }) println("Hello") }
今までの Plugin インタフェースのメソッドを実装する形で実装していた、 Play アプリケーションの起動時の処理は、シングルトンクラスの初期化処理の中で行います。 終了時の処理は injection された lifecycle オブジェクトの addStopHook メソッドを利用して登録します。
これを Play Module として読み込むには
play.api.inject.Module
を継承して、binding メソッドを実装します。
package example import play.api._ import javax.inject._ import play.api.inject._ import scala.concurrent._ import scala.concurrent.ExecutionContext.Implicits.global class HelloModule extends Module { def bindings(environment: Environment, configuration: Configuration) = { Seq( bind[Hello].toSelf.eagerly ) } }
bind の仕方を DSL で記述します。
Play アプリケーション起動時の処理を行うためは、起動時に bind する必要があるので、
.eagerly
を使用します。
作った Module を利用するときは application.conf でその Module を有効にします。
play.modules.enabled += "example.HelloModule"
シングルトンオブジェクトなので toSelf
で自分自身に bind してしまいましたが、
より一般的にはインタフェースを作ってそれに bind します。
package example import play.api._ import javax.inject._ import play.api.inject._ import scala.concurrent._ import scala.concurrent.ExecutionContext.Implicits.global class HelloModule extends Module { def bindings(environment: Environment, configuration: Configuration) = { Seq( bind[HelloInterface].to[Hello].eagerly ) } } @Singleton class Hello @Inject() (lifecycle: ApplicationLifecycle) extends HelloInterface { lifecycle.addStopHook(() => Future.successful { println("Goodbye") }) println("Hello") } trait HelloInterface
さらに、インスタンスの生成が複雑な場合は Provider を利用することもありそうです。
package example import play.api._ import javax.inject._ import play.api.inject._ import scala.concurrent._ import scala.concurrent.ExecutionContext.Implicits.global class HelloModule extends Module { def bindings(environment: Environment, configuration: Configuration) = { Seq( bind[HelloInterface].toProvider[HelloProvider].eagerly ) } } @Singleton class HelloProvider @Inject() (lifecycle: ApplicationLifecycle) extends Provider[HelloInterface] { lazy val get = new Hello(lifecycle) } class Hello (lifecycle: ApplicationLifecycle) extends HelloInterface { lifecycle.addStopHook(() => Future.successful { println("Goodbye") }) println("Hello") } trait HelloInterface
Java っぽさが増したので、特に Java の DI になじみのない LL からのユーザーは戸惑うかもしれません。 Runtime Dependency Injection の導入経緯やテストへの影響などは playframework-dev のメーリングリストを読むと面白いかと思います。
sbt でファイル変更をフックしてコンソールをクリアしつつコンパイルする
Scala Advent Calendar 2014 の 12 日目です。
sbt で ~compile
でファイル変更をフックしてコンパイルは皆さんよくやってると思いますが、
~ ;eval "\u001B[2J\u001B[0\u003B0H" ;compile
とにするとコンソールをクリアしつつコンパイル続行するのでちょっといいかんじになります。
~/.sbtrc に
alias cc = ~ ;eval "\u001B[2J\u001B[0\u003B0H" ;compile
って書けば cc
で使えるようになります。