Scala覚え書き その1 HelloWorldと関数定義と無名関数
HelloWorld
JRubyな高井さんのサイトに本家のチュートリアルの写経シリーズ(http://recompile.net/2007/07/a-scala-tutorial-for-java-prog.html)があったので、HelloWorldを試す。emacs上にコピペして、run-scalaしてC-cC-l。無事にHelloWorldが出力された。
//class HelloWorld { // クラス宣言 object HelloWorld { def main(args : Array[String]) { println("Hello, World") } } HelloWorld.main(null) // インタプリタで動かすためのコード
*1
このHelloWorldは"object宣言"で作られている。object宣言を使うとHelloWorldクラスを定義した上に、そのクラスのシングルトンオブジェクトを生成して、"HelloWorld"という名前と結びつけるらしい。HelloWorldはすでにオブジェクトなのでnewできない。class宣言というのもあって、それを使うとnewできるらしい。
元々のチュートリアルのコードには最終行は存在していない。コンパイルしてJavaのclassファイルを作った後にHelloWorldクラスを呼びだすとmain関数が呼ばれるらしい。最終行は、インタプリタに読みこませた時に出力されるように追加したもの。
じゃあクラス宣言にしたらこれでは動かないのかな、と思いきや、object宣言をclass宣言に置き変えても、HelloWorld.main(null)で無事に動いてしまった。class宣言だと、「HelloWorld」というのはインスタンスじゃなくてクラスになると思うんだけど、クラスからでもmainはそのまま呼べるのかな。インスタンスメソッドも呼べる仕様なのか、mainはインスタンスメソッドではないのか、それともクラスとインスタンスの境目があいまいなんだろうか。まだ謎。
関数定義
クラス定義やオブジェクト定義(?)をしなくても、べたにdefで関数定義ができるらし。というわけでお約束の階乗。
def fact(n:Int):Int = { if (n == 1) {1} else {(n * fact(n-1))} }
"(n:Int):Int"なあたりは引数と関数(の返り値)の型を宣言している。Scalaはある程度型推論をやってくれて、返り値の型は省略しても推論してくれる。引数の型は省略するとエラーになってしまった。残念。上の例で返り値の型まで書いてあるのは、再帰関数だと返り値の型も書かなきゃいけないようだから。これまた残念。
あとちょっと変わってるのが関数定義の構文。中括弧を多用するところはCに似てるので、Cのように関数シグネチャの後に直接{}って書いてたらパースエラーになった。"= {}"にしないといけないようだ。このへんの感覚は、無名関数をつくってから変数と結びつける雰囲気で関数型ぽい。
{}の中はどう書くか、というとこれはschemeやruby風。複数の式を羅列することができて、順番に実行される。最後の式の値が全体の値として返る。最後の式以外は副作用のある式を書くことになるんだろうな。きっと。
無名関数
じゃあ無名関数は、というと、これは"=>"で作れる。無名関数を作ってから変数に代入してみたりした。
def add(a:Int,b:Int) = {a+b} val add2= {(a:Int,b:Int) => a + b} val add3:Int=>Int=>Int = {a => b => a + b}
add3はカリー化されてるかんじか?変数の型は無名関数の入力に書いても、変数側にまとめて書いてもよい。けっこう柔軟。
ついでに変数の話も。valっていうのは変数の宣言なんだけど、もう一つvarってのもある。valは更新不能でvarは更新可能らしい。
val id = {n:Int=>n} var id2 = {n:Int=>n} // id = {n:Int=>n} //これはエラー id2 = {n:Int=>n+1}
valを使ったidへの再代入である3行目はエラーになっちゃうけど、varを使ったid2のほうは関数も更新できた。型はあってないと型エラーといわれてしまうけど、型さえあってりゃ関数の中身を書きかえられる。関数の中身を書き換えられるのに型のチェックはする、とも言えるか。どちらにしてもちょっと面白い。
まとめ
疲れてきたのでこのへんで適当にまとめ。
- 型指定はPASCAL風というかOcaml風というか基本は変数の後に型を書く。関数の返り値の型は関数シグネチャの直後に書く。
- 個人的には関数定義とは独立に型を指定できるhaskell風なのが好みなので残念。
- 関数の仮引数の型は必ず明示する必要がある
- 返り値の型は省略しても推論してくれる
- でも再帰になってると推論してくれない
- 関数定義に"="を忘れるな
- 無名関数は "=>"で定義可能
- 変数にはvalとvarの二種類ある。
基本は関数型ながら、手続型っぽい機能もシンプルに取りこんでる感じでわりと好感触。手続スキーな人から見ると真逆の感想になるかもしんない。