2017/08/11

 Scalaで、再代入時にOptionを使おうとしたらtype-mismatch


Scalaに入門して少し経ちましたが、コードを書いていると早速、型推論でのエラーが発生して少し悩みました。 Option型で再代入可能な変数(varで宣言した変数)に再代入しようとした箇所に、エラーが発生。

ざっくりと書くと、以下のような感じでエラーが発生しました。。。

scala> var x = None
var x = None
x: None.type = None

scala> x = Some(10)
x = Some(10)
<console>:8: error: type mismatch;
 found   : Some[Int]
 required: None.type
       x = Some(10)
               ^

実際のコードは上記と異なり、replで実行したコードではなく、 メソッド内でvarの変数を定義し、そこに再代入を行う以下のような処理ですが、意味的には上記と同じような状況です。

var x = None

// 様々な処理

x = Some(10)

先に変数xにNoneを代入しておいて、あとから、Some(〜)を再代入しようとした処理を書くという流れです。

Noneを初期値として入れておいた変数xに後からSome(〜)を代入する処理を書こうとすれば、当然、コンパイラの側でOption型を 推論してくれるものと思っていたのですが、甘かった。

replだとなんとなく分かりますが、初期値を代入した時点では、var xの型はNone.typeとなってしまい、Option型ではありません。 問題は、コンパイラが後から変数xはNone.typeではなく、Option[Int]型であるといった推論をしてくれないことにあります。

先にNoneを代入した後に、Someを代入する場合は、変数がOption型であることを先に明示しておく必要があります。 型注釈を以下のようにつけると、変数がOption型であることを認識して、Option型の再代入が可能になります。

scala> var x = None:Option[Int]
var x = None:Option[Int]
x: Option[Int] = None

scala> x = Some(10)
x = Some(10)
x: Option[Int] = Some(10)

そもそもなんでこんな言語仕様なのか、なぜ、このような挙動をするのか、詳しく調べた訳ではないですが、 以下にそれらしい内容が載っていました。

Scalaの型推論について - Togetterまとめ

  • Scalaの型推論では、基本的に前方から順に型推論し、後方で想定外の型であると型エラーが発生する仕組みになっている。
  • 強力な型推論を行った場合、型エラーが分かりづらくなるため、上記のような弱い推論方法を採用しているのかも知れない。

とのこと。

時代的にみてもScalaは、SystemFが登場した後に設計された(2001年ごろ〜)言語で、 すでにHaskellやOCamlなど強力な型推論システムを持つ実用的な言語が既に登場しています。 にも関わらず、このような設計にっているのは、型エラーの複雑さ(わかりづらさ)を回避したい狙いがあった、ということのようです。

前方から型推論していくと考えると、型エラーの分かりづらさはかなり軽減されそうです。 上記のエラーは、その辺りのトレードオフを考えてた上での、型エラーとという事のなのでしょう。

  型推論 デバッグ Scala