戻り値が異なる場合の型推論

Ken published on
8 min, 1524 words

Categories: Programming

Tags: Scala

今日はScala(2.8.0.RC2)の型推論ではまった。 このコードをコンパイルするとエラーが発生する。

import scala.collection.mutable. { Map, ListBuffer }

object TypeError {
  val list:List[Option[String]] = List(Some("a"), Some("b"))
  
  var result:ListBuffer[String] = ListBuffer()
  var map:Map[String, ListBuffer[String]] = Map()
  list.foreach(v =>
    v match {
      case Some(s) => result += s
      case _ => map += ("" -> ListBuffer(""))
    })
}

意味不明だったため、Twitterでつぶやいてみたら、何人かの方々が一緒に考えて下さるという、とてもありがたいことがありました。 一緒に考えてくださった方々、ありがとうございました。

以下、Twitter上で教えていただいたことを元に簡単にまとめます。

scalac -Xprint:typer typeerror.scala とコンパイルすると次のようなエラーメッセージが表示される。

[[syntax trees at end of typer]]// Scala source: typeerror.scala
package <empty> {
  import scala.collection.mutable.{Map, ListBuffer};
  final object TypeError extends java.lang.Object with ScalaObject {
    def this(): object TypeError = {
      TypeError.super.this();
      ()
    };
    private[this] val list: List[Option[String]] = immutable.this.List.apply[Some[java.lang.String]](scala.Some.apply[java.lang.String]("a"), scala.Some.apply[java.lang.String]("b"));
    <stable> <accessor> def list: List[Option[String]] = TypeError.this.list;
    private[this] var result: scala.collection.mutable.ListBuffer[String] = scala.collection.mutable.ListBuffer.apply[String]();
    <accessor> def result: scala.collection.mutable.ListBuffer[String] = TypeError.this.result;
    <accessor> def result_=(x$1: scala.collection.mutable.ListBuffer[String]): Unit = TypeError.this.result = x$1;
    private[this] var map: scala.collection.mutable.Map[String,scala.collection.mutable.ListBuffer[String]] = scala.collection.mutable.Map.apply[String, scala.collection.mutable.ListBuffer[String]]();
    <accessor> def map: scala.collection.mutable.Map[String,scala.collection.mutable.ListBuffer[String]] = TypeError.this.map;
    <accessor> def map_=(x$1: scala.collection.mutable.Map[String,scala.collection.mutable.ListBuffer[String]]): Unit = TypeError.this.map = x$1;
    TypeError.this.list.foreach[scala.collection.mutable.Iterable[_ >: String with (String, scala.collection.mutable.ListBuffer[String]) <: java.lang.Object] with PartialFunction[Int with String,java.lang.Object] with scala.collection.mutable.Builder[String with (String, scala.collection.mutable.ListBuffer[String]),Iterable[java.lang.Object] with PartialFunction[Int with String,java.lang.Object]{def companion: scala.collection.generic.GenericCompanion[Iterable[Any]{def companion: scala.collection.generic.GenericCompanion[Iterable[Any]]}]}] with scala.collection.generic.Shrinkable[String]{protected def clone(): scala.collection.mutable.Iterable[_ >: String with (String, scala.collection.mutable.ListBuffer[String]) <: java.lang.Object] with PartialFunction[Int with String,java.lang.Object] with scala.collection.mutable.Builder[String with (String, scala.collection.mutable.ListBuffer[String]),Iterable[java.lang.Object] with PartialFunction[Int with String,java.lang.Object]{def companion: scala.collection.generic.GenericCompanion[Iterable[Any]]}] with scala.collection.generic.Shrinkable[String]{protected def clone(): scala.collection.mutable.Iterable[_ >: String with (String, scala.collection.mutable.ListBuffer[String]) <: java.lang.Object] with PartialFunction[Int with String,java.lang.Object] with scala.collection.mutable.Builder[String with (String, scala.collection.mutable.ListBuffer[String]),Iterable[java.lang.Object] with PartialFunction[Int with String,java.lang.Object]{def companion: scala.collection.generic.GenericCompanion[Iterable[Any]]}] with scala.collection.generic.Shrinkable[String]{protected def clone(): scala.collection.mutable.Iterable[_ >: String with (String, scala.collection.mutable.ListBuffer[String]) <: java.lang.Object] with PartialFunction[Int with String,java.lang.Object] with scala.collection.mutable.Builder[String with (String, scala.collection.mutable.ListBuffer[String]),Iterable[java.lang.Object] with PartialFunction[Int with String,java.lang.Object]] with scala.collection.generic.Shrinkable[String]{protected def clone(): scala.collection.mutable.Iterable[_ >: String with (String, scala.collection.mutable.ListBuffer[String]) <: java.lang.Object] with PartialFunction[Int with String,java.lang.Object] with scala.collection.mutable.Builder[String with (String, scala.collection.mutable.ListBuffer[String]),Iterable[Any] with PartialFunction[Nothing,Any]] with scala.collection.generic.Shrinkable[String]{protected def clone(): Iterable[Any] with PartialFunction[Nothing,Any] with scala.collection.mutable.Builder[Nothing,Any] with scala.collection.generic.Shrinkable[String] with Mutable}; def iterator: Iterator[java.lang.Object]}; def iterator: Iterator[java.lang.Object]}; def iterator: Iterator[java.lang.Object]}; def iterator: Iterator[java.lang.Object]}](((v: Option[String]) => v match {
      case (x: String)Some[String]((s @ _)) => TypeError.this.result.+=(s)
      case _ => TypeError.this.map.+=(scala.this.Predef.any2ArrowAssoc[java.lang.String]("").->[scala.collection.mutable.ListBuffer[java.lang.String]](scala.collection.mutable.ListBuffer.apply[java.lang.String]("")))
    }))
  }
}

