Scalaの名前渡しパラメータが参照する度に評価されるってどこに明記されてるか調べてみた

恥ずかしい話なんですが、Scala の名前渡しパラメータ (by-name parameter) がメソッドの中でパラメータを参照する度に評価されてしまうことを知りませんでした。つまりこういうことですね。

def f: Int = {
  println("hello")
  0
}

def g(v: => Int) = {
  v + v
}

g(f)

// => 実行結果
// hello
// hello

幸いにして、今まで名前渡ししたパラメータをメソッドの中で複数回参照するような実装はしたことがなかったのですが、それにしても今まで何で気付けなかったのかなと思い、公式ドキュメントや書籍でどのように記載されているのかを調べてみました。

Scala Documentation

まずは、Scala Documentation ですが、名前渡しパラメータだとこのあたりですね。

このページでは次のようなサンプルコードが記載されています。

def whileLoop(condition: => Boolean)(body: => Unit): Unit =
  if (condition) {
    body
    whileLoop(condition)(body)
  }

var i = 2

whileLoop (i > 0) {
  println(i)
  i -= 1
}  // prints 2 1

確かにこれを見ると、名前渡しパラメータが毎回評価されることを前提とした実装になっています。また、

By-name parameters are only evaluated when used.

と書いてありますが、「使うときだけ評価される」というのが毎回という意味も含んでいたのか... まあちゃんと読み解けば気付けましたね...

Functional Programming Principles in Scala

次に CourseraFunctional Programming Principles in Scala。これの Lecture 1.3 - Evaluation Strategies and Termination の中で、Martin Odersky さんは以下のように仰ってます。

In Scala, we normally use call by value. You might ask, well, given the advantages of call by name that it terminates more often, why call by value? Well, it turns out that, for expressions in practice, call by value is often exponentially more efficient than call by name because it avoids this repeated recomputation of argument expressions that call by name entails. The other argument for call by value is that it plays much nicer with, imperative effects and side effects because you tend to know much better when expressions will be evaluated.Since Scala is also an, an imperative side, call by value is the standard choice. Except that, sometimes you really want to force call by name, and Scala lets you do that also. You do that by adding a, an arrow in front of the parameter type.

以下は、Coursera の和訳

Scalaでは通常は値渡しを使います。名前渡しに(引数に渡した無限ループが)停止しやすい利点があるのになぜ値渡しを使うのかと訝しむかもしれません。現実の式では値渡しの方が名前渡しよりも指数的に効率的であることが分かっています。なぜなら値渡しは名前渡しが必要とする引数の式の再計算が不要だからです。値渡しに有利になる別の主張はそれが命令的な作用や副作用をずっと上手く扱うことができるというものです。なぜならいつ式が評価されるのかがずっとわかりやすい傾向があるからです。Scalaもまた命令的な側面がありますので、値渡しが標準的な選択となります

めっちゃ言ってる...

値渡しは名前渡しが必要とする引数の式の再計算が不要だからです。

これも私が聞き逃してただけですねorz

Scalaスケーラブルプログラミング第3版

次にコップ本ですが、名前渡しパラメータについては「9.5 名前渡しパラメータ」に記載があって、ifwhile に近い制御構造を作る際にパラメータの predicate: () => Boolean から冗長な () を省略して predicate: => Boolean にしたのを名前渡しパラメータとして紹介されています。

確かに元の形が () => BooleanFunction0 と同じと考えれば毎回評価されるのは納得がいきます。 実際にこちらで紹介されているように名前渡しパラメータは Function0 に置き換わるようですしね。

まあそれは良いとして、本には「毎回評価されるよ」と明記されているわけじゃないので、気付けた人だけが知っているみたいなのはちょっと嫌だなと思ってました。

ところが、コップ本の最後の方を何となく見ていたらちゃんとそこに書いてあることに後から気付きました。コップ本の最後の方の「用語集」にはこんな感じで書かれています。

名前渡しパラメーター(by-name parameter)
たとえば(x: => Int)のように型の前に=>が付けられているパラメータ(仮引数)。名前渡しパラメーターに対応する実引数は、メソッドが呼び出される前ではなく、メソッド内でパラメータが名前で参照されるたびに評価される。名前渡しでないパラメータは、値渡しである。

これも私が見落としてただけでしたか...

おわりに

最近会社の若者たちに Scala を教えてるのですが、今回の件はその中で気付くことができました。なので、若者たちに感謝!

追記(2018/08/22)

Scala関数型デザイン&プログラミング ―Scalazコントリビューターによる関数型徹底ガイド」を確認したら「5.1 正格関数と非正格関数」で、名前渡ししたパラメータはキャッシュされないので毎回評価される。毎回評価させたくないのであれば明示的にlazyキーワードを使って値をキャッシュさせると書いてありました!もう一度全部読み返さないと!