勉強帳 (1)

Scala を勉強してみます。売り文句だけ見てみると

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)

まとめ

なかなか面白そうな言語ですね。でも、自分の常用に耐える言語かどうかはまだわからない。楽チンなリテラルとか正規表現とかどうなのかな。
とりあえず、インタプリタの起動が遅すぎるのは何とかならないかなあ。対話的環境は Ctrl+C で終了してしまうのがうっとうしい。
次は Scala By Example を読もう。