小技パート2。
Scala では関数もオブジェクトだから、どこにでも配置できる。
def sort(xs: Array[Int]) { def swap(i: Int, j: Int) { ... } def sort1(l: Int, r: Int) { ... swap(i, j) ... } }
こんな感じで、関数の中にサブ関数を定義できる。
Java では、こういう事をしたかったらクラス化する必要があった。
Scala では、特定の関数からしか使われない関数は、その関数の内部に閉じ込めてしまおう。
Scala の protected は、そのクラスおよび派生クラスからしかアクセスできない。
これを聞いて「Java と同じじゃないか」と思ったそこの君。それは違う。
Java の protected は、同一パッケージ内の全クラスからもアクセスが可能なことを知っておこう(俺もすっかり忘れていたが)。
また、アクセス修飾子には公開範囲を指定することができる。
private[mathfun] def bizexp(a : Int, b: Int) = 0
このようにすると、この関数は mathfun パッケージ内からのみアクセス可能となる。
これを使うと、特定のパッケージ(複数でも良い)のみに公開するクラスや関数を作成できる。
Java でコードを組んでいて、アクセス制限が理由でパッケージ構成を変更したことは無いか?
もちろん理想はそう(同一機能のクラス群を同一パッケージにまとめる)なのだが、
例外的な事態が発生することは現実的にはよくあることだ。
こんなとき、Scala のアクセス識別子はより柔軟に作用する。
Ruby などではおなじみの方法だが、以下のようなものがある。
val method = if (dataMap.size == 0) { new GetMethod(url) } else { var m = new PostMethod(url) ... m }
Scala では、最後に評価した値がその文の戻り値となることを思い出そう。
また、関数(コードブロック)はオブジェクトだから、いかなる場所にもコードブロックを埋め込むことができる。
Java ではこういった場合、if - else 文をメソッドとして切り出す方法が一般的だった。
もちろん Scala でもその手法は有効だが、むやみに分割することはコードの可読性を落とす。
「1メソッドを20行以下に収める」なんていう慣習は Scala には無い(多分)。
ほとんどのプログラマが、その手法でシステムの全てを完成させるなんてことが不可能だと気付いている。
DRY原則の観点だけで言えば「たった1箇所からしか呼び出されないメソッド」を作るメリットは特に無い。
多少コードが長くなっても良いから、わかりやすく保守しやすいコードであればそれが良いコードなのだ。
Scala では、型の記述を省略することができる。
これは「明らかにわかっているのに型の記述をすることはムダ」という思想から来るものかと思われる。
つまり、外部に公開する関数ではもちろん型を明確に定義しておいた方がいいのだが
ローカル変数のように一時的にしか使われない値やクラス内部のみで使われるprivate変数などでは
型を省略してもシステムの保守性は失われないことも多いのだ。
val i = 1 val s = "abc"
これらの文に、型定義が必要だろうか?いや、ないはずだ(反語)。
Scala の型推論はさらに賢い。
val method = if (dataMap.size == 0) { new GetMethod(url) } else { new PostMethod(url) }
このようなコードを書いたとき、Scala は method の型を両者の共通基底クラスである HttpMethodBase と推測する。
よって、例えば続いて以下のようなコードがあればコンパイラはエラーを出力する。
method.addParameters(...)
addParameters は PostMethod で定義されてはいるが、HttpMethodBase では定義されていないからだ。
そう、Scala はコンパイル時に型を推測する。
プログラマは、両者の共通基底クラスが HttpMethodBase であることを知らなくても良いのだ。
本来このクラスはライブラリ内部で定義されたものであり、プログラマが知っておく必要があるとは思えない。
さらに言えば、ライブラリがバージョンアップしたときにクラス名が変更されてしまうかもしれない。
つまり、推論に任せておいた方が便利なことだってあり得るのだ。
また、Scala には暗黙の型変換機能もある。Implicit を参照。
次のコードを見てみよう。
def count(VIPs : List[String]) : Int = { if (VIPs.isEmpty) 0 else count(VIPs.tail) + 1 }
見ての通り、これは VIPs に含まれる要素の数を数える関数だ。
「こんなに再帰呼び出ししたらスタックフレームが溢れるのでは?」と心配する方もいるだろう。
しかし大丈夫だ。問題ない。
このコードを Scala は最適化するため、実際にこのコードを実行してもスタックフレームは積み上がらない。
Javaプログラマは通常それほど再帰処理を使わない。
しかし関数型言語でもある Scala では、再帰は非常に強力な武器となる。
このように関数末尾から自身を再帰で呼び出せば最適化されるとのこと。
可能ならば、ループよりも再帰を。
その発想転換の積み重ねが、関数型プログラミングのスペシャリストへの道となるだろう。
Scala は、言語仕様としてXMLをサポートしている。
そのため、XML や (X)HTML との親和性は高い。
def message = <HTML> <HEAD><TITLE>Hello, Scala!</TITLE></HEAD> <BODY>Hello, Scala! It's now { currentDate }</BODY> </HTML> def currentDate = java.util.Calendar.getInstance().getTime() override def doGet(req : HSReq, resp : HSResp) = resp.getWriter().print(message)
Scala に慣れてくれば、これ位のコードも自然に感じるだろう。
つまり最後の行の print は引数に String を取るが、上の例ではここに「message関数」を渡している。
繰り返し言うが、Scala では変数と関数を区別しない。
引数にX型の変数を取る関数に渡すものは、
このどちらでも良い。
実際には後者の場合、関数が直接渡されるのではなく「関数を評価した結果得られたX型の値」が渡される。
では、message の定義を見てみよう。いきなりXMLリテラルが書かれている。
このように Scala では、ダブルクォート無で直接XMLを記述できる。
これは非常に見やすい。
そしてタグの閉じ忘れなどもコンパイラがチェックしてくれる。
message の中では式展開もしている。
{ currentDate } の部分だ。中括弧で括ると、その内部が式展開される。
もちろん関数も式展開可能だ。