私はScala Beggining Scala by DavidPollackを読んで試してみるのはちょっと初めてです。彼はファイルからすべての文字列をロードする単純な再帰関数を定義しています。
def allStrings(expr: => String): List[String] = expr match {
case null => Nil
case w => w :: allStrings(expr)
}
巨大な辞書ファイルを読み込もうとしたときにStackOverflow例外がスローされたことを除けば、エレガントで素晴らしいです。
私が理解している限り、Scalaは末尾再帰をサポートしているので、関数呼び出しがスタックをオーバーフローする可能性はなく、おそらくコンパイラはそれを認識しませんか?それで、グーグルした後、@ tailrecアノテーションを試しましたコンパイラを助けてください、しかしそれは言いました
error: could not optimize @tailrec annotated method: it contains a recursive call not in tail position
def allStrings(expr: => String): List[String] =
末尾再帰が間違っていることを理解していますか?このコードを修正するにはどうすればよいですか?
Scalaは、最後の呼び出しがメソッド自体の呼び出しである場合にのみ、これを最適化できます。
さて、最後の呼び出しはallStrings
ではなく、実際には::
(短所)メソッド。
この末尾再帰を作成する方法は、次のようなアキュムレータパラメータを追加することです。
def allStrings(expr: => String, acc: List[String] = Nil): List[String] =
expr match {
case null => acc
case w => allStrings(expr, w :: acc)
}
アキュムレータがAPIにリークするのを防ぐために、末尾再帰メソッドをネストされたメソッドとして定義できます。
def allStrings(expr: => String) = {
def iter(expr: => String, acc: List[String]): List[String] =
expr match {
case null => acc
case w => iter(expr, w :: acc)
}
iter(expr, Nil)
}
最終的な操作はallStrings
の再帰呼び出しではなく、::
メソッドの呼び出しであるため、末尾再帰ではありません(また、そうすることはできません)。
これを解決する最も安全な方法は、アキュムレータを使用するネストされたメソッドを使用することです。
def allStrings(expr: => String) = {
@tailrec
def inner(expr: => String, acc: List[String]): List[String] = expr match {
case null => acc
case w => inner(expr, w :: acc)
}
inner(expr, Nil)
}
この特定のケースでは、アキュムレータをallStrings
のパラメータに持ち上げ、デフォルト値Nil
を指定して、内部メソッドの必要性を回避することもできます。しかし、それが常に可能であるとは限りません。相互運用性が心配な場合は、Javaコードから適切に呼び出すことはできません。