Scala を勉強してみます。売り文句だけ見てみると
- 1. 手続き型でも関数型でも書ける
- 2. 純粋なオブジェクト指向
- 3. 柔軟な文法で DSL しやすい
- 4. パターンマッチ
- 5. 静的型付け (ジェネリクスあり)
- 6. JVM で動く (Java の資産を活用できる)
1 〜 3 は Ruby と同じで、4 と 5 は Ruby にないところ。
以下はチュートリアルやマニュアルを読んで得たいい加減な理解や疑問点を晒すものです。間違ってるところは教えてください。
資料: http://www.scala-lang.org/docu/files/ScalaTutorial.pdf
1. Introduction
いんとろだくしょん
2. A first example
いちおう書いてあるとおりの Hello, World 。
object HelloWorld { def main(args: Array[String]) { println("Hello, world!") } }
class 以外に object という構文があるらしくて、特異メソッドつきのオブジェクトみたいなものを定義しているっぽい。それが Java の static メソッドに相当するのかな。static メソッドと普通のメソッドが混ざってるクラスは表現できるのかしら。できなくてもいいのかな。
一応実行。
$ time scalac HelloWorld.scala real 0m4.864s user 0m6.796s sys 0m0.240s $ time scala HelloWorld Hello, world! real 0m0.729s user 0m0.652s sys 0m0.064s
お・そ・い!
いちいちファイルを作るのが嫌なのでワンライナーのやり方を試行錯誤する。
$ time scala -e 'println("Hello, world")' Hello, world real 0m2.190s user 0m0.872s sys 0m0.056s
これもめちゃ遅い!対話的環境で試すしかないか。
3. Interaction with Java
興味ないのでほぼスルー
文法の話があった。一引数のメソッドはなんでも中置にできるらしい。
df format now
は
df.format(now)
の syntax sugar 。慣れるまではキモそうだけど、思い切った文法だ。I love you とか普通に書けそうで、DSL 向きの工夫だなあ。
4. Everything is an object
数や関数もオブジェクト。いちいち言うまでもないことだよね。Java が変なだけで。
さっきの文法の話。
1 + 2 * 3 / X
は
(1).+(((2).*(3))./(X))
の syntax sugar 。まあ Ruby と同じ。といっても Scala の場合は、任意の一引数メソッドが中置にできるので、「+ や * はただの識別子とまったく同じ」というあたりがきれい。
関数オブジェクト。
def foo(f: () => Unit) { f() } def bar() { println("Hello") } foo(bar) // Hello と出力
ふんふん。メソッド名を括弧なしで書けば関数オブジェクトが取り出せる。Python 風な。メソッド呼び出しで括弧は省略できないの?という疑問は後で解消される。
もちろん匿名関数もある。あ、無名関数って言うんだっけ?まあどっちでもいいや。
def foo(f: () => Unit) { f() } foo(() => println("Hello")) // Hello と出力
() => println("Hello") の部分。
試行錯誤してたらこんなの動いた。
def foo(f: => Unit) { println("before"); f; println("after") } foo({ println("Hello") }) // before, Hello, after と出力 foo { println("Hello") } // before, Hello, after と出力
うーん、{ println("foo") } は無引数の匿名関数なんですかね。Ruby のイテレータみたいなことを許すべく設計された感じの。
でもこれだけ書くとその場で実行しちゃうようだ。匿名関数じゃなさそう。
val foo = { println("foo") } // foo を出力
5. Classes
class Foo(_x: Int, _y: Int) { def x() = _x def y() = _y }
Foo はクラスで、そのコンストラクタは Int を 2 つ受け取る、という感じ。コンストラクタをオーバーロードで複数用意する方法はないのかな。
インスタンス生成はこう。じゃばじゃば。
new Foo(1, 2)
あと、def x() = _x のところで返り値の型を明示することもできるみたい。
class Foo(_x: Int, _y: Int) { def x() : Int = _x def y() : Int = _y }
というか、「推論できない場合もあるのでそういうときは明示してね。どういうときに推論できないかは一概には言えない。慣れろ」みたいなことが書いてあった。
次にメソッド呼び出しの括弧の省略の話。
val foo = new Foo(1, 2) println(foo.x())
って毎回 .x() に括弧つけるのダサいよね、フィールドみたいにしたいよね、という人は
class Foo(_x: Int, _y: Int) { def x = _x def y = _y } val foo = new Foo(1, 2) println(foo.x)
としろとのこと。「0 引数のメソッド」と「無引数のメソッド」が区別されるらしい。「0 引数のメソッド」は () => Unit みたいな型になって、呼び出しには括弧が省略できない (括弧を省略すると関数オブジェクトが得られる) 。逆に「無引数のメソッド」は、呼び出しには括弧をつけられない (型はなんだろう。あと関数オブジェクトは得られないのかな?) 。うーん、そういう設計もありか。
メソッドをオーバーライドするときは override って modifier を def の前につける。C# もそうだっけ。まあ普通だと思う。
6. Case classes and pattern matching
これこれ。
パターンマッチにかけられるオブジェクトのクラスは、ケースクラスという特殊なクラスである必要があるみたい。要するにバリアントであろう。
data Tree = Sum Tree Tree | Const Int
みたいなものは
abstract class Tree case class Sum(l: Tree, r: Tree) extends Tree case class Const(v: Int) extends Tree
と書く。うわっ長いダサいうざい。
ケースクラスと普通のクラスの違いは以下のとおり。
- インスタンス化するのに new がいらない。Const(5) だけでいい。
- getter 関数が勝手に作られる。つまり Const(5).v と書ける。
- equals と hashCode と toString が勝手に適当に作られる。
- パターンマッチにかけられる。
で、パターンマッチはどうやるかというと。
def foo : String => Int = { case "x" => 1 case "y" => 2 } println(foo("x")) // 1 を出力
{ case "x" => 1 } だけで「引数を受け取って、それが "x" ならば 1 を返す、そうでなければ MatchError を投げる関数」になるみたい。ただし、何でかわからないけどこれは型推論できないみたい。なので、{ case "x" => 1 } : String => Int とかなんとか書く必要があるみたい。みたいみたい。
あと、パターンマッチには match というキーワードを使う方法もあるらしい。
"x" match { case "x" => println(1) case "y" => println(2) } // 1 を出力
scala.Object#match が定義されてるのかなと思ったけど、そうではないみたい。だとしたら、この文法に特別な意味があるのかな。まだよくわからない。
以上を使って、あとはよくある計算機。
def eval(t: Tree): Int = t match { case Sum(l, r) => eval(l) + eval(r) case Const(v) => v }
ふんふん。
余談だけど、以下で型の alias が書けるらしい。Haskell の type と同じようなもだろう。
type Environment = String => Int
そういえば、Haskell の場合は type 使っても、型チェッカのエラーメッセージで分解されて表示されちゃうのがちょっと悲しい。
7. Traits
一般の trait がどういうものか知らないのだけれど、少なくとも Scala の trait は Ruby の module みたいなものらしい。
trait FooBar { def foo(x: Boolean): Boolean def bar(x: Boolean) = !foo(x) }
あらかじめ定義されている foo メソッドを使って、bar を定義する trait 。
Ruby でいう include をするためには、普通に extends する。
class Baz extends FooBar { def foo(x: Boolean) = x } println((new Baz).bar(true)) // false と出力
この辺を静的型付けでチェックしてくれるのは結構うれしそう。本当にチェックしてくれるのか知らないけど。もちろんしてくれるよね?
8. Genericity
最後はジェネリクス。
リファレンスセルの定義。
class Ref[T] { private var content: T = _ def set(value: T) { content = value } def get: T = content }
_ というのはデフォルト値を表して、数値なら 0 、Boolean なら false 、Unit なら () 、オブジェクト型なら null になるらしい。
あとは Ref の使い方。普通に見える。
val cell = new Ref[Int] cell.set(1) println(cell.get)