前ページ | トップページ | 次ページ

2階:書き方

空白と改行

Scalaでは、識別子として記号も使用できる(つまり、記号演算子をユーザが定義できるーー記号だからと言って別に必ず演算子として使わなければならないわけでは無いが)。と言うことは、「a+b」は「識別子aとbと演算子+」では無く、「三つの識別子aと+とb」ということになるわけだ。これは結局、「+」をメソッド名として「a.+(b)」に(Scalaでは)等しい。こういうものは、しかし、より見やすくするためにむしろ普通「a + b」と間に空白を入れるものだし、そもそもメソッドが記号で無く文字だったらどーするよ、ということもあるので、結局Scalaではそれもまた「a.+(b)」と解釈するようにしている。つまり、Scalaでは空白(\tも含む)は可能なら「.」、「(」または「)」と見なされ得る。

改行(\rおよび/または\n)も一応空白文字なのだが、ただしScalaが「一応式として成立可能である」と認識できるものの後の改行は複文区切り、すなわち「;」に等しい、と見なされ、その時はもちろん空白文字扱いにはならない。なんかアバウトにも思えるかもしれないが(実際にはもっと厳密なしかしややこしいルールがある、が)、要するに普通のJava(Script)のように、まともで誰にも読み易いようにプログラムを記述していれば、まずこの問題に直面することはあるまい(あったらあったでScalaがエラーや警告をしてくれる;もちろん英語で、だが)。

識別子と予約語

識別子にはJavaのそれに加えてキーボード上に印字してある通りにテキストファイル上で表示できるもの(要するにASCII記号字)も使える。なお、「$」と「_」は、一般にはASCII記号字と見なされているけれども、Scalaでは大文字扱いになる(ただし「$」はScalaが内部的に使うので、一応使えるができれば識別子用としては使わない方が良い)。なお、無論のことだが、予約語とまったく同じ識別子は使えないし(この場合でも「`」(バッククォート)で囲めば使える)、その予約語定義の上の括弧定義や分離子定義の要素、またバックスラッシュやコメント囲み記号等もいらぬ混乱の元なので避けた方が良いだろう。標準ライブラリで使われている識別子も特に必要が無ければ避けた方が良いのは言うまでもあるまい。

それで、可能識別子の形態は、大文字か小文字に始まり以下任意の数の文字数字が続くもの(上述したように、「_」も大文字としてこちらに含まれる)、記号だけから成るもの、前者の最後に「_」を付けてその後に任意の数の記号が続くもの(間に「_」を挟まないと文字数字部と記号部は各々別の識別子と見なされる)、それと、以上の制約を一切受けない代わりに「`」(バッククォート)で囲むもの、の四通り、となる。なお、Javaでもそうだが、日本語は大小どちらでも無い文字に(少なくともUnicodeの分類では)属するのに、なぜか先頭文字にも使えたりする。

リテラル

Javaのリテラルは、配列を除けばそのままScalaのリテラルである(一応Javaの型名も使えるが、Scalaの本来のそれはその先頭1文字を大文字にしたものである;ただし、文字列型だけは特別に、次に紹介する複数行文字列も含めて、java.lang.String型である);配列はオブジェクト型のみあって、後に述べる「疑似リテラル」に属する。代わりと言っては何だが、ScalaではJavaの配列以外のリテラルに加えてさらに、複数行文字リテラル、シンボルリテラル、タプルリテラルとXMLリテラルが使用可能だ。

複数行文字リテラルは、「”””」(二重引用符三つ)で囲み、その間には、普通の文字列ではエスケープシーケンスで表されるものが直接在って良い;つまり、改行やタブや二重引用符(連続は2つまで)がそのまま使える。また、エスケープシーケンスは変換されずにそのまま扱われるので、エスケープ文字がやたら多い時の正規表現用文字列としても有益だ(Unicodeシーケンスはここでもやはり変換される)。Pythonではこれを使って対話シェルやプログラムから呼び出せるオンラインヘルプデータとしているが、Scalaではその習慣は無い。

シンボルリテラルは、「'」(一重引用符)に続く識別子を、要するにJavaのintern文字列(常に同一の参照実体である文字列)とするものだ。

タプルリテラルは、多様な型からなる不変集合で、「(1, "abc", 0.5)」のような形で示す。各要素は、1から始まるインデクスで、それをaとした場合に「a._1」(1)、「a._2」("abc")として参照できる。また、空のタプルはUnit型という、Javaではvoidに相当する意味を持つ。型としては、この場合は「(Int, java.lang.String, Double)」で示す。

XMLリテラルは、XMLをそのままリテラルとして扱える、ということだ(ただし、最初の「<」の前は空白か「(」または「{」でなければならない)。また、「{a._2}」という形でScala式を埋め込むことができる(逆にこのため、XMLデータ自体として「{」を示す時は「{{」と二重にしなければならない)。これの型はscala.xml.Nodeである。

疑似リテラル

Scalaでは、関数・メソッドは実は内部にapplyメソッドを持つクラスのインスタンスである。逆に言えば、自己のインスタンスを返すapplyメソッドを持つstaticなクラス(すなわちScalaで言うところのobject)は「インスタンスを生成する関数」として「new」無しで使え、これを(現状私だけだが)疑似リテラルと呼ぶ。Scalaにおける代表的な疑似リテラルは次の四つだ。


配列:

配列の疑似リテラルは「Array("ab", "cd", "ef")」のような形で、これはJavaでの「{"ab", "cd", "ef"}」に等しい。Javaと同じく要素(型は一種のみ)の変更はできるが長さは固定のインデクスはゼロから始まるシーケンスで、参照は、aをその配列として「a(1)」("cd")、変更は「a(1) = "CD"」("ab", "CD", "ef")の形となる(前者はインスタンス用のapply、後者は同じくupdateメソッドの適用がその実体である)。この場合の型は「Array[java.lang.String]」である。多重配列は、リテラルは言わずとも分かるだろうが、参照は「a(1)(2)」のような形になる(一見Javaと同じで当たり前に見えるかもしれないが、Scalaの配列はあくまで単なるオブジェクトであることを思い出してほしい。これは「(a(1))(2)」、すなわち「a(1)の結果である配列の(2)」の簡略表現で、この形は配列に限らず結果を返すすべての関数やメソッドで使用可能である)。なお、配列(に限らずJavaにほぼ等価なしかしより効率的なものがある型はすべてそうだが)は可能な限り、コンパイル時にJavaの配列へと最適化される。ゼロ等で初期化されたある長さの配列は「new Array[要素の型](個数)」で得られ、また同様な多重配列は例えば「new Array[Array[Array[Int]]](3, 3, 3)」で得ることができる(空要素3の配列3つを持つ3つの配列を要素とする配列)。リテラル(「Array(Array(1, 2), Array(3, 4))」で各々の要素を持つ配列を要素とする配列)同様妙に煩雑に思えるかもしれないが、これは、Scalaのような言語(関数型言語)では基本的な構造型は配列では無く下のリストが普通それだから、だ;配列のような内容書き換えが可能なデータは意図せぬ副作用が生じる恐れがあり、しかしそれでは関数型言語の主義「副作用をできる限り排除することで関数を汎用化する」ことに反するからである。だから、一応Javaとの対応を示すために配列を先に紹介するのだけれども、Scalaにおけるプログラミングでは配列では無くリストをこそ主要構造型として多用するべきことが奨励される。


リスト:

リストは不変配列、すなわち内容の変更も不可(メソッド適用はJavaの文字列同様に、対象を書き換えるのでは無く適用結果であるところの新たなインスタンスを返す)な配列で、「List("ab", "cd", "ef")」が疑似リテラルである。ただし、こちらでは「"ab"::"cd"::"ef"::Nil」という形も使える(最後の「::Nil」(「::List()」でも可、というかNilは空リストを表す疑似リテラルである)が無いとエラーになるので注意。リストは、最初のNil無しのリテラルでも必ず最後に、要素数にはカウントされない暗黙のNilを持つ)。Nilを先頭にして「::」の代わりに「+」でもやはり結果は同じだが(「+」の方は他の不変集合でも同様に使える)、右から加えていく「::」の方が何かと便利なのか、リストの場合は大抵それが使われるようだ。なお、配列とリスト(とscala.xml.Node)はSeq型に属する、つまり、scala.Seqの子孫クラスである(タプルと、次に述べるセット及びマップはSeq型では無い)。型は、Arrayの代わりにListである以外は配列と同様。


セット:

セットは要素の重複が無い同一型の集合で、基本的には不変である(可変もあるが、インポートが必要)。疑似リテラルは「Set("ab", "cd", "ef")」で、むろん「Set("ab", "cd", "ef", "ab")」でも結果は同じである(無重複の名に違わず、重複する最後の"ab"は無視してくれる)。型は、厳密には「scala.collection.immutable.Set[java.lang.String]」だが、この不変(immutable)セットに限ってはプログラム上ではただの「Set[java.lang.String]」で表して良い。


マップ:

マップはいわゆる連想配列のことで、その疑似リテラルは「Map(1 -> "ab", 2 -> "cd", 3 -> "ef")」である;「キー -> 内容」で、キーはキー、内容は内容で各々同一型でなければならない。これも基本的には不変である(こちらもインポートすれば可変版もある)。参照はそれをmとして「m(2)」("cd")と、インデクスで無くキーを実引数に与える。なお、「->」はPair型と特に名指されるタプルを生成する演算子で、実際のところ「(1 -> "ab")」の代わりに「(1, "ab")」のタプルで初期化しても結果は同じである。型は、「scala.collection.immutable.Map[Int, java.lang.String]」だが、セットの場合と同様にただの「Map[Int, java.lang.String]」で表して良い。

パターン

パターンとは、Javaで言えばswitch文のcaseによるマッチングのようなものだが、Scalaのそれは遥かに多機能な上に、構文的にも重要な意味を持つので、いわば準リテラルなもの、としてここで解説しておく。

リテラルにマッチ、識別子の値にマッチ、XMLパターン(XMLリテラルのうち、属性指定を除いたもの。埋め込めるのもScalaの式では無くパターンのみ)にマッチ、は、基本な上に説明する必要もあるまい。しかしScalaのマッチは、単なるマッチに留まらず、その一部を変数識別子に束縛することで抜き出すことができる(「束縛」は次ページで解説するが、ここでは代入のようなものだと思えばまあ間違いでは無い);「変数識別子」というのは、先頭が小文字で始まる識別子のことだ(このゆえに、逆に、その値にマッチさせたい識別子は小文字以外で始めるか、さもなければバッククォートで囲まねばならない。なお、Scalaで先頭文字の大小が特別な意味を持つのはこのパターンにおける変数識別子だけである)。さらにもう2つ、「_」一字が捨てワイルドカード、また「_*」がSeq型の残りすべて捨て、の意味で使える。また、変数識別子の後ろに「:」に続けて型名を記述すればその型に一致するものをその変数識別子に束縛させ、さらに「@」に別のマッチパターンで、そのマッチ全体をその「@」の前の変数識別子に束縛させられる。さらに、「|」で区切って「いずれかにマッチ」させられるし(ただしこの場合、束縛は「_」にのみ可)、そしてインスタンスのクラスがunapplyメソッドを持つものなら、「クラス名(変数識別子)」でそのインスタンスの初期化実引数を抜き出すことも可能だ(それがSeq型ならunapplyの代わりにunapplySeqメソッドを持っていなければならないが)。

実は以上でとりあえず必要なパターンの説明はすべて済んでいるのだが、その便利さを知るには実際に例を見た方が手っ取り早いだろう。

ex: IOException		// IOExceptionのインスタンスにマッチし、それをexに束縛
Array(1, x, 3)		// この配列にマッチしその第二要素をxに束縛
(x, _)			// 任意のペアなタプルにマッチしその第一要素をxに束縛
x::y::z			// 要素が二つ以上のリストにマッチし、三つ目以降はすべてzに束縛
1 | 2 | 3		// 1か2か3にマッチ
x@ <head>{_*}</head>	// headタグで囲まれたXML部分にマッチし、それをxに束縛

四番目は、例えばマッチしたのが「List(1, 2, 3, 4)」なら、xは1、yは2、zはList(3, 4)になる、ということである;ただしこのテクニックもまたSeq型にしか使えない(ただし、デフォルトではリストのみ。この形は「a op b」を「op(a, b)」と見なすものなので、配列で同様のことをしたければ自前でそのopに相当するものを作っておかねばならない)。ちなみにもちろん、Array型もList型もunapplySeqメソッド持ちである。なお、マッチするパターンが無い場合はMatchErrorが投げられる。


あとついでにもう一つ、Scalaではクラス、トレイト、シングルトンをまとめて「テンプレート(定義)」と呼ぶ;トレイトとはJavaのinterfaceのようなもの、シングルトンとは既に何度か触れているobjectのことだ。


前ページ | トップページ | 次ページ