筆者は,プログラミング言語には実用言語として生産性,保守性の高さを求めています。そのような観点から筆者が1年弱Scalaプログラミングを行った中で,経験則として取得したScalaプログラミングの勘所について説明します。今回は「コレクション」についてです。
3 コレクション
少し込み入ったプログラムを作る場合,オブジェクトの集まりを操作する必要がでてきます。このオブジェクトの集まりを操作するために使用するのがコレクション・ライブラリです。
コレクション・ライブラリは事実上プログラミング言語の一部として空気のように使用するので,コレクション・ライブラリの使い勝手がプログラミング言語の使い勝手を決めるといっても過言ではないでしょう。
Scalaのコレクション・ライブラリを使用する勘所について説明しましょう。
3.1 コレクション・ライブラリの全体像
まず,Scalaのコレクション・ライブラリを使用する場合に,軸となるクラスについてみていきます。
図1はScalaのコレクション・ライブラリの全体像を記述したクラス図です。
パッケージscala,scala.collection,scala.collection.immutable,scala.collection.mutableの4つのパッケージから構成されています。この4つのパッケージがScalaのコレクション・ライブラリの基本部です。
この他に,Javaのコレクション・ライブラリをラップしたscala.collection.jclがあり,Javaのコレクション・ライブラリそのものを直接使用することもできます。しかし,いずれの場合もJavaとの連携が必要な場合以外は意識しなくてよいでしょう。
3.2 ListとArray
図1の中で基本となるクラスはscala.Listとscala.Arrayです。
Listは,関数型計算で重要な役割をになうコレクションです。不変オブジェクトという性質を持ち,関数型でよく利用されるリスト処理操作用のメソッドをサポートしています。また,不変オブジェクトでありながら,オブジェクトを先頭に追加できるという性質を持っています。
このため,特に性能を気にしない用途には,Listを最有力候補として考えるとよいでしょう。例えばスクリプト言語的な利用方法の場合,List決め打ちで問題ありません。
しかし,ある程度本格的なアプリケーションを開発する場合,性能を意識する必要が出てきます。
ここで重要なのは,Scalaの実行環境であるJava VMでは配列操作の実行速度がコレクション操作の実行速度に対して段違いに高速という特性があることです。このため,配列をいかに活用するのかという点がJavaプログラミングのコツの一つとなっていますが,これはScalaでも同様のはずです。つまり,Java VMとの相性からScalaでも配列,すなわちscala.Arrayは重要な位置づけのコレクション・クラスとなります。
また,Listは構造上ランダムアクセスが苦手,メモリーを消費しやすい,という欠点もあります。
一方,Arrayは格納されているオブジェクトを置換することができるので不変オブジェクトではありません。このため,関数型計算との相性は良くありません。また,Arrayのサイズは生成時のみに指定可能で一度生成すると変更することができず,オブジェクトの追加をすることができません。つまり,Arrayは不変オブジェクトではないにもかかわらず,オブジェクトの追加ができないという中途半端な位置づけのコレクションというわけです。
以上の説明でわかるとおり,ListとArrayはどちらも独特のクセを持っているコレクションで,適材適所で使い分ける必要があります。とはいえ,実際にはListとArrayのどちらを使っても,あまり違いが出てこないという場合も多いでしょう。日々のプログラミングでは,どちらでもよいという場合が多いので,プログラマとしては,どちらを自分のプログラミング作法としてデフォルトで用いるのかを決めておくのが得策です。
一般論としては,Scalaは関数型言語なのでListをデフォルトで用いるのがよいでしょう。
しかし,筆者は実用プログラムの開発を目的としていて,動作性能を重視するしているのでArrayをデフォルトで用いています。そして,不変オブジェクトが必要な場合にListを用いることにしています。
ただし,XMLリテラルと組み合わせる場合にはListを用います。これは,XMLリテラルが不変オブジェクトなので,不変オブジェクトであるListの方が相性がよいと思われることと,Listの方が少しだけですがコレクション処理を簡潔に記述できるようになっているのでXMLリテラルに埋め込む時に若干取り回しが楽になるという利点があるからです。
3.2.1 Seq
ListとArrayのいずれをデフォルトにしたとしても,ListとArrayを適材適所で使い分けたり,他人が作ったプログラムとの連携をとることを考えると,結果としてはListとArrayが混在したプログラムを想定しなければなりません。
汎用的な処理を行うサブルーチンの引数の型として,ListやArrayを使用すると,ListやArrayのいずれかしか対応できなくなってしまいます。この問題に対処する場合,どの型を使えばよいでしょう。
筆者の経験では,このような目的にはトレイツscala.Seqを使うとよいようです。オブジェクトの集まりを示すトレイツとしてはSeqの親トレイツであるscala.Collectionもありますが,できるだけ機能が充実していてかつ汎用性も高いトレイツの方が都合がよいので,Seqがバランスがよいということです。
蛇足ですが,Javaの場合,配列とjava.util.Listは全く別物という扱いなので,汎用処理を書く場合,配列版とList版の2つ作成する必要がありました。Scalaでは,統一オブジェクト・モデル(uniform object model)の理念の下,Array(配列)とListが共通する親トレイツから派生する構造になっており,ArrayとListに共通した処理を書くことができます。ちょっとしたことですが,これもScalaの美点の一つです。