Scalaでの集合の扱い
コレクションクラス(Set、Array、Listなど)
Scalaで、データ集合を扱うための基本となるコレクションクラスを見ていきましょう。Scalaのコレクションは、ベースクラスであるIterableを継承したSeq、およびSeqから派生したArray、Listからなります。このうち、ScalaのArrayは、Javaの配列に多くの便利なメソッドを付け加えたものととりあえずは言えるでしょう。ScalaのListは、さらに興味深い性質を複数持っています。
ここからは対話型評価環境(REPL)でScalaのコレクションのコードを実行し、その実情を把握してみます。まずは、それぞれを定数(val)として宣言してみましょう。既に説明したように、Scalaでは自明な型宣言は省略できます。例えばInt型のデータ要素を持つコレクションは、LIST12のように書きます。
scala> val S = Seq(1,2,3) S: Seq[Int] = ArrayBufferRO(1, 2, 3) scala> val A = Array(1,2,3) A: Array[Int] = Array(1, 2, 3) scala> val L = List(1,2,3) L: List[Int] = List(1, 2, 3)
各データ要素については、Javaの配列と同様にアクセスできる(LIST13)。
scala> S(0) res0: Int = 1 scala> A(0) res1: Int = 1 scala> L(0) res2: Int = 1
それぞれのデータの先頭/末尾のデータ要素へのアクセスには、first、lastというメソッドでアクセスできます(S.first、A.first、L.first...といった具合)。Listについては、先頭以外のリストの獲得にtail、末尾以外の要素の獲得にinitという、特別なメソッドも提供されています(L.tail、L.initとタイプしてみましょう)。それぞれのコレクションは、toArray、toList、toSeqメソッドにより相互に変換可能です(LIST14)。
scala> val X1 = Seq(1,2,3).toArray X1: Array[Int] = Array(1, 2, 3) scala> val Y1 = Array(1,2,3).toList Y1: List[Int] = List(1, 2, 3) scala> val Z1 = List(1,2,3).toSeq Z1: Seq[Int] = List(1, 2, 3) scala> val X2 = Seq(1,2,3).toList X2: List[Int] = List(1, 2, 3) scala> val Y2 = Array(1,2,3).toSeq Y2: Seq[Int] = Array(1, 2, 3) scala> val Y3 = List(1,2,3).toArray Y3: Array[Int] = Array(1, 2, 3)
Array、Listが、Seqから派生していることは、型を明示した記述により確かめることができます。上位型であるSeqは、派生したArrayまたはListを持つことができます(LIST15)。
scala> val S1 :Seq[Int] = Array(1,2,3) S1: Seq[Int] = Array(1, 2, 3) scala> val S2 :Seq[Int] = List(1,2,3) S2: Seq[Int] = List(1, 2, 3)
逆に、派生したArrayやListは、Seq型を持つことができません(LIST16)。
scala> val A1 :Array[Int] = Seq(1,2,3)
<console>:4: error: type mismatch;
found : Seq[Int]
required: Array[Int]
val A1 :Array[Int] = Seq(1,2,3)
ここまで、単一のデータ型(Int)を持つコレクションだけを試してきました。複数のデータ要素を持てるかを試してみましょう(LIST17)。
scala> val S3 = Seq(1,2,3.0) S3: Seq[AnyVal] = ArrayBufferRO(1, 2, 3.0) scala> val A3 = Array(1,2,3.0) A3: Array[Double] = Array(1.0, 2.0, 3.0) scala> val L3 = List(1,2,3.0) L3: List[AnyVal] = List(1, 2, 3.0) scala> val S4 = Seq(1,2,"something") S4: Seq[Any] = ArrayBufferRO(1, 2, something) scala> val A4 = Array(1,2,"something") <console>:4: error: overloaded method value apply with alternatives ・・・(略)・・・ scala> val L4 = List(1,2,"something") L4: List[Any] = List(1, 2, something)
ArrayとSeqおよびListとでは、挙動が違います。SeqとListは任意の型(LIST17では、整数と実数、文字列)を含む、Any(そしてAnyを継承したAnyval)をデータ要素として保持できます。Arrayは直接にAnyをデータ要素として持つことはできないようです(注4)。
少し意味は異なりますが、Javaと同様にGenericsを使うことでArrayに持たせるデータ型を実行時に指定することは可能です(後述)。
また、整数(1および2)と実数(3.0)とを同時に持つArrayは可能ですが、データ要素はDoubleとなっています。これは、整数から実数への暗黙の型変換が起きたためです。Scalaのコレクションクラスは、Iterableという振る舞い(trait)を受け継ぐことで、さまざまなメソッドを用いることができます。ここまで、各コレクションを再代入不可なvalとして宣言してきましたが、このことの意味するところを実際にコードを書く際に多用することになる、ArrayとListについて調べておきましょう。
はじめに、定数として宣言されたArrayの要素を書き換えてみましょう。複数のメソッドを「;」でつないだ場合、最後の命令の返り値がREPLに表示されます。
scala> val A = Array(1,2,3); A(0) =10; A A: Array[Int] = Array(10, 2, 3)
Arrayでは、データ要素の変更は可能なようです。Javaに親しんでいる人なら、ScalaのArrayのこの振る舞いは、Javaと同様であることが分かるでしょう。実際のところ、ScalaのArrayは、JavaのArrayを継承しています。しかし、Listの場合は勝手が異なります(LIST18)。
scala> val L = List(1,2,3); L(0) =10
<console>:4: error: value update is not a member of List[Int]
val L = List(1,2,3); L(0) =10
●変数(var)としてListを宣言してみても結果は同じ
scala> var L = List(1,2,3); L(0)=10
<console>:5: error: value update is not a member of List[Int]
var L = List(1,2,3); L(0)=10
●Listにおける要素の変更・追加は「::」メソッドを使う
scala> var L = List(1,2,3)
L: List[Int] = List(1, 2, 3)
scala> L = 10::L.tail
L: List[Int] = List(10, 2, 3)
LIST18の場合、「::」メソッドは実際には、新たなListを作っています(すなわち、10::L.tailという部分では、Listから先頭以外(tail)を切り出したListが作成され、さらにそのListと新たな先頭要素10を持つListが生成されています)。
XML/XHTMLリテラル
Scalaでは、scala.xmlライブラリを使って、(X)HTMLをはじめとするXMLをリテラルで表記できます。次のような具合です。
scala> val title = <h1> タイトル </h1> title: scala.xml.Elem = <h1> タイトル </h1>
これは関数内でも同じです。XMLリテラルは、ノードの集まり(ツリー集合)として関数内で1つの値(scala.xml.Elem型)として評価されます(そのため、中括弧{}は省略することが可能)。
関数newsの引数doc(String型)は、XMLリテラル表記中の{doc}の部分に埋め込まれます。REPLを使って確かめてみましょう。newsに引数を入れて呼び出したのち、宣言した変数titleと関数newsを呼び出して新たな変数aHTMLpageに代入してみます。変数aHTMLpageに、XHTMLが格納されたことが確認できるでしょう(LIST19)。
scala> var title = <h1>タイトル</h1>
title: scala.xml.Elem = <h1>タイトル</h1>
scala> def news(doc:String) =
| <div>
| <h2>本日の新着記事 </h2>
| <p>{doc}</p>
| </div>
news: (String)scala.xml.Elem
●newsに引数を入れて呼び出す
scala> news("記事をここに書きます。")
res3: scala.xml.Elem =
<div>
<h2>本日の新着記事 </h2>
<p>記事をここに書きます。</p>
</div>
●宣言した変数titleと関数newsを呼び出し、新たな変数aHTMLPageに代入
scala> val aHTMLpage =
| <html>
| <body>
| {title}
| {news("本日リリース! 並列処理言語○×!!")}
| </body>
| </html>
aHTMLpage: scala.xml.Elem =
<html>
<body>
<h1>タイトル</h1>
<div>
<h2>本日の新着記事 </h2>
<p>本日リリース! 並列処理言語○×!!</p>
</div>
</body>
</html>
シリーズ前編の今回は、Scalaのメリットや文法の基礎について解説しました。次回はシリーズの後編として、Scalaのプログラムコンパイルと、データベース接続について説明します。