typeerror.scala:8: error: kinds of the type arguments (Iterable[Any]{def companion: scala.collection.generic.GenericCompanion[Iterable[Any]]}) do not conform to the expected kinds of the type parameters (type CC) in class GenericCompanion.
Iterable[Any]{def companion: scala.collection.generic.GenericCompanion[Iterable[Any]]}'s type parameters do not match type CC's expected parameters: <refinement> has no type parameters, but type CC has one
  list.foreach(v =>
       ^
one error found

型を推論しているときに、推論しきれなかったっぽいです。

次のコメントを読んでもらった方がわかりやすい。

@teaplanet 通常、条件分岐の各節ごとに異なる型を返す場合(StringとIntとか)、Scalaの型チェッカが頑張ってその共通のスーパータイプを見つけようとするのですが、今回の場合、うまくスーパータイプを見つけられないレアケースにはまったのだと思います。 #scalaless than a minute ago via Twit for Windows

対応策としては次のようなことを教えていただきました。

@teaplanet とりあえず、foreachの型パラメータにAnyなどと書いて、hoge.foreach[Any](attr => ...)として、型チェッカが型推論を頑張れなくて良いようにすれば解決できるかと思います。 #scalaless than a minute ago via Twit for Windows

なるほど、foreach[Any](...とすることによって型推論をしなくて済むようにするらしい。 今回の現象は不具合なのか、こういうエラーが発生するケースなのかは不明。 こうやって原因がわかると、値を返す必要がないのにmatchは変かもなぁ〜と、ちょっと思ったり。

ちなみに、foreach内のmatch式を関数にするとエラーは発生しない。

import scala.collection.mutable. { Map, ListBuffer }

object TypeError {
  val list:List[Option[String]] = List(Some("a"), Some("b"))
  
  var result:ListBuffer[String] = ListBuffer()
  var map:Map[String, ListBuffer[String]] = Map()
  list.foreach(v => func(v))
  
  // match部分を関数にした。
  def func(v:Option[String]):Unit = {
    v match {
      case Some(s) => result += s
      case _ => map += ("" -> ListBuffer(""))
    }
  }
}

これはfunc関数の戻り値がUnitということで型推論をする必要がないということらしい。 そもそも、型を返す必要がないんだからこのように関数にしてしまう方がいいのかもしれない。

Scala、奥が深いです・・・。