Использование 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
где теперь парены имеют значение, потому что компилятор пытается сопоставить сигнатуру метода с сигнатурой функции. Особые случаи ситуации с параметрами по имени больше не применяются.