flagir を作るなどの過程で感じた Scala への不満を列挙。こんな記事を (日本語で) 公開しても誰も得しないし誰も読まないだろうから、やめようかなとも思ったんだけど。ぼくの勘違いなら指摘してもらえるかもしれないし、記録という意味もあるのでやっぱり公開。
文法
- 束縛前の val 変数を参照できてしまった。かなりはまった。これは Scala のバグと言っていいと思う *1 。どう直すかというとなかなか難しいけれど。
object T {
val foo = setup_foo
val bar = 1
def setup_foo = {
bar
}
def main(args: Array[String]) {
println(foo)
}
}
- 空行があるかないかで意味が変わって、はまった。ちょっとスコープを作ろうと思っただけなのに *2 。
foo = 0
{
val bar = 1
}
foo = 0
{
val bar = 1
}
val x = 1, y = 1
val (x, y) = (1, 1)
val x, y, z = 1
val x@(y@z) = 1
- val で同じ名前の変数を束縛できない (隠せない) 。ocaml の let はこれができていいですよね。
val x = 1
val x = 2
def foo((x, y): (Int, Int)) = x + y
- 関数型プログラミングのイメージで書くと、どうしても f(x) を f x と書いてしまう。これはまあたぶんそのうち慣れる。
- 引数に必ず型書くのが思った以上にだるい。これは慣れる気がしない。
- 配列リテラルやリストリテラルでいちいち Array や List と打たなきゃいけない。xml リテラルなんぞ入れる前に必要なリテラルがあるだろう。
- 1 つくらいよかったところも。placeholder としての _ はやはり便利。多重配列のマップでいちいち変数名考えなくていいのがうれしい。
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
(0 until 3).toArray map { n => n + 1 }
- パターンマッチが型安全じゃない。asInstanceOf を使わなくても実行時に stuck することがある。要するに Obj.magic が自分で定義できちゃう。警告されるとは言え、かなりかっこ悪い。理由は (想像通りなら) JavaVM の仕様のせいなんだろうけど。
object Obj {
def magic[A](v: AnyRef): A = {
(List(v) match { case x:List[A] => x }).head
}
}
val a:Int = Obj.magic("foo")
- とはいえ、静的型チェック自体はやはりとてもよい。Ruby と違って、コンパイルが通ったときに安心感 (達成感?) がある。
環境・その他
- 便利メソッドがなさすぎて息苦しい。sort 、flatten 、each_cons 、 each_slice あたりは普通に欲しい。あと可変長配列ってないのかな。Java の Vector を直接使えって?
- やっぱりコンパイルと起動が遅い。ghc より遅い。ちょっと書いて実行して直して、を繰り返すスタイルだとかなりストレスがたまる。
- Java 関係のツールが無条件にいらつく。生理的に受け付けない。
- オブジェクトを作るとみるみる遅くなる。直接の原因は JavaVM なんだろうけど、Scala 側も実用向けの細かい最適化はまだまだ? リフレクションとかの関係でアグレッシブな最適化はできないんだっけ?
for (n <- 0 until 100000000) {
val (x, y) = (1, 1)
x
}
for (n <- 0 until 100000000) {
val x = 1
val y = 1
x
}
for (n <- 0 until 100000000) {
for (i <- 0 until 2) {
}
}
val range = i <- 0 until 2
for (n <- 0 until 100000000) {
for (range) {
}
}
- とはいえ、ライブラリが充実しているのと (起動以外の) 動作速度が速いのはいいこと。そういうのが必要になるプログラムはめったに書かないんだけど。