1. 概要

このチュートリアルでは、Scalaでの値による呼び出しと名前による呼び出しの機能と評価戦略、およびそれらを実際に使用する方法について学習します。

2. 値による呼び出し

Scalaでは、関数の引数はデフォルトで値による呼び出しと見なされます。 関数testを定義しましょう。この関数は、値による呼び出しとして引数を取ります。

def test(a: Int) = a * a

一般に、パラメーター化された関数のアプリケーションは、演算子と同様に評価されます。 まず、左から右にすべての関数の引数を評価します。 次に、関数アプリケーションを関数の右側に置き換え、同時に、関数の仮パラメーターを実際の引数に置き換えます。 値による呼び出し戦略には、すべての関数の引数を1回だけ評価するという利点があります。

3. 名前で呼び出す

名前で呼ばれる引数を作成するには、その型の前に => (ロケット記号)を付けるだけです。

名前による呼び出しとして引数を取る関数testを定義しましょう。

def test(a: => Int) = a * a

名前による呼び出しの評価は値による呼び出しと似ていますが、対応する値が関数本体内で使用されるまで関数の引数が評価されないという利点があります。

次の場合に限り、両方の戦略が最終値に削減されます。

  • 縮小された式は純粋関数で構成されます
  • 両方の評価が終了します

4. 例

タイプIntの引数xを値による呼び出しおよび引数yとして取る関数addFirst、を定義しましょう。名前による呼び出しとしてIntと入力します:

def addFirst(x: Int, y: => Int) = x + x

ここで、値による呼び出しとしての関数addFirstの評価を理解しましょう。

assert(addFirst(3+5, 7) == 16)

この場合、値による呼び出し戦略は、最初に式 3 + 5 を評価し、次に値8を関数の本体に渡します。ここでxパラメーターは16の最終値を評価するためにそれ自体に追加されました。

それでは、名前による呼び出しと同じ例を見てみましょう。

assert(addFirst(7, 3+5) == 14)

この場合、名前による呼び出し戦略は、式 3 + 5 を無視します。これは、パラメーターyが関数の本体内で使用されていないためです。 したがって、パラメータ x がそれ自体に追加され、14の最終値が生成されます。

この例では、名前による呼び出し戦略は、値による呼び出し戦略よりも1ステップ高速です。

それでは、もう1つの例を見てみましょう。 無限再帰関数infinite()を定義しましょう:

def infinite(): Int = 1 + infinite()

この関数をaddFirstxパラメーターとして適用すると、値による呼び出し引数によってStackOverflowErrorが生成されます。

assertThrows[StackOverflowError] {
  addFirst(infinite(), 4)
}

上記の例では、関数本体が評価される前に infinite()が評価されるため、関数を呼び出すだけでStackOverflowErrorが生成されます。

ここで、 infinite()を名前による呼び出し引数として使用すると、次のようになります。

assert(addFirst(4, infinite()) == 8)

infinite()関数は、関数本体で評価されることはありません。 したがって、関数呼び出しは正常に実行されます。

5. 値による呼び出しまたは名前による呼び出し

名前による呼び出しを使用すると、引数が評価されない可能性があるため、より効率的な解決策のように見える場合があります。

でも、 値による呼び出しは、名前による呼び出しに伴う引数式の繰り返しの再計算を回避するため、名前による呼び出しよりも効率的であることがよくあります。 さらに、式がいつ評価されるかがわかっているため、他の副作用を回避できます。

6. 結論

このチュートリアルでは、Scalaでの値による呼び出しと名前による呼び出しを紹介しました。 それらの機能、使用法、および評価戦略のいくつかを見てきました。 いつものように、完全なソースコードはGitHubにあります。