kmizuの日記

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

Scala 2.8へ移行する際の注意点 〜Web Flavorの場合〜

Twitter見ていると、@keisuke_nさん

Web Flavorの話を日記に書くと、約一名反応してくれたw。Scala 2.8に対応してくれ人のこと。正直いうと実装コストがよくわからない><

http://twitter.com/keisuke_n/statuses/9523684805

のような事をポストされていたので、一体どれくらい移行コストがかかるのか、実際にWeb Flavor(0.3.0a2)がScala 2.8beta1でビルドを通るようにする作業を行ってみた。移行作業(=ビルドが通るようになるまで)は3時間程度で終わったが、いくつかハマりそうなポイントがあったので、以下に述べておく。

内側のパッケージから外側のパッケージのクラスを暗黙に利用している場合に注意

Scala 2.7では、内側のパッケージから外側のパッケージのクラスを参照する場合、特にimportすることなく、暗黙に参照することが可能だった。たとえば、次のように、foo.barパッケージのクラスからfooパッケージのFooをimportすることなしに、暗黙に参照することができていた。

package foo.bar
class Bar {
  val foo = new Foo
}
package foo
class Foo

理由についてはちゃんと調べていないが、2.8ではどうやらこの挙動が無くなったようで、上記のようなコードはコンパイルを通らなくなっている。

Seq型に注意

Scala 2.8では、scala.collection.mutable.Seqとscala.collection.immutable.Seqが追加されているため、

import scala.collection.mutable._

import scala.collection.immutable._

としているファイル中でSeq型を使っている場合、意図しない挙動になる恐れがある。既存のソフトウェアを移行する際は、

import scala.collection.mutable.{Seq => _, _}

のようにして、mutable/immutableなSeqをimportしないように明示的に指定することで解決できる。今回は、あちこちでこの問題によるコンパイルエラーが発生していたため、機械的に
import scala.collection.mutable._をimport scala.collection.mutable.{Seq => _, _}に、import scala.collection.immutable._をimport scala.collection.immutable.{Seq => _, _}に変換するスクリプトを書いた。

配列とSeqの組み合わせに注意

Scala 2.8では配列(Arrayクラス)の扱いが変わり、scala.Seqを(直接/間接に)継承しないようになった。その代りに、Arrayをscala.Seqに変換するためのimplicit conversionが追加されているが、型推論を利用している場合、これが思わぬ挙動を招く可能性がある。たとえば、

// anArrayはArray[T], anSeqはSeq[T]とする
val hoge = if(condition) anArray else anSeq

のような形の式があった場合、2.7までのScalaではhogeをSeq[T]のサブタイプとして推論してくれたが、2.8では、ArrayはSeqのサブタイプでないため、単なるscala.ScalaObjectとして推論されてしまう。この場合、以下のようにhogeを明示的にSeq[T]として宣言することで解決できる。

// anArrayはArray[T], anSeqはSeq[T]とする
val hoge: Seq[T] = if(condition) anArray else anSeq
コレクションクラスの継承に注意

Scala 2.8では全面的にコレクションライブラリが書きなおされ、それに伴って、

  • メソッドの返り値がUnitだったのがコレクション自身を返すように変更されたり
  • 新しくabstractなメソッドが追加されたり
  • abstractだったメソッドにデフォルトの実装が追加されていたり

などしている。コレクションを単に利用しているだけの場合は、これらの変更の影響を受ける場合は比較的少ないと思われるが、Scalaのコレクションクラスを継承したコレクションクラスを自作している場合は要注意だ。Web Flavorでは、Mapを継承したコレクションが使われていたため、メソッドにoverrideを追加したり、abstractなメソッドの実装を追加するなどの作業が必要だった。これに関しては一般的な対策は無くて、とにかく2.8のscalacでコンパイルして、出たエラーを見ながら随時修正するしかない。2.8のコレクションライブラリは2.7に比べてよりジェネリックになっているため、慣れないとこの作業はやや面倒かもしれない。

implicit conversionによるメソッドオーバーローディングに注意

Scala 2.7までは、次のように、既に存在するメソッドに対して、引数の個数が違う同名のメソッドをimplicit conversionによって追加することができていた。

class RichListString(xs: List[String]) {
  def find(x: String) = xs.find{_ == x} //(1) こっちはOK
  def find(x: String, dummy: String) = xs.find{_ == x} //(2) これがダメ
}
implicit def enrich(xs: List[String]): RichListString = new RichListString(xs)
List("A", "B", "C").find("A", "c") //(3)

Scala 2.8では、(2)のメソッドが定義されている場合に、(3)のコードを書くとコンパイルエラーになる。(1)のように、引数の数が同じで型が違うだけの場合は、今までと同様にOKのようだが、implicit conversionを使って、既に定義されているメソッドと同名のメソッドを追加するのは避けた方が良いかもしれない。

他にもまだ隠された問題があるかもしれないが、とりあえずWeb Flavorを2.8対応させる上で遭遇した問題は一通り書けたと思う。他にもこんな点に注意した方がいいよという点があれば報告してもらえるとありがたいかも。