Scalaのimplicit conversion(暗黙の型変換)を試す

きっかけ

play framework 2.3を使って新規のWEBアプリを
開発しようと調査していたところ、標準でバンドル
されているanormが、従来、僕が経験したことのあるORM
と比較して、ずいぶん抽象度が低く、『SQL直書きは
しんどいなぁ・・・』とか思っていたところ、
先輩にscalikejdbcというORMを教えてもらいました。

(anormはORMではない、ORMは不要だと公式でも言っているくらいなので)

使ってみたところ、CakePHPのAppModelやRubyのActiveRecordと
play frameworkのanormの中間的な使用感。
(scalikejdbcはplay用のpluginが出ているだけで、playでの使用が前提ではないです。)

そのコードがこちら

Companyテーブルからidをもつレコードを取得して、Companyのインスタンスに
して返してます。(データがなければNoneを返す)

ほとんどSQL書いているような感覚でScalaコードを書ける。
ただ、既存のメソッドでは実現できなかったり、ちょっと複雑になりすぎる場合
なんかは、結局直接SQLを書きたいケースも当然出てくる
(というかある程度の規模や機能数になると、必ずと言っていいほどこういう需要がある)

そんなときのコードは

本来はこの書き方しなくてもこの程度であればscalikejdbcが提供するメソッドで
簡単に実現できるんだけど、例としてこんなかんじに。
Userとそれに紐づくDivisionをリストにして取得するみたいなサンプル

sql”””の後にほとんど普通にSQL書いてる。
${}の中はJSPのEL式みたいな感じでバインドされる。

このコードを半信半疑でサンプル見ながら書いて、期待通りに動作したときは
結構衝撃だった。Scalaではこんな書き方ができるのか・・・と。

何が起きてこうなってるのか、もその時はよくわからなかった。
同じ用な話で、このモデルのテストを書いてたとき

space2というテストフレームワークを使ってました。

“find by primary keys” in new AutoRollback。
文字列に対して一体何が起きているんだろう・・・と不思議になりました。
(テストの内容はidでUserテーブルを検索して、ちゃんと見つかるかみたいなもの)

まぁこんな感じで、既存の型をものすごく拡張できる。内部DSLの
ホスト言語としての評判がいいのはこの辺が理由。
これを支えているのがscalaのimplicit conversionだということを
ソースコード読んでたらなんとなくわかってきたので、まとめたくなりました。
(ブログ書いてた過程で気になった部分も一緒に調べて書いてます。
僕の中でこの記事が内部DSLの入り口になれば・・・)

implicit conversionとは

日本の解説サイトや本で暗黙の型変換と訳されることが多いです。

型変換を必要に応じて自動でやってくれる仕組みです。

単純なサンプルはこちら

implicit conversionを利用するときは
scala.language.implicitconversionsをインポートする必要があります。
加えて、”komai”.helloを”komai” helloみたいにドットを省略するには
scala.language.postfixOpsをインポートする必要があります。

(importしてもプログラムは動きますが、scala2.10以降のコンパイラでは警告を出します。
乱用するとプログラムが煩雑になりやすい機能なので、明示的にしようね、的なものです。
コンパイラの警告を気にする環境であればちゃんとimportしてあげたほうがいいです。)

これを実行すると

が出力されます。

一見、mainだけを見ると、Stringのインスタンスとhelloという何かしらの制御構文
が利用されているような感じがしますが、これを実現しているのがimplicit conversion。

処理される手順は以下の通り

  1. Stringクラスのhello()メソッドを探す
  2. 1がなければStringインスタンスを引数に受け取り、
    hello()メソッドを持つ型を返すimplicit defで定義されているメソッドを探す
  3. 2が複数見つからなければ、それを利用してStringクラスをhello()メソッドを持つ
    インスタンスに変換する

これによって、単なるStringクラスがBarインスタンスに変換されました。

この仕組がimplicit conversion
これを利用するとfinalで宣言されたクラスでも拡張することができます。
(finalで宣言されたクラスのインスタンスを引数にうけとって、新しいクラスのimplicit defを定義するだけ)

ドットの省略

メソッドのドットは省略することができます。
中置記法というらしい。
上記のサンプルを使うと

どれもドットがあるものと同様に動作します。

括弧()の省略

一定の条件のもと、引数の括弧も省略することができます。

これも同様に動作します。
ただ、scala2.10以降は引数のない場合に括弧を省略すると
コンパイラがエラーを出します。
引数のないメソッドは

と呼び出したほうがよいでしょう。

引数の括弧を省略できる条件

  1. 引数がなく、かつ定義時に()をつけて定義したもの
  2. 引数が1つのメソッドの引数

その他にもscalaには省略ルールが数多くあり、複雑ですが
覚えないと読むのが難しいくらいに拡張されているプログラムが
多数存在するので注意が必要です。

まとめ

正直自分でもまとまりのない内容になってしまったと反省しています。
が、そのまま投稿しちゃいます(・ω<)

implicit conversionと内部DSLやってみよう!
の入り口になれば・・・と思います

参考リンク

  1. Scala の implicit conversions がよくわかんなかったからいろいろやってようやくわかった気がする
  2. Scala の省略ルール早覚え
  3. 文字列の補間

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です