Уже есть много фантастических ответов на этот вопрос в Интернете. Я напишу сборник нескольких объяснений и примеров, которые я собрал по этой теме, на случай, если кто-то может найти это полезным
ВВЕДЕНИЕ
вызов по значению (CBV)
Обычно параметры функций являются параметрами вызова по значению; то есть параметры оцениваются слева направо, чтобы определить их значение, прежде чем оценивается сама функция
def first(a: Int, b: Int): Int = a
first(3 + 4, 5 + 6) // will be reduced to first(7, 5 + 6), then first(7, 11), and then 7
вызов по имени (CBN)
Но что, если нам нужно написать функцию, которая принимает в качестве параметра выражение, которое мы не должны вычислять, пока оно не будет вызвано внутри нашей функции? Для этого обстоятельства Scala предлагает параметры по имени. Это означает, что параметр передается в функцию как есть, и его оценка происходит после подстановки.
def first1(a: Int, b: => Int): Int = a
first1(3 + 4, 5 + 6) // will be reduced to (3 + 4) and then to 7
Механизм вызова по имени передает кодовый блок на вызов, и каждый раз, когда вызов обращается к параметру, выполняется кодовый блок и вычисляется значение. В следующем примере, delayed печатает сообщение, показывающее, что метод был введен. Далее с задержкой печатается сообщение со своим значением. Наконец, отложенное возвращение 't':
object Demo {
def main(args: Array[String]) {
delayed(time());
}
def time() = {
println("Getting time in nano seconds")
System.nanoTime
}
def delayed( t: => Long ) = {
println("In delayed method")
println("Param: " + t)
}
}
В отложенном методе
Получение времени в нано секундах
. Параметр: 2027245119786400
Плюсы и минусы для каждого случая
CBN:
+ чаще завершается * проверка ниже, чем завершение выше * + Преимущество в том, что аргумент функции не оценивается, если соответствующий параметр не используется при оценке тела функции. - Это медленнее, он создает больше классов (то есть программа занимает загружается дольше) и потребляет больше памяти.
CBV:
+ Он часто экспоненциально более эффективен, чем CBN, потому что он избегает повторных вычислений аргументов, вызываемых по имени. Он оценивает каждый аргумент функции только один раз. Он играет намного лучше с императивными и побочными эффектами, потому что вы, как правило, лучше знаете, когда будут оцениваться выражения. -Это может привести к циклу при оценке его параметров * проверка ниже прекращения *
Что делать, если прекращение не гарантировано?
-Если CBV-оценка выражения e завершается, то CBN-оценка e также завершается. -Неное направление неверно
Пример не прекращения
def first(x:Int, y:Int)=x
Сначала рассмотрим выражение (1, цикл)
CBN: первый (1, цикл) → 1 CBV: первый (1, цикл) → уменьшить аргументы этого выражения. Так как каждый является циклом, он сокращает аргументы бесконечно. Не заканчивается
ОТЛИЧИЯ В ПОВЕДЕНИИ КАЖДОГО СЛУЧАЯ
Давайте определим метод теста, который будет
Def test(x:Int, y:Int) = x * x //for call-by-value
Def test(x: => Int, y: => Int) = x * x //for call-by-name
Тест Case1 (2,3)
test(2,3) → 2*2 → 4
Так как мы начинаем с уже оцененных аргументов, будет одинаковое количество шагов для вызова по значению и вызова по имени
Тест Case2 (3 + 4,8)
call-by-value: test(3+4,8) → test(7,8) → 7 * 7 → 49
call-by-name: (3+4)*(3+4) → 7 * (3+4) → 7 * 7 → 49
В этом случае вызов по значению выполняет меньше шагов
Тест Case3 (7, 2 * 4)
call-by-value: test(7, 2*4) → test(7,8) → 7 * 7 → 49
call-by-name: (7)*(7) → 49
Мы избегаем ненужных вычислений второго аргумента
Тест Case4 (3 + 4, 2 * 4)
call-by-value: test(7, 2*4) → test(7,8) → 7 * 7 → 49
call-by-name: (3+4)*(3+4) → 7*(3+4) → 7*7 → 49
Другой подход
Во-первых, давайте предположим, что у нас есть функция с побочным эффектом. Эта функция печатает что-то, а затем возвращает Int.
def something() = {
println("calling something")
1 // return value
}
Теперь мы собираемся определить две функции, которые принимают аргументы Int, которые в точности совпадают, за исключением того, что одна принимает аргумент в стиле вызова по значению (x: Int), а другая - в стиле вызова по имени (x: => Int).
def callByValue(x: Int) = {
println("x1=" + x)
println("x2=" + x)
}
def callByName(x: => Int) = {
println("x1=" + x)
println("x2=" + x)
}
Что происходит, когда мы вызываем их с нашей побочной функцией?
scala> callByValue(something())
calling something
x1=1
x2=1
scala> callByName(something())
calling something
x1=1
calling something
x2=1
Таким образом, вы можете видеть, что в версии с вызовом по значению побочный эффект переданного вызова функции (нечто ()) произошел только один раз. Тем не менее, в версии по имени, побочный эффект произошел дважды.
Это связано с тем, что функции вызова по значению вычисляют значение переданного выражения перед вызовом функции, поэтому каждый раз к одному и тому же значению обращаются. Однако функции вызова по имени повторно вычисляют значение переданного выражения при каждом обращении к нему.
ПРИМЕРЫ ГДЕ ЛУЧШЕ ИСПОЛЬЗОВАТЬ CALL-BY-NAME
От: https://stackoverflow.com/a/19036068/1773841
Простой пример производительности: регистрация.
Давайте представим такой интерфейс:
trait Logger {
def info(msg: => String)
def warn(msg: => String)
def error(msg: => String)
}
А потом использовал вот так:
logger.info("Time spent on X: " + computeTimeSpent)
Если информационный метод ничего не делает (потому что, скажем, уровень ведения журнала был настроен на более высокий уровень), то computeTimeSpent никогда не вызывается, что экономит время. Это часто случается с регистраторами, где часто можно увидеть манипуляции со строками, которые могут быть дорогостоящими по сравнению с регистрируемыми задачами.
Пример корректности: логические операторы.
Вы, наверное, видели такой код:
if (ref != null && ref.isSomething)
Представьте, что вы бы объявили && метод следующим образом:
trait Boolean {
def &&(other: Boolean): Boolean
}
тогда всякий раз, когда ref имеет значение null, вы получите ошибку, потому что isSomething будет вызываться по нулевой ссылке перед передачей в &&. По этой причине фактическая декларация:
trait Boolean {
def &&(other: => Boolean): Boolean =
if (this) this else other
}
=> Int
отличается от типаInt
; это «функция без аргументов, которая будет генерироватьInt
» против простоInt
. Получив первоклассные функции, вам не нужно придумывать терминологию для вызова по имени, чтобы описать это.