[Scala]無限ループより再帰を使うのがScalaらしいコードらしい
Scalaで無限ループを書くとエラー
投資商品等で、スタートの金額、利率、目標金額を与えて、何年後に到達するかを計算する関数をループで書きたいとします
※本当は対数計算のほうが筋が良いと思います
Javaの感覚で以下のように書くと、コンパイル時にエラーが出ます
def calc(start: Int, rate: Double, goal: Int): Int = { var years = 0 var current: Double = start while(true) { if (current >= goal) { return years } years += 1 current = current * rate } } > Found: Unit Required: Int while(true) {
これは、Int
を返さないといけないところ、scalaは最後の文を自動的に戻り値として認識するゆえに、最後の文であるwhile
が値を返さないためUnit
と認識されてしまっているために起こるエラーのようです
無限ループを成立させるには
到達することはないものの、最後に例外を投げてあげると成立します。
def calc(start: Int, rate: Double, goal: Int): Int = { var years = 0 var current: Double = start while(true) { if (current >= goal) { return years } years += 1 current = current * rate } // この文を追加するとコンパイルが通る throw new RuntimeException("ここには来ない"); }
積極的に再帰を使う
本題なのですが、他言語を普段書かれている方だと、再帰はStackOverflowを起こす可能性があることから、普段は再帰よりもループを選ばれると思います
ところが、Scalaでは、 末尾再帰の関数であれば、コンパイル時にループに書き換え、最適化をしてくれます
末尾再帰とは、 自身のシンプルな再帰呼び出しが、最後のステップになっているパターン のことです
例えば、これは末尾再帰です
def foo(): Int = {
foo()
}
これは、末尾再帰ではありません
自身の呼び出しに+ 1
と、余計なことをしてしまっているためです
def bar(): Int = { bar() + 1 }
そして、@tailrec
アノテーションを付与することで、末尾再帰ではない、最適化がかからない関数に関しては、エラーを発生させることが可能です
まとめると、今回のケースだと、以下のように書き換えが可能です
import scala.annotation.tailrec @tailrec def calc(current: Double, rate: Double, goal: Int, years: Int): Int = { if (current >= goal) { years } else { calc(current * rate, rate, goal, years + 1) } }
参考
Scala while(true) type mismatch? Infinite loop in scala? - Stack Overflow