2010年3月26日金曜日

By-name Parameters

先々週に、メインで使っていたThinkPadがとうとう壊れてしまった。ファンの故障なので修理できないこともなさそうだったけど、5年程使っているマシンでディスクも50GBと少なく、メモリ2GBは今となってはEclipse&Scalaの組み合わせには到底足りないので、観念して新しいものを買うことにした。

最近、周りの人のMac率が非常に高いこともあり、スペックが魅力的だったのでiMacにしてみた。到着したのが先週の金曜。ちょうど一週間経ったけど、やっと作業環境が整備できてきた。(そういえば、ブログもこの記事がiMacからの初投稿になる。)

CPUはCore i7。4コアで、Hyper Threading機能により擬似的に8コアになる。今後、マルチコアを前提としたプログラミングモデルに移行することが予想されるので、検証の基盤として期待している。

ということで、8コアの振舞いを見たいのと、ScalaのActorを試したいということもあり簡単な並列ソートプログラムを作って動かしてみた。このプログラムについてはいずれ取り上げるかもしれないけど、今回は並列ソートプログラムの性能測定用に作った以下の小さなプログラムがテーマ。

Tmr.scala
object Tmr {
  def run(proc: => Unit) {
    System.gc
    val start = System.currentTimeMillis
    proc
    val end = System.currentTimeMillis
    println("elapse(ms) = " + (end - start))
  }
}

このプログラムはScalaの特徴的な機能であるBy-name Parameterを使っている。By-name Parameterは関数リテラル/クロージャと字句的なトリックを使って、メタな機構を導入せずに擬似的な制御構文の拡張を可能にした、まさにスケーラブルを目指すプログラミング言語Scalaの影の主役ともいうべき重要な機能である。大事なのはrunメソッドのパラメタ「proc: => Unit」の所。通常の関数パラメタだとパラメタがない場合「proc: () => Unit」となるところを、パラメタリストを省略している。この省略記法をScalaではBy-name parameterと呼んでいる。

Tmr.scalaは小さなプログラムなので、ひと目で何をしているか分かると思う。

でもできることはすごい。

基本的な使い方は次のようになる。この場合は小さなソートが5msで終了している。

scala> Tmr.run(List(2, 1, 3).sort(_ < _))
elapse = 5

何となく、見逃してしまうかもしれないけど、関数リテラルや高階関数を持たないJavaなどプログラミング言語ではこういう書き方はできない。なぜなら、Tmr.runメソッドに制御が移る前に、引数の式が評価されてしまうから。地味だけど、関数型言語ならではの機能なのである。

試しに1000msスリープする式を引数にTmr.runメソッドを実行してみると経過時間が1003秒となった。妥当なところである。

scala> Tmr.run(Thread.sleep(1000))
elapse = 1003

ここまでの機能だけだと「影の主役」はちょっと大げさ。本当に感心するのは、以下のように引数として制御構造のボディのような複数の文の列を書くことができることである。このことによって擬似的な制御構文が追加可能になっている。

scala> Tmr.run {
     |   val list = List(2, 1, 3)
     |   list.sort(_ < _)
     | }
elapse = 2

この記法を支えているのは2つの特例。まず、パラメタが1つのパラメタリストは「(」「)」だけでなく、「{」「}」が使えるという特例がある。また、先ほど説明したようにBy-name Parameterはパラメタがない関数リテラルでパラメタリストを省略することができるという特例である。この2つの特例が字句上の工夫で、組み合わせて使用すると擬似的に制御構文を追加することができる。

こういった字句上の工夫というかトリックが色々と入っているのがScalaの特徴で、一筋縄ではいかないところである。プログラミング言語は、こういった字句上の工夫が使い勝手に大きく影響するということが、Scalaを使ってみるとよくわかる。

0 件のコメント:

コメントを投稿