名前渡し Call-by-name にて引数のコード実行タイミングを制御する

Java で以下のコードにてgreeting()を呼び出すと、

public String hello() {
    return "HELLO";
}

public void print(String s) {
    System.out.println(s);
}

public void greeting() {
    print(hello());
}

最初に hello() メソッドが呼ばれ、その結果の HELLO という文字列が print() の引数として渡されます。


同じようにScalaにて。

def hello() = "HELLO"

def print(s : String) = { println(s) }

def greeting() = print(hello())

greeting() を呼び出すと、まぁ、同じように HELLO を出力します。


ここで Scala では print を以下のように書くことができます。

def print(s : => String) = { println(s) }

この場合も同じように HELLO が出力されますが、なにが違うのでしょうか。


コードを以下のように変更して greeting() を呼び出すと、

def hello() = {
  println("in hello()")
  "HELLO"
}

def print(s : => String) = { 
  println("print() start")
  println(s)
  println("print() end")
}

def greeting() = print(hello())

以下のような結果が得られます。

print() start
in hello()
HELLO
print() end

最初に print() が呼び出され、その後で hello() が呼び出されています。
なにが起きたかというと、print() 関数に、hello() という関数が s という名前のコードブロックとして渡されています。print() 関数は、何かわからないけど結果が String となるコードブロックの s を受け取って、自身の中でコードブロックを実行しているのです。


以下のように、変数と型の間に => を記述すると Call-by-name(名前渡し) の宣言となり、引数のメソッドの実行を遅らせることができます。

def print(s : => String) = ・・


ログ出力時などで、

if(log.isDebugEnable()) {
    log.debug(hoge() + "hoge");
}

などと書かなくてすむようになったりします。