Scalaにまつわるよくある誤解~returnの省略~

特にシリーズ化するつもりはないのですが、Scalaに関する解説で非常によく見られる、とある「誤解」について取り上げたいと思います。知っている人には今更感がある話です。

Scalaのメソッド定義に関する解説で、

def add(x: Int, y: Int): Int = {
  x + y
}

と書くことができることに関する説明で、「returnが省略できる」(あるいはそれに類する表現)を非常によく見ます。便宜上の説明としてはそれほど悪くないと思うのですが、Scalaにおける、式と return の役割を考えたとき、あまり適切ではありません。

結論から書くと、Scalaにおいて、「 return がない」のがデフォルトであって、 return は、メソッドの途中で値を返すための特別な構文である、というのが本来の姿だということです。言い回しを変えただけでは?と思われる方も居るかもしれませんので、以下で詳しく説明します。

まず、Scalaのメソッド定義の右辺に書けるものというのは、全て「式」です。ここで、「式」が意味するのは、「評価」することによって特定の値が返ってくる、ということです。「評価」という用語は言語処理系に慣れていないとわかりにくいかもしれないので、「計算する」と言い換えても良いです。

Scalaでは、(宣言を除いて)、ほとんどの、構文要素が「式」です。ifwhileformatchreturnthrowもメソッド呼び出しも中置演算(っぽいもの)も全て式です。また、式中にあらわれるブロック( {} )も式です。

たとえば、

def display[A](a: A): A = {
  println(a)
  a
}

において、=以降は式です。このメソッドが呼び出されたとき、次のようにして評価が行われます。

  1. ブロック式 { println(a); a} を評価しようとする
  2. ブロック式の最初の要素 println(a) 評価する。評価結果は捨てられる
  3. ブロック式の最後の要素 a を評価する。 a の値が評価結果になる

ブロック式自体が、ブロック式の要素を順番に評価して、最後の要素の評価結果をブロック式の評価結果とする、という意味を持っているため、また、メソッド呼び出しの評価結果は、 = の右辺の式の評価結果であることが決まっているので、returnを省略 というより、通常の評価が進む限りにおいて、returnという特別なものを必要としないのです。

ただし、評価が進む過程で、ある条件が満たされたとき、評価を打ち切って、メソッド呼び出しの評価結果を確定したいことがあります。そのときに使われるのが return 式です。return式は次のようにして使うことができます(この例ではreturnがなくても同じコードを書けますが)。

def fact(n: Int): Int = {
  if(n < 2) return 1
  n * fact(n - 1)
}

このとき、ブロック式の構成要素は

  1. if(n < 2) return 1
    • return 1
  2. n * fact(n - 1)
    • fact(n - 1)

というふうになります。return 1が評価されるときは、 if 式の条件 n < 2true になるときで、そのとき、return 1という式が評価され、メソッドから脱出すると同時に、呼び出し元に 1 を返します。

このように、Scalaにおける return というのは、省略可能というより、言語のコアに「付け足された」ものだと考えるのが自然です。また、 return があるメソッドにおいて、返り値の型推論が効かなくなるのも、Scalaにおける return の立ち位置を示しているといえます。

結論を再度書くと、 return が省略可能、というのは、Scalaの通常の評価規則を考えたときに違和感がある表現で、 return による途中脱出が可能、というのが自然です。

もちろん、構文要素の多くが式ではなく文であるような言語からScalaを学ぶときに、 return を省略可能、という説明があってもいいと思いますが、そのような考えのままでいると「Scalaらしいコード」を書くときには、問題になることもありえます。