В Інтернеті вже є багато фантастичних відповідей на це питання. Я напишу збірку з декількох зібраних пояснень та прикладів щодо цієї теми, на всякий випадок, якщо хтось може вважати її корисною
ВСТУП
вартість дзвінка (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
Механізм виклику по імені передає блок коду на виклик і кожного разу, коли виклик звертається до параметра, виконується блок коду та обчислюється значення. У наступному прикладі відкладене друкує повідомлення, що демонструє, що метод був введений. Далі відкладене друкує повідомлення з його значенням. Нарешті, затримка повернення '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
}
Тепер ми визначимо дві функції, які приймають внутрішні аргументи, які абсолютно однакові, за винятком того, що одна приймає аргумент у стилі виклику за значенням (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
}
тоді, коли посилання буде нульовим, ви отримаєте помилку, оскільки isSomething буде викликано на нульреференції, перш ніж перейти до &&. З цієї причини фактична декларація:
trait Boolean {
def &&(other: => Boolean): Boolean =
if (this) this else other
}
=> Int
- різний тип відInt
; це "функція без аргументів, яка генеруєInt
" проти простоInt
. Після того, як ви отримаєте першокласні функції, вам не потрібно придумувати термінологію виклику по імені, щоб описати це.