匿名関数で明示的なreturnステートメント(return
キーワードを使用するステートメント)が、匿名関数自体からだけでなく、囲んでいる名前付き関数から返されるのはなぜですか?
例えば。次のプログラムでは、タイプエラーが発生します。
def foo: String = {
((x: Integer) => return x)
"foo"
}
return
キーワードを避けることが推奨されていることは知っていますが、明示的および暗黙的なreturnステートメントが無名関数で異なるセマンティクスを持っている理由に興味があります。
次の例では、m
の実行が終了した後、returnステートメントが「存続」し、プログラムによって実行時例外が発生します。無名関数が囲んでいる関数から戻らなかった場合、そのコードをコンパイルすることはできません。
def main(args: Array[String]) {
m(3)
}
def m: (Integer => Unit) =
(x: Integer) => return (y: Integer) => 2
正式に言えば、returnは、常に最も近い囲んでいる名前付きメソッドから戻ることとして定義されます。
Return式returneは、それを囲む名前付きメソッドまたは関数の本体内で発生する必要があります。ソースプログラムの最も内側を囲む名前付きメソッドまたは関数fは、明示的に宣言された結果タイプを持っている必要があり、eのタイプはそれに準拠している必要があります。戻り式は式eを評価し、fの結果としてその値を返します。 return式に続くステートメントまたは式の評価は省略されます。
したがって、ラムダでは異なるセマンティクスはありません。しわは、通常のメソッドとは異なり、ラムダから作成されたクロージャは、それを囲むメソッドへの呼び出しをエスケープでき、そのようなクロージャに戻りがある場合は例外を取得できることです。
戻り式自体が無名関数の一部である場合、戻り式が実行される前に、fの囲んでいるインスタンスがすでに戻っている可能性があります。その場合、スローされたscala.runtime.NonLocalReturnExceptionはキャッチされず、コールスタックに伝播されます。
さて、「なぜ」は。あまり理由がないのは美的です。ラムダは式であり、式とそのすべての部分式がネスト構造に関係なく同じ意味を持つ場合は便利です。ニールガフターはそれについて http://gafter.blogspot.com/2006/08/tennents-correspondence-principle-and.html で話します
ただし、それが存在する主な理由は、命令型プログラミングで一般的に使用される制御フローの形式を簡単にシミュレートできる一方で、物事を高階関数に抽象化できることです。おもちゃの例として、Javaのforeach構文(for (x : xs) { yada; }
)を使用すると、ループ内で戻ることができます。 Scalaにはforeachの言語レベルがありません。代わりに、foreachをライブラリに配置します(foreachに脱糖するだけなので、yieldなしで「forexpression」はカウントされません)。つまり、Java foreachを取得して、Scala foreachに直接変換できます。
ところで、Ruby、Smalltalk、およびCommon LISP(私の頭のてっぺんから)も同様の「非ローカル」リターンを持っています。
return
キーワードは(クラス)メソッド用に予約されており、関数では使用できません。あなたはそれを簡単にテストすることができます:
object Foo {
val bar = (i: Int) => return i + i
}
これは与える
<console>:42: error: return outside method definition
object Foo { val bar = (i: Int) => return i + i }
^
関数のapply
メソッドは構文的にメソッドを呼び出すように動作し、いわゆるeta-expansionによりメソッドを関数の引数として渡すことができるため、ほとんどの場合、メソッドと関数を同じものとして扱うことができます。
この場合、それは違いを生みます。メソッドとして定義する場合、それは合法です。
object Foo {
def bar(i: Int): Int = return i + i
}
要約すると、条件付き(早期)リターンを許可するメソッドではreturn
のみを使用する必要があります。 メソッドと関数の説明については この投稿 を参照してください。