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

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

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)

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

Спасибо.


person dublintech    schedule 20.01.2013    source источник
comment
не уверен, что это точный дубликат, но этот вопрос близок: stackoverflow.com/questions/6939908/   -  person Paolo Falabella    schedule 21.01.2013
comment
Я не думаю, что это дубликат, хотя он связан. Эти вопросы явно фокусируются на параметрах по имени, поэтому такая эквивалентность (или ее отсутствие) должна быть явно указана в ответе.   -  person    schedule 21.01.2013
comment
(Хотя я уверен, что в этом есть много дыр, я обычно рассматриваю вызов по имени как создание неявной оболочки аргумента, переданного внутри анонимной функции. Если принять такое простое представление, то {() => x} и {() => x()} можно просмотреть в качестве эквивалентных прокси, когда x эквивалентно x().)   -  person    schedule 21.01.2013
comment
@pst: это нецелесообразно. Параметры по имени могут быть реализованы так же, как анонимная функция без аргументов, но они совершенно разные. В частности, вы не можете получить функцию, представляющую фактический параметр по имени. Все, что вы можете сделать, это вычислить фактическое выражение параметра (сколько угодно раз), и это не делается путем добавления суффикса (), как это было бы для функции с нулевым аргументом.   -  person Randall Schulz    schedule 21.01.2013


Ответы (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, параметр по имени уже заполняет отсутствующее (), но затем оборачивает все это в блок кода для последующего выполнения.

(Заметьте также, что параметры по имени на самом деле просто 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

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

person Rex Kerr    schedule 20.01.2013