インストールである。Scalaサイトのダウンロードページから適当なのを見繕って実行または解凍する、とりあえずはそれだけである。あとは環境変数SCALA_HOMEを設定し、そのSCALA_HOMEのbinディレクトリをPATH環境変数に加える;前者はエディタやIDEが参照する可能性が高いし(Scalaのシステム自体は、それが無ければ自身(実行ファイル)のいるディレクトリ(bin)の1つ上をscala.homeプロパティと見なす)、後者は左に述べた理由ゆえにその実行ファイル(実体はシェルスクリプトまたはBATファイル)の場所をうかつに動かせないからである。
この場所は、既にsbazローカルディレクトリ(詳細はコマンドの説明を見よ)なので、以降のバージョンアップ等はsbazコマンドで簡単にできる。別の場所をsbazローカルディレクトリにしたい時は、「sbaz-setup そのディレクトリのパス」とすればsbazシステム自体と必要なディレクトリを自動で作ってくれるので、SCALA_HOMEとPATHを書き換え有効化させてから、「sbaz install」で「scala」、「scalap」、「scala-documentation」、「scala-devel-docs」(ライブラリのAPIはここに含まれる)、「scala-tool-support」(色んなエディタ用のScala編集用設定ファイル「だけ」入っている)、「sbaz-setup」パッケージをインストールし直せば、ライブラリAPI(サイトからのダウンロード版には入っていない。Scalaサイトから見れるAPIドキュメントはRC(リリース候補)版が出るとそちらに変わってしまうので、いずれはsbazでfinal版用のをダウンロードすることとなろう)以外は元のダウンロード版と同じ構成になる。
日本語である。Scala自身はJVMでUTF-8デフォルトでJavaのライブラリも難無く使えるので日本語も特には問題無いが、対話シェルで(入力メソッドにもよるかもしれないが)日本語入力中の操作用キー入力まで拾ってしまい、表示が著しく乱れる(Linuxでは入力自体はできるが、Windowsでは文字化けしたままになる)。またこれはプラットフォームにもよろうが、DELキーがうまく機能しない。これらはしかしすべて、対話シェルが外部的ライブラリとして利用しているjlineという「Javaのコンソールモードでも左右矢印キーやBSキーで入力中の行が編集できたり、上下矢印キーで以前の入力行を呼び出したりできるようにする」もののゆえである。現在はWindowsのコマンドプロンプトでは別にそれ無しでも同様のことができるし、Linuxでもrlwrapというツールでやはり同様のことができるので、これは、作者やScala開発チームには申し分けなくも、やはり外してしまった方が良い。その実体はzipであるところのSCALA_HOME/lib/scala-compiler.jarをzipが編集できるGUIツールで開いて、jlineというフォルダを削除すれば、それでもう対話シェルでの日本語入力も全然オッケーだ。
上を書いて後、scalacの-Xnojlineオプション発見:コンパイラのソースを見るに、jlineではなくJava自身の標準入出力を使うようにするものなので、これさえ指定すれば、もはや上のように内蔵jlineをわざわざ削除せずともrlwrap等が使えるようになる(元々EmacsのIDE等を考慮してのオプションなので、-X系ではあれ将来も残ると思われ)。
Scalaでは、プログラムはコンパイルモード、スクリプトモード、対話シェルモードの三通りで動かすことができる。このうち、最も基本となるのはコンパイルモードなので、まずはそれから説明しよう。
Scalaではstaticなクラスをobjectで定義し、また文字列の配列はArray[String]の型で指定できる。実行時にscala-library.jarが最低限必要とはいえ、classファイルにコンパイルされたScalaプログラムは結局はJavaプログラムでもあるので、その起動部分の書き方は本質的にはJavaのそれとまったくに同じである。
object HelloApp {
def main(args: Array[String]) {
println("Hello myself!")
}
}
上のコードをscalacでコンパイルして「scala HelloApp」とすれば((Linuxでを例とすれば)「java -cp $SCALA_HOME/lib/scala-library.jar:. HelloApp」でも可)、まったくの期待通りに「Hello myself!」と表示して終わる。あとは、objectの名前を変えたり、mainメソッドの中身を変えたりするだけで、自分だけのプログラムが作れ、コンピュータにそれを実行させられる。
しかし、スクリプトモードではもっと簡単に、
println("Hello myself!")
とたったこれだけで同じことができる。しかしその実体は、実は上のプログラムとまったく同じだ;scalaコマンドがこのたった一行のコードを(少なくともユーザからは)匿名のobjectのmainメソッドでくるんでコンパイルし実行しているだけなのだ。だからargsも既に自動定義されてスクリプトプログラムから参照できるし、また結局はコンパイルされているのでコンパイルモードとまったく同じ速度で実行されることにもなる(ちなみに、Scalaで記述されたプログラムの実行速度は、Javaで直接記述した場合とほとんど同じである;Scalaの作者は現行のjavacの最初のバージョンの作者でもあるわけで(まあ高速化よりはジェネリクスの実装のためだったのだろうが)、JVMの長所短所やそれにおける高速化技術を知り尽している、ということなのだろう)。ところでしかし、では上のコンパイルモード版をスクリプトモードでも動かせないだろうか? 答えは残念ながら「ノー」だ;ブロックは定義では無く結果式で終わらねばならない、というエラーが出てしまう。これまで説明したことを踏まえれば、この時実行されたものは次の形になる。
object 匿名object {
def main(args: Array[String]) {
object HelloApp {
def main(args: Array[String]) {
println("Hello myself!")
}
}
}
}
Scalaでは、クラスもメソッドも各々のみならずお互いにも入れ子定義できるので、この一見妙な形自体は実際には間違っていない;問題は、呼ばれるのは匿名objectのmainであって、HelloAppのmainではない、という所にある。そこで、
HelloApp.main
object HelloApp {
def main(args: Array[String]) {
println("Hello myself!")
}
}
の形にすれば、これは問題無くスクリプトモードで実行される。改めてまたscalacでコンパイルしたい時は一行目をコメントアウトするだけで良い。実際には、一行スクリプトだけでも「scalac -Xscript HelloApp HelloScript.scala」とすればコンパイラが自動で最初の例の形にしてくれるので、どちらの方法を選ぶかはプログラムの規模や複雑さの問題となろう(スクリプトモードのプログラムは暗黙のブロック内にいるので、例えばパッケージ定義等のブロック内でできないことはできない。改良例でもそのままでは無理だが、一行目をコメントアウトし代わりにパッケージ定義を置くようにすれば、開発の途中まではスクリプトモードで動かせることになる(対話シェルに読み込ませて関数やクラスの振る舞いをあれこれ試せるわけだ))。なお、「scala -savecompiled スクリプトファイル名」とすればコンパイル済みファイルがスクリプトファイル名でただし拡張子はjarで作成され、次以降の「scala スクリプトファイル名」ではそちらが実行されることになって、いくらか(実行開始までが、だが)速くなるようだ。
さて、対話シェルモードは、いくらかややこしい。だが、基本は以上とまったく同じだ。簡単に言えば、対話シェルは有効行、すなわちScalaの行として意味がある行が成立するのを待って、その最後の改行が打ち込まれるやたちまちこれを(ユーザにとっては)匿名のobjectにくるみコンパイルしてしまう。それからその実行結果をリポータobjectへ送り、そのリポータobjectがそれを画面表示する。行がコンパイルされる際には、直前の行のobjectのメンバが新たに生成されるobjectにインポートされ、それであたかも状態が持続しているかのように見せかけている。このために、コンパイルモードやスクリプトモードでは不可能なことが2つ生じる。1つは、同じ識別子が何度も再定義できてしまうことだ。Scalaの文法では同一ブロック内で同じ識別子を再定義するのは文法違反であって必ずエラーになるのだが、この対話シェルの仕組みから分かるように、各有効行は各々別のobject、すなわち別のブロックなので、それができてしまう(多重定義では無く、インポートされた識別子の隠蔽に過ぎなくなるのである。試しに「;」で区切って同一行内で多重定義すれば、こちらはなるほどちゃんとエラーになる)。もう1つは、式だけの行は本来識別子に束縛されないので二度と参照できないはずなのだが、リポータobjectが表示する「仮識別子」で実は以降もその結果にアクセス可能だったりする。これは、その仮識別子が「公開」されることでリポータobjectがそれを読み取る仕組みになっているためで、「公開」されているために、新たなobjectにそれも一緒にインポートされてしまうから、なのだ。まあとまれ、そんな奇妙な振舞いの代償にこの方法論で得られるのは、「対話シェル上のプログラム(というか式)でさえコンパイルモードと同じ速度で実行される」という果実である。例えば再帰関数のテストも、Scalaなら対話シェル上でもそれほどストレス無く試せてしまうわけである(また、いずれの方法も同じコンパイラを使うので、実装の違いに悩まされることも無い)。
なお、スクリプトモードではscalaはスクリプトファイル冒頭の「#!」から「!#」の間、または「::#!」から「::!#」までの間は無視するので、Linuxのbashなら、
#!/bin/bash
exec scala -savecompiled "$0" "$@"
!#
println("Hello myself!")
WindowsのBATファイルなら、
::#!
@echo off
call scala %0 %*
goto :eof
::!#
println("Hello myself!")
とすれば、スクリプトファイル名だけで実行可能ないわゆるシェルスクリプトにできる。
ごく一部の人にだけ意味のあるおまけ:
Scalaに付随する、jEditのコンソール用commandoのうち、scalacのそれにコマンドライン引数を指定可能にしたものと、あと追加でスクリプトモード用のscalas。なおちなみに、rlwrapはjEditの内部コンソールからは呼び出せない (エラーになる) ので、そこから呼び出すためのrlwrap抜き版シェルスクリプトも作っておくと幸せである (内部コンソール自体がすでに自前でrlwrap相当の機能を持っている)。