kmizuの日記

プログラミングや形式言語に関係のあることを書いたり書かなかったり。

Re: 勉強帳 (1)

まめめもより。

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

  • 1. 手続き型でも関数型でも書ける
  • 3. 柔軟な文法で DSL しやすい
  • 4. パターンマッチ
  • 6. JVM で動く (Java の資産を活用できる)

以下はチュートリアルやマニュアルを読んで得たいい加減な理解や疑問点を晒すものです。間違ってるところは教えてください。

http://d.hatena.ne.jp/ku-ma-me/20090512/p1

Scalaを初めて調べて見た人の多くが疑問に思う点が網羅されていたので、せっかくなので、ツッコミのネタにさせていただきます。

class 以外に object という構文があるらしくて、特異メソッドつきのオブジェクトみたいなものを定義しているっぽい。それが Java の static メソッドに相当するのかな。static メソッドと普通のメソッドが混ざってるクラスは表現できるのかしら。できなくてもいいのかな。

Javaのstaticメソッドに相当するというのはその通りです。staticメソッドと普通のメソッドが混ざっているクラスは、companion objectという、classと同じコンパイル単位・同じ名前で定義されたobjectによって表現します(classとobjectは別の名前空間を持ちます)。companion objectはcompanion classに対して特権的なアクセス権を持っており、privateなフィールドにもアクセスできます(companion objectからのアクセスを防ぎたい場合、private[this]とします)。

class A {//object Aのcompanion class
  private val x = 1
}
object A {// class Aのcompanion object
  def display(a: A) {
    println(a.x) // privateなデータにアクセスできる
  }
}

これもめちゃ遅い!対話的環境で試すしかないか。

起動時間とコンパイルの遅さはいかんともしがたいところですね。この辺はあきらめるしか。コンパイル時間については、Scalaのディストリビューションに付属のfscという、コンパイラのデーモンが常駐するプログラムを使えば多少は緩和できます。

文法の話があった。一引数のメソッドはなんでも中置にできるらしい。

厳密に言うと、レシーバを指定した呼び出しでは、任意のメソッドについて中置にできて(.を省略できて)、一引数の場合、さらに引数を囲む括弧も省略でる、という形になります。たとえば、以下のような呼び出しが可能です。

System.out printf("%s world%n", "hello")
def foo(f: () => Unit) { f() }
def bar() { println("Hello") }
foo(bar)  // Hello と出力

ふんふん。メソッド名を括弧なしで書けば関数オブジェクトが取り出せる。Python 風な

これは少し違いまして、メソッドから関数オブジェクトを取り出すには、一般には、メソッド名 _とする必要があります。上の例だと、foo(bar _)となります。ただ、関数オブジェクトを期待している箇所に対して、それに適合する型のメソッドが現れた場合、暗黙に関数オブジェクトに変換してくれるというようなルールがあるため、上の場合、メソッド名を括弧無しで書くだけで関数オブジェクトに変換してくれます。

def foo(f: => Unit) { println("before"); f; println("after") }
foo({ println("Hello") })  // before, Hello, after と出力
foo { println("Hello") }   // before, Hello, after と出力

うーん、{ println("foo") } は無引数の匿名関数なんですかね。Rubyイテレータみたいなことを許すべく設計された感じの。

でもこれだけ書くとその場で実行しちゃうようだ。匿名関数じゃなさそう。

これは、{ println("foo") }が特別なのではなくて、def foo(f: => Unit)の中の、f: => Unitの部分で、引数fの評価が遅延される(call-by-name)ことを指定しているのがミソです。{ println("foo") }は単なるUnitを返す式ですが、fooの引数にそれを渡した場合、評価が遅延されるわけです。

class Foo(_x: Int, _y: Int) {
  def x() = _x
  def y() = _y
}

Foo はクラスで、そのコンストラクタは Int を 2 つ受け取る、という感じ。コンストラクタをオーバーロードで複数用意する方法はないのかな。

def this(引数1, ...)のようにすることで、コンストラクタをオーバーロードすることができます。たとえば、class Fooについて、以下のようにして、_yだけを引数に持つ、オーバーロードされたコンストラクタを用意することができます。

class Foo(_x: Int, _y: Int) {
  def x() = _x
  def y() = _y
  def this(_y: Int) = this(0, _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 みたいな型になって、呼び出しには括弧が省略できない (括弧を省略すると関数オブジェクトが得られる) 。逆に「無引数のメソッド」は、呼び出しには括弧をつけられない (型はなんだろう。あと関数オブジェクトは得られないのかな?) 。うーん、そういう設計もありか。

この辺、歴史的事情もあって、バッドノウハウ的な話になるのですが、実は、「0引数のメソッド」も問題無く括弧を省略することができます。あと、「0 引数のメソッド」も「無引数のメソッド」のどちらも、メソッド名 _で関数オブジェクトを得ることができます。この辺の仕様のごちゃごちゃした部分は、将来のバージョンで修正されるとかされないとか。

で、パターンマッチはどうやるかというと。

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 を出力

パターンマッチに関してですが、matchというキーワードを使う形が本来の形で、 { case ... }だけの式は、exp => exp match { case ... }という無名関数のシンタックスシュガーになります。型推論については、関数オブジェクトを要求している事がわかっている文脈であれば、適切に推論することができます。たとえば、以下のように書けます。この辺、MLやHaskellに比べて、型推論が機能する場所がある程度限定されているわけですが、Scalaの型システムを考えると、やむを得ないかなあ…と思います。ちなみに、matchが中置形式になってることですが、別に文法上特別な意味は無いです。

def foo(x: Int => Int) = x(100)

foo { case 100 => 101 } // fooの引数はInt => Intである事がわかっているので、明示的に型を書かなくても良い。

Ruby でいう include をするためには、普通に extends する。

class Baz extends FooBar {
  def foo(x: Boolean) = x
}
println((new Baz).bar(true))  // false と出力

この辺を静的型付けでチェックしてくれるのは結構うれしそう。本当にチェックしてくれるのか知らないけど。もちろんしてくれるよね?

はい。もちろん、普通に型チェックされます。