前回に引き続き第3回もいってきました。そのときにでた話題についてちょっと書こうかとおもいます。
using文
C#にusing文*1という制御構造がありますが、scalaでは、同じようなものを簡単に作れます。C#のほうのusing文については詳しく触れませんが、これからかくようなusing文について、個人的に思う利点としては、
- 本体と例外時の処理を分けて書けるので、コードが読みやすくなる
- リソースの開放のし忘れが起きにくい
- どこからどこまでリソースを使用しているのかが、読む側としてわかりやすくなる
- 解放後のリソースに間違ってアクセスする心配がない。(スコープがそもそも限定されるため)
などでしょうか?この、scalaでのusing文の制御構造の作り方なんて結構よく例としてあげられるものですが、あえて詳しく書いてみようと思いますww
で、その第3回勉強会でのソースを引用させて頂きます(・∀・ )
元のコード
object JavaLikeDatabaseSelect { def main(args: Array[String]) = { Class.forName("org.postgresql.Driver") val db = DriverManager.getConnection( "jdbc:postgresql://localhost/scaladb", "scalar", "scalar") val st = db.createStatement val sql = "SELECT id, name FROM sample" val res = st.executeQuery(sql) while(res.next){ val id = res.getInt(1) val name = res.getString(2) println(id+", "+name) } db.close } }
↓ scalaで自分でつくったusing文*2を使ったversion
import Utility.using //例として、自分で定義したものをimport object JavaLikeDatabaseSelect { def main(args: Array[String]) = { Class.forName("org.postgresql.Driver") using(DriverManager.getConnection( "jdbc:postgresql://localhost/scaladb", "scalar", "scalar")){ db => val st = db.createStatement val sql = "SELECT id, name FROM sample" val res = st.executeQuery(sql) while(res.next){ val id = res.getInt(1) val name = res.getString(2) println(id+", "+name) } } } }
で、これがどういう仕組みかというと、実際は、usingという名前の、引数を2つ取る関数に過ぎません。
細かいこと考えればいろいろヴァリエーションはあるとは思いますが、例えば以下のような感じでしょうか?
object Utility{ /** C#のusing文のパクリ * @tparam A closeという名前を持っているものならなんでも * @tparam B 2番目の引数の関数オブジェクトの戻り値の型(これもなんでもいい) * @param resource 最後にcloseを必ず呼ばないといけないオブジェクト * @param func 1つめの引数のオブジェクトを使用して行う処理 */ def using[A <: {def close()},B](resource:A)(func:A => B):Unit = { try{ func(resource) //処理実行 }catch{ case e:Exception => e.printStackTrace //エラーキャッチしたら、とりあえずログはいておく。(本当はこの部分の処理とか変える必要あるはず) }finally{ if(resource != null) resource.close() //エラーが起きても起きなくても、必ず最後にcloseを呼ぶ(close呼ぶことによって、さらに例外が発生するようなものの場合、さらに何らかの処理が必要ですが・・・(´・ω・`) ) } } }
で、他の言語にはないscalaの変な特徴があって、それを知らないと理解出来ないわけですが、scalaでは、
関数のパラメータが1つの場合は、普通のかっこ( )ではなく、中括弧{ }も使える*5(どっち使っても意味同じ)
という、明らかに、こういうDSLのためにあるんだろうと思われる変な規則があります。しかしここで、
いや、さっきのusingって関数パラメータ2つだったじゃん( ゚д゚ )
ってなるわけです。確かにパラメータ2つの場合は、中括弧使えません。
しかし、気づいていると思いますが、パラメータの部分が
(resource:A)(func:A => B)
というように、なぜかカッコ2つに分けてありましたよね?これがいわゆるカリー化とか、部分適用*6とかいう関数型言語の話になってくるわけです。
が、そこまで詳しくないというか、下手に説明して間違ったこというのもあれなので、とりあえず今日は、こういうふうに書くと、最後のところ中括弧で書けるよ(^ω^)/ 程度しかいいませんwww
気になる人は、カリー化とか部分適用でググッてください。
結局、using使ったversionのものは、文法的に説明すると、10行程にわたって、無名関数リテラルになっているわけです。
dbというのが引数名です。また、dbという変数の型はこの場合、DriverManager.getConnectionの戻り値である、Connectionというものですが、型推論によって省略できるので、省略してあります。
このように、scalaでは、
- 関数のパラメータが1つの場合は、普通のかっこ( )ではなく、中括弧{ }も使えるという規則と
- 基本的に、文という概念がなくすべてが式である(複数の文のように見える場合でも、最後の式が評価されたものが、式としての全体の値となる)
という特徴によって、このような制御構造に(見せかけたような)DSLを書きやすくなっています。C#なんかでも似たようなことできますが、2つ目の特徴がない>ので、直接あまり長い無名関数リテラルはかけませんね。
↓
追記勘違いしてました、できるようです(3.0から?)。でも、ひとつの式の場合と、そうでない場合で中括弧とセミコロンがついたりつかなかったりが面倒です。あとパターンマッチないので、結局長いの書いても見難い・・・匿名型つかえば少しましになりますが・・・それでもscalaのパターンマッチやタプル
から比べると使いづらいです
末尾再帰についてとかも書こうかと思いましたが、結構丁寧に書いたら、using文についてだけで疲れたので、とりあえず、今日はここまで(´・ω・`;)
*1: 名前空間のためのusingディレクティブというのもありますが、そっちではなく、IDisposableというインターフェイスを実装してる場合に、(エラーが起きても)リソースの開放を自動でやってくれるもの
*2: 正確には文ではなく関数
*3: 型パラメータは、@tparamって書くらしいですね。ライブラリがそうなってるので
*4: 型パラメータAについては、structual subtyping っていうのを使っています。何かを継承しているという条件ではなく、closeっていう名前のメソッド持っていればいいよっていうのです。あまり詳しくないので、このへんはもっと上級者に聞いてくださいw
*5:定義するときではなく、呼び出すときの話。定義するときはパラメータ1つでも普通のカッコしか使えません。
*6: ちなみに、カリー化と部分適用という言葉は、意味異なるらしいです。結構間違った使われ方してるらしく、そのへんの事情については、水嶋さんのブログとか、参考になるかも