Использование apply ("()") для функции, переданной в параметре by-name: оценка не является принудительной?

У меня есть функция:

def nanoTime() = {
    println("Getting nano time...")
    System.nanoTime // returns nanoTime
}

и другая функция, которая принимает функцию

def printTime(time: => Long) = {  // indicates a by-name parameter
    println(">> delayed()")
    println("Param: " + time)
    time // returns time
}

Теперь вот в чем дело. Когда я делаю:

scala> printTime(nanoTime())
>> delayed()
Getting nano time...
Param: 546632085606127
Getting nano time...
res11: Long = 546632086131624

Я получаю те же результаты, когда я делаю:

scala> printTime(nanoTime)
>> delayed()
Getting nano time...
Param: 546622367510997
Getting nano time...
res10: Long = 546622368149903

Там нет никакой разницы между:

scala> printTime(nanoTime())

а также

scala> printTime(nanoTime)

Таким образом, нет разницы между передачей имени функции и передачей имени функции, за которым следует (). Это всегда так, или что такого особенного в этой игре?

Благодарю.

1 ответ

В Scala есть концепция списков параметров, в которых метод может занимать более одного. Однако это также, для удобства, позволяет опускать терминальные пустые списки параметров. Так

f
f()
f()()

может быть все одно и то же - вы не знаете, пока вы не посмотрите на f, Задание параметра по имени заключается в задержке выполнения блока кода. Теперь, формально, если мы имеем

def f0: String = "salmon"
def f1(): String = "herring"
def f2()(): String = "halibut"

тогда вы ожидаете f0 соответствовать параметру по имени, а другим - нет, если он преобразован в функцию. В частности, вы ожидаете

f0   <==>   => String
f1   <==>   () => String
f2   <==>   () => () => String

когда преобразован. Давайте посмотрим, что на самом деле происходит при запросе через f _:

scala> f0 _
res4: () => String = <function0>

scala> f1 _
res5: () => String = <function0>

scala> f2 _
res6: () => () => String = <function0>

Ну что ж; f0 фактически преобразуется в функцию с одним пустым блоком параметров вместо нуля (именно так выглядит параметр по имени). Так что получается, что ваш параметр по имени вообще не преобразует ваш метод в функцию - сигнатуры типов не будут совпадать!

Так что вместо этого, это так:

// I need a code block that returns a long
nanoTime             // Wait, there is no nanoTime exactly
nanoTime()           // Aha, that works!  Must have meant that
: => { nanoTime() }  // There, nicely packaged.

Причина, по которой вы не видите разницы в том, что для того, чтобы вернуть Long параметр by-name уже заполняет отсутствующие (), но затем завернуть все это в блок кода для выполнения позже.

(Обратите внимание, что параметры по имени на самом деле просто Function0 под капотом - то есть x: => A действительно x: () => A - а "блоки с нулевым параметром" - это просто выдумка компилятора. На самом деле все блоки параметров являются вымыслом компилятора - JVM знает только об одном списке параметров. И именно эта фантастика без блоков, в сочетании с фантастикой "кто заботится о пустых пареннах", приводит к наблюдаемому поведению.)

Если вы запрашиваете функцию из пустого блока параметров, то все работает так:

def printF(f: () => String) = println(f())

scala> printF(f0)
<console>:23: error: type mismatch;
 found   : String
 required: () => String
              printF(f0)
                     ^

scala> printF(f1)
herring

scala> printF(f2)
<console>:23: error: type mismatch;
 found   : () => String
 required: String
              printF(f2)

scala> printF(f2())
halibut

где теперь парены имеют значение, потому что компилятор пытается сопоставить сигнатуру метода с сигнатурой функции. Особые случаи ситуации с параметрами по имени больше не применяются.

Другие вопросы по тегам