2010年3月10日水曜日

Scalaのロジック、Javaのロジック

OptionとListとforとflatMapではPersonのaddressメソッドの実装として、以下の2つを挙げた。Optionに対して、for文&パターンマッチングやflatMapメソッドを使うのがScala的なプログラミング。 いずれにしても一度ListにしてからList操作の関数を活用するのがポイントである。

Person.scala
class Person(val name: String) {
  var zip: Option[String] = None
  var prefecture: Option[String] = None
  var city: Option[String] = None

  def address: String = {
    (for (Some(s) <- List(zip, prefecture, city)) yield s).mkString(" ")
  }
}
PersonFlatMap.scala
class Person(val name: String) {
  var zip: Option[String] = None
  var prefecture: Option[String] = None
  var city: Option[String] = None

  def address: String = {
    List(zip, prefecture, city).flatMap(s => s).mkString(" ")
  }
}

Personのインスタンス変数zip, prefecture, cityを「List(zip, prefecture, city)」としてList化して、Listとして統一的に処理できるようにしている。そして、このListをfor文のジェネレータに指定したり、ListのflatMapメソッドを直接使ったりして、List操作を行うわけである。

for文では、「Some(s)」によるパターンマッチング、yieldによるListの生成もがすこぶる便利。

また「OptionとListとforとflatMap」で述べた通りOptionとflatMapの組合せは魔法的な便利さがある。

もう一つ、面白いのがmkStringメソッド。Listの各要素を引数で指定された区切り記号を挟んで連結したStringを生成するメソッドである。文字列処理では何かとこの処理が出てくるので、専用メソッドが基本クラスに定義されているのはとても便利である。分かってらっしゃる、という感じ。

さて、Scala的なプログラミングがいかに効率がよいかという点を確認するためにJava的なプログラミングでaddressメソッドを実装してみよう。ボクの場合は、以下のようにプログラムすることになる。

Person.java
public class Person {
    final String name;
    String zip = null;
    String prefecture = null;
    String city = null;

    public Person(String name) {
        this.name = name;
    }

    public String address() {
        StringBuilder buf = new StringBuilder();
        boolean first = true;
        if (zip != null) {
            first = false;
            buf.append(zip);
        }
        if (prefecture != null) {
            if (first) {
                first = false;
            } else {
                buf.append(" ");
            }
            buf.append(prefecture);
        }
        if (city != null) {
            if (first) {
                first = false;
            } else {
                buf.append(" ");
            }
            first = false;
            buf.append(city);
        }
        return buf.toString();
    }
}

    

Scalaでリライトするとこんな感じになる。

PersonNotList.scala
class Person(val name: String) {
  var zip: String = null
  var prefecture: String = null
  var city: String = null

  def address: String = {
    val buf = new StringBuilder
    var first = true
    if (zip != null) {
      first = false
      buf.append(zip)
    }
    if (prefecture != null) {
      if (first) {
        first = false
      } else {
        buf.append(" ")
      }
      buf.append(prefecture)
    }
    if (city != null) {
      if (first) {
        first = false
      } else {
        buf.append(" ")
      }
      first = false
      buf.append(city)
    }
    buf.toString
  }
}

なんとも長いプログラムになるけど、Javaで普通に書くとこうなってしまう。zip, prefecture, cityというインスタンス変数ごとにほとんど同じだけど少しずつ違うロジックを並べていくことになるのが美しくない。Stringを段階的に構築していくことになるためStringBuilderを導入することになり、その取り回しも煩雑である。

また「Listの各要素を引数で指定された区切り記号を挟んだStringを生成する」というmkStringメソッドの処理は、実は案外煩雑である。これを実現するためにfirstというフラグを使って区切り記号を挿入するタイミングを制御したり、StringBuilderの操作も必要になる。

以上の比較から分かる通り、Listというデータ構造を軸にして事前に用意されているアルゴリズムを適用していく関数型言語的なプログラミングモデルははまると非常に強力である。もちろん、ある特殊な場合のみ便利でそれ以外はそうでもないとか、逆に不便ということであれば価値は低いわけだけど、Scalaの場合は、はまる場所が頻出するので、結果としてプログラミング効率が大幅に向上する。

また、データ構造のアルゴリズム的な処理を簡潔に記述できたとしても、字句上の見た目が煩雑では効果半減である。この点もScalaは高いレベルでニーズを満たしている。

このあたりの違いが分かってくると、Javaでのプログラミングはなんとももどかしくなってくる。「Scalaはオブジェクト指向と関数型のハイブリッド」と聞いても「だから?」という感じだけれど、実際にコーディングしてみると関数型言語とのハイブリッドの効果がよく分かる。宣言的に書ける、副作用がないように書ける、といった理念的な部分も重要だけど、実務的には簡単に短く書けるということがより重要である。「簡単に短く書ける」理由をよくよく見てみると、Listというデータ構造と高階関数の組合せによるものであり、Lisp時代から不変の方程式である。この方程式を現代的なAlgol系の静的型付オブジェクト指向プログラミング言語の文法に違和感なく溶け込ますことができたのがScalaの美点といえる。

0 件のコメント:

コメントを投稿