不満の記録

flagir を作るなどの過程で感じた Scala への不満を列挙。こんな記事を (日本語で) 公開しても誰も得しないし誰も読まないだろうから、やめようかなとも思ったんだけど。ぼくの勘違いなら指摘してもらえるかもしれないし、記録という意味もあるのでやっぱり公開。

文法

  • 束縛前の val 変数を参照できてしまった。かなりはまった。これは Scala のバグと言っていいと思う *1 。どう直すかというとなかなか難しいけれど。
object T {
  val foo = setup_foo
  val bar = 1

  def setup_foo = {
    bar // これが実行される時点では bar が 1 に束縛されていない
  }

  def main(args: Array[String]) {
    println(foo)  // 0 が出力される
  }
}
  • 空行があるかないかで意味が変わって、はまった。ちょっとスコープを作ろうと思っただけなのに *2
foo = 0
{ // syntax error
  val bar = 1
}
foo = 0

{ // OK
  val bar = 1
}
  • val で複数の変数が定義しにくい。
val x = 1, y = 1    // syntax error
val (x, y) = (1, 1) // 実際に Tuple を作るので、コアループでは使いたくない

val x, y, z = 1     // x == y == z == 1 になる。こんなの誰がうれしいんだ
val x@(y@z) = 1     // どうしても上の動作がしたいならこれでいいと思うんだが
  • val で同じ名前の変数を束縛できない (隠せない) 。ocaml の let はこれができていいですよね。
val x = 1
// ... x を使う ...
val x = 2 // error: x is already defined as value x
  • def の引数がパターンマッチしない。悲しい。
def foo((x, y): (Int, Int)) = x + y
  // error: identifier expected but '(' found.
  • 関数型プログラミングのイメージで書くと、どうしても f(x) を f x と書いてしまう。これはまあたぶんそのうち慣れる。
  • 引数に必ず型書くのが思った以上にだるい。これは慣れる気がしない。
  • 配列リテラルやリストリテラルでいちいち Array や List と打たなきゃいけない。xml リテラルなんぞ入れる前に必要なリテラルがあるだろう。
  • 1 つくらいよかったところも。placeholder としての _ はやはり便利。多重配列のマップでいちいち変数名考えなくていいのがうれしい。
# Ruby
matrices.map do |matrix|
  matrix.map do |column|
    column.map do |element|
      element + 1
    end
  end
end
# Scala
matrices map { _ map { _ map { element => element + 1 } } }

一応、いつぞやの dmap があれば Ruby でも簡単に書けないこともない。

matrices.dmap.dmap.dmap.map {|element| element + 1 }

  • 型推論が弱すぎて頼りない。new Array(n) だけ書くと Array[Nothing] と推論するので何かと推論失敗する。Array 使うなってことですか。
(0 until 3) map { n => n + 1 } toArray  // OK
(0 until 3).toArray map { n => n + 1 }  // error: missing parameter type
  • パターンマッチが型安全じゃない。asInstanceOf を使わなくても実行時に stuck することがある。要するに Obj.magic が自分で定義できちゃう。警告されるとは言え、かなりかっこ悪い。理由は (想像通りなら) JavaVM の仕様のせいなんだろうけど。
object Obj {
  def magic[A](v: AnyRef): A = {
    (List(v) match { case x:List[A] => x }).head
      // warning: non variable type-argument Int in type pattern is unchecked
      // since it is eliminated by erasure
  }
}

val a:Int = Obj.magic("foo")
  // String を Int に無理やりキャストしているがコンパイルは通る
  // (実行したら java.lang.ClassCastException)
  • とはいえ、静的型チェック自体はやはりとてもよい。Ruby と違って、コンパイルが通ったときに安心感 (達成感?) がある。

環境・その他

  • 便利メソッドがなさすぎて息苦しい。sort 、flatten 、each_cons 、 each_slice あたりは普通に欲しい。あと可変長配列ってないのかな。JavaVector を直接使えって?
  • やっぱりコンパイルと起動が遅い。ghc より遅い。ちょっと書いて実行して直して、を繰り返すスタイルだとかなりストレスがたまる。
  • Java 関係のツールが無条件にいらつく。生理的に受け付けない。
  • オブジェクトを作るとみるみる遅くなる。直接の原因は JavaVM なんだろうけど、Scala 側も実用向けの細かい最適化はまだまだ? リフレクションとかの関係でアグレッシブな最適化はできないんだっけ?
for (n <- 0 until 100000000) { // 18 秒
  val (x, y) = (1, 1)
  x
}
for (n <- 0 until 100000000) { // 7 秒
  val x = 1
  val y = 1
  x
}
for (n <- 0 until 100000000) { // 32 秒
  for (i <- 0 until 2) {
  }
}

val range = i <- 0 until 2
for (n <- 0 until 100000000) { // 7.5 秒
  for (range) {
  }
}
  • とはいえ、ライブラリが充実しているのと (起動以外の) 動作速度が速いのはいいこと。そういうのが必要になるプログラムはめったに書かないんだけど。

*1:Ruby でも宣言前 (代入前) の変数を参照することはできるけれど、そもそも定義前のメソッドが呼べないので、こういう問題はない。

*2:ちなみに Ruby 1.9 でも空行があるとないとで意味が変わるところがありました。さてどこでしょう。