Дзвінок по імені проти виклику за значенням у Scala, потрібне уточнення


239

Як я розумію, в Scala функція може називатися або

  • за вартістю або
  • прізвище

Наприклад, даючи наступні декларації, чи знаємо ми, як буде називатися функція?

Декларація:

def  f (x:Int, y:Int) = x;

Дзвінок

f (1,2)
f (23+55,5)
f (12+3, 44*11)

Які правила, будь ласка?

Відповіді:


540

Наведений вами приклад використовує лише call-by-value, тому я наведу новий, простіший приклад, який показує різницю.

Спочатку припустимо, що у нас є функція з побічним ефектом. Ця функція щось роздруковує, а потім повертає Int.

def something() = {
  println("calling something")
  1 // return value
}

Тепер ми визначимо дві функції, які приймають Intабсолютно однакові аргументи, за винятком того, що одна приймає аргумент у стилі call-by-value ( x: Int), а інша у стилі call-by-name ( 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

Таким чином, ви можете бачити, що у версії за викликом за значенням побічний ефект виклику функції, що передається ( something()), відбувся лише один раз. Однак у варіанті заклику до імені побічний ефект траплявся двічі.

Це пояснюється тим, що функції виклику за значенням обчислюють значення переданого в виразі перед викликом функції, таким чином , до кожного і того ж значення можна отримати доступ кожного разу. Замість виклику по імені функції перераховувати передане значення виразу кожен раз при зверненні до нього.


296
Я завжди вважав, що ця термінологія непотрібно заплутати. Функція може мати декілька параметрів, які залежать від статусу дзвінка по імені та за викликом за значенням. Отже, справа не в тому, що функція називається по-імені або називається за значенням, це те, що кожен з її параметрів може бути пропускним -іменем або прохідним значенням. Крім того, "call-by-name" не має нічого спільного з іменами . => Int- різний тип від Int; це "функція без аргументів, яка генерує Int" проти просто Int. Після того, як ви отримаєте першокласні функції, вам не потрібно придумувати термінологію виклику по імені, щоб описати це.
Бен

2
@Ben, це допомагає відповісти на пару питань, дякую. Я хотів би, щоб більше записів пояснило семантику проходження імені це чітко.
Крістофер Пойл

3
@SelimOber Якщо текст f(2)складено як вираз типу Int, згенерований код викликає fаргумент, 2а результат - значення виразу. Якщо той самий текст складений як вираз типу, => Intто генерований код використовує посилання на якийсь "блок коду" як значення виразу. У будь-якому випадку значення цього типу можна передати функції, що очікує параметр цього типу. Я впевнений, що ви можете це зробити зі змінним призначенням, не бачачи жодного параметра. Отже, що імена або дзвінки мають щось спільне?
Бен

4
@Ben Отже, якщо => Intце "функція без аргументів, яка генерує Int", чим вона відрізняється від () => Int? Схоже, Scala ставиться до цього по-різному, наприклад, => Intмабуть, не працює як тип a val, лише як тип параметра.
Тім Гудман

5
@TimGoodman Ви маєте рацію, це трохи складніше, ніж я розібрався. => Intце зручність, і вона реалізована не так, як є об'єктом функції (імовірно, чому ви не можете мати змінні типу => Int, хоча немає принципової причини, чому це не може працювати). () => Intце явно залежить від будь - яких аргументів , які будуть повертати Int, яка повинна бути викликана явно і може бути переданий в якості опції. => Intє свого роду "проксі Int", і єдине, що ви можете зробити з ним, це викликати його (неявно), щоб отримати Int.
Бен

51

Ось приклад Мартіна Одерського:

def test (x:Int, y: Int)= x*x

Ми хочемо вивчити стратегію оцінювання та визначити, яка з них швидша (менше кроків) у цих умовах:

test (2,3)

виклик за значенням: тест (2,3) -> 2 * 2 -> 4
виклик по імені: тест (2,3) -> 2 * 2 -> 4
Тут результат досягається з однаковою кількістю кроків.

test (3+4,8)

дзвінок за значенням: тест (7,8) -> 7 * 7 -> 49
дзвінок по імені: (3 + 4) (3 + 4) -> 7 (3 + 4) -> 7 * 7 -> 49
Тут дзвоніть за значенням швидше.

test (7,2*4)

дзвінок за значенням: тест (7,8) -> 7 * 7 -> 49
дзвінок по імені: 7 * 7 -> 49
Тут дзвінок за іменем швидше

test (3+4, 2*4) 

дзвінок за значенням: тест (7,2 * 4) -> тест (7, 8) -> 7 * 7 -> 49
дзвінок по імені: (3 + 4) (3 + 4) -> 7 (3 + 4) -> 7 * 7 -> 49
Результат досягається в рамках тих же кроків.


1
У третьому прикладі для CBV, я думаю, ви мали на увазі тест (7,8) замість тесту (7,14)
talonx

1
Приклад взято з Coursera, принципу в програмі масштабування. Лекція 1.2. Виклик за іменем повинен читати def test (x:Int, y: => Int) = x * xзауваження, що параметр y ніколи не використовується.
д-р Джері

1
Хороший приклад! Взято з Coursera MOOC :)
alxsimo

Це є гарним поясненням різниці, але не звертається до запитань, що їх задають, а саме, хто з двох дзвонить Скалі
db1234

16

У випадку вашого прикладу всі параметри будуть оцінені до виклику функції, оскільки ви визначаєте їх лише за значенням . Якщо ви хочете визначити свої параметри за назвою, вам слід передати блок коду:

def f(x: => Int, y:Int) = x

Таким чином параметр xне буде оцінено, поки він не буде викликаний у функції.

Цей невеличкий пост тут також добре пояснює.


10

Для ітерату @ @ Ben у вищезазначених коментарях, я думаю, що найкраще думати про "call-by-name" як просто синтаксичний цукор. Аналізатор просто загортає вирази в анонімні функції, щоб їх можна було викликати в більш пізній момент, коли вони використовуються.

Насправді замість визначення

def callByName(x: => Int) = {
  println("x1=" + x)
  println("x2=" + x)
}

і працює:

scala> callByName(something())
calling something
x1=1
calling something
x2=1

Ви також можете написати:

def callAlsoByName(x: () => Int) = {
  println("x1=" + x())
  println("x2=" + x())
}

І запустити його наступним чином для того ж ефекту:

callAlsoByName(() => {something()})

calling something
x1=1
calling something
x2=1

Я думаю, ви мали на увазі: <! - мова: lang-scala -> def callAlsoByName (x: () => Int) = {println ("x1 =" + x ()) println ("x2 =" + x ( ))}, а потім: <! - мова: lang-js -> callAlsoByName (() => щось ()) Я не думаю, що вам потрібні фігурні дужки навколо чогось () в цьому останньому дзвінку. Примітка. Я спробував просто відредагувати вашу відповідь, але мою редакцію рецензенти відхилили, сказавши, що замість цього має бути коментар або окрема відповідь.
лямбдіста

Мабуть, ви не можете використовувати підсвічування синтаксису в коментарях, тому просто ігноруйте частину "<! - language: lang-scala ->"! Я б відредагував власний коментар, але ви можете це робити лише протягом 5 хвилин! :)
lambdista

1
Нещодавно я також натрапив на це. Добре концептуально думати це так, але масштаб розрізняє => Tі () => T. Функція, яка приймає перший тип як параметр, не сприймає другий, scala зберігає достатньо інформації в @ScalaSignatureанотації, щоб скинути для цього помилку часу компіляції. Байт-код для обох => Tі () => Tє однаковим, хоча і є Function0. Дивіться це питання для більш детальної інформації.
vsnyc

6

Я спробую пояснити простим випадком використання, а не просто прикладом

Уявіть, що ви хочете створити "гніздовий додаток", який буде наганяти вас кожного разу з моменту останнього набої.

Вивчіть такі реалізації:

object main  {

    def main(args: Array[String]) {

        def onTime(time: Long) {
            while(time != time) println("Time to Nag!")
            println("no nags for you!")
        }

        def onRealtime(time: => Long) {
            while(time != time) println("Realtime Nagging executed!")
        }

        onTime(System.nanoTime())
        onRealtime(System.nanoTime())
    }
}

У вищезазначеній реалізації наггер працюватиме лише при передачі по імені, причина полягає в тому, що при передачі за значенням воно буде повторно використане, і тому значення не буде повторно оцінено, коли при передачі по імені значення буде переоцінюватися кожен час доступу до змінних


4

Як правило, параметри функцій є значеннями параметрів; тобто значення параметра визначається до його передачі функції. Але що робити, якщо нам потрібно записати функцію, яка приймає в якості параметра вираз, який ми не хочемо оцінювати, поки він не буде викликаний у межах нашої функції? З цієї обставини Scala пропонує параметри заклику по імені.

Механізм виклику по імені передає блоку коду виклику і кожен раз, коли викликає доступ до параметра, блок коду виконується і обчислюється значення.

object Test {
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)
  t
}
}
 1. C: /> scalac Test.scala 
 2. Тест на шкалу
 3. При затриманому методі
 4. Отримання часу в наносекундах
 5. Парам: 81303808765843
 6. Отримання часу в наносекундах

2

Як я припускаю, call-by-valueфункція, як обговорювалося вище, передає лише значення функції. Відповідно до Martin Oderskyстратегії оцінювання, яку слід застосовувати Scala, яка відіграє важливу роль в оцінці функцій. Але, зробити це просто call-by-name. це як передає функцію як аргумент методу також відомо як Higher-Order-Functions. Коли метод отримує доступ до значення переданого параметра, він викликає реалізацію переданих функцій. як нижче:

Відповідно до прикладу @dhg, створіть метод спочатку як:

def something() = {
 println("calling something")
 1 // return value
}  

Ця функція містить одне printlnтвердження і повертає ціле значення. Створіть функцію, яка має аргументи як call-by-name:

def callByName(x: => Int) = {
 println("x1=" + x)
 println("x2=" + x)
}

Цей параметр функції визначає анонімну функцію, яка має одне ціле значення. Тут xміститься визначення функції, яка 0передала аргументи, але повертає intзначення, а наша somethingфункція містить ту саму підпис. Коли ми викликаємо функцію, ми передаємо функцію як аргумент callByName. Але у випадку call-by-valueйого лише передайте ціле значення функції. Функцію ми називаємо так:

scala> callByName(something())
 calling something
 x1=1
 calling something
 x2=1 

У цьому наш somethingметод викликається двічі, тому що , коли ми досягаємо значення xв callByNameметоді, його заклик до визначення з somethingметоду.


2

Дзвінок за значенням є загальним випадком використання, як пояснено багатьма відповідями тут ..

Call-by-name передає код коду абоненту, і кожного разу, коли абонент звертається до параметра, блок коду виконується і обчислюється значення.

Я спробую продемонструвати дзвінок по імені більш простим способом із наведеними нижче випадками використання

Приклад 1:

Простий приклад / випадок використання виклику за іменем знаходиться нижче функції, яка приймає функцію як параметр і дає час, що минув.

 /**
   * Executes some code block and prints to stdout the 
time taken to execute   the block 
for interactive testing and debugging.
   */
  def time[T](f: => T): T = {
    val start = System.nanoTime()
    val ret = f
    val end = System.nanoTime()

    println(s"Time taken: ${(end - start) / 1000 / 1000} ms")

    ret
  }

Приклад 2:

apache spark (зі шкалою) використовує ведення журналу, використовуючи call by name name, дивіться Loggingознаку, в якій його ліниво оцінює, чи log.isInfoEnabledне використовується метод нижче.

protected def logInfo(msg: => String) {
     if (log.isInfoEnabled) log.info(msg)
 }

2

У виклику за значенням значення виразу попередньо обчислюється під час виклику функції, і це певне значення передається як параметр відповідної функції. Одне значення буде використовуватися для всієї функції.

Тоді як у виклику по імені сам вираз передається як параметр функції, і він обчислюється лише всередині функції, щоразу, коли цей конкретний параметр викликається.

Різницю між дзвінками по імені та викликом за значенням у Scala можна було б краще зрозуміти із наведеного нижче прикладу:

Знімок коду

object CallbyExample extends App {

  // function definition of call by value
  def CallbyValue(x: Long): Unit = {
    println("The current system time via CBV: " + x);
    println("The current system time via CBV " + x);
  }

  // function definition of call by name
  def CallbyName(x: => Long): Unit = {
    println("The current system time via CBN: " + x);
    println("The current system time via CBN: " + x);
  }

  // function call
  CallbyValue(System.nanoTime());
  println("\n")
  CallbyName(System.nanoTime());
}

Вихідні дані

The current system time via CBV: 1153969332591521
The current system time via CBV 1153969332591521


The current system time via CBN: 1153969336749571
The current system time via CBN: 1153969336856589

У наведеному вище фрагменті коду для виклику функції CallbyValue (System.nanoTime ()) попередньо обчислюється нано-час системи та попередньо обчислене значення передано параметр виклику функції.

Але у виклику функції CallbyName (System.nanoTime ()) сам вираз "System.nanoTime ())" передається як параметр виклику функції, і значення цього виразу обчислюється, коли цей параметр використовується всередині функції .

Зверніть увагу на визначення функції функції CallbyName, де є символ =>, що розділяє параметр x та його тип даних. Цей конкретний символ вказує на функцію дзвінка за типом імені.

Іншими словами, виклик за значенням аргументів функції оцінюється один раз перед входом у функцію, але виклик аргументів функції імені оцінюється всередині функції лише тоді, коли вони потрібні.

Сподіваюсь, це допомагає!


2

Ось короткий приклад, який я кодував, щоб допомогти моєму колезі, який зараз проходить курс Scala. Що мені здалося цікавим, це те, що Мартін не використав відповідь на && питання, представлену раніше на лекції, як приклад. У будь-якому випадку я сподіваюся, що це допоможе.

val start = Instant.now().toEpochMilli

val calc = (x: Boolean) => {
    Thread.sleep(3000)
    x
}


def callByValue(x: Boolean, y: Boolean): Boolean = {
    if (!x) x else y
}

def callByName(x: Boolean, y: => Boolean): Boolean = {
    if (!x) x else y
}

new Thread(() => {
    println("========================")
    println("Call by Value " + callByValue(false, calc(true)))
    println("Time " + (Instant.now().toEpochMilli - start) + "ms")
    println("========================")
}).start()


new Thread(() => {
    println("========================")
    println("Call by Name " + callByName(false, calc(true)))
    println("Time " + (Instant.now().toEpochMilli - start) + "ms")
    println("========================")
}).start()


Thread.sleep(5000)

Вихід коду буде таким:

========================
Call by Name false
Time 64ms
========================
Call by Value false
Time 3068ms
========================

1

Параметри зазвичай передаються за значенням, це означає, що вони будуть оцінені перед тим, як замінити їх у тілі функції.

Ви можете змусити параметр викликати по імені, використовуючи подвійну стрілку під час визначення функції.

// first parameter will be call by value, second call by name, using `=>`
def returnOne(x: Int, y: => Int): Int = 1

// to demonstrate the benefits of call by name, create an infinite recursion
def loop(x: Int): Int = loop(x)

// will return one, since `loop(2)` is passed by name so no evaluated
returnOne(2, loop(2))

// will not terminate, since loop(2) will evaluate. 
returnOne(loop(2), 2) // -> returnOne(loop(2), 2) -> returnOne(loop(2), 2) -> ... 

1

В Інтернеті вже є багато фантастичних відповідей на це питання. Я напишу збірку з декількох зібраних пояснень та прикладів щодо цієї теми, на всякий випадок, якщо хтось може вважати її корисною

ВСТУП

вартість дзвінка (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*24

Оскільки ми почнемо з уже оцінених аргументів, це буде однакова кількість кроків для виклику за значенням та виклику за іменем

Case2 тест (3 + 4,8)

call-by-value: test(3+4,8) → test(7,8)7 * 749
call-by-name: (3+4)*(3+4)7 * (3+4)7 * 749

У цьому випадку виклик за значенням виконує менше кроків

Case3 тест (7, 2 * 4)

call-by-value: test(7, 2*4) → test(7,8)7 * 749
call-by-name: (7)*(7)49

Ми уникаємо зайвих обчислень другого аргументу

Тест Case4 (3 + 4, 2 * 4)

call-by-value: test(7, 2*4) → test(7,8)7 * 749
call-by-name: (3+4)*(3+4)7*(3+4)7*749

Різний підхід

Спочатку припустимо, що у нас є функція з побічним ефектом. Ця функція щось роздруковує, а потім повертає 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
}

1

Перегляд прикладу повинен допомогти вам краще зрозуміти різницю.

Давайте визначимо просту функцію, яка повертає поточний час:

def getTime = System.currentTimeMillis

Тепер ми визначимо функцію за назвою , яка друкує два рази затримку на секунду:

def getTimeByName(f: => Long) = { println(f); Thread.sleep(1000); println(f)}

І один за значенням :

def getTimeByValue(f: Long) = { println(f); Thread.sleep(1000); println(f)}

Тепер давайте зателефонуємо кожному:

getTimeByName(getTime)
// prints:
// 1514451008323
// 1514451009325

getTimeByValue(getTime)
// prints:
// 1514451024846
// 1514451024846

Результат повинен пояснити різницю. Фрагмент доступний тут .


0

CallByNameвикликається при використанні та callByValueвикликається кожного разу, коли трапляється твердження.

Наприклад:-

У мене є нескінченний цикл, тобто якщо ви виконаєте цю функцію, ми ніколи не отримаємо scalaпідказки.

scala> def loop(x:Int) :Int = loop(x-1)
loop: (x: Int)Int

callByNameфункція приймає вище loopметоди в якості аргументу , і він ніколи не використовується всередині свого тіла.

scala> def callByName(x:Int,y: => Int)=x
callByName: (x: Int, y: => Int)Int

При виконанні callByNameметоду ми не знаходимо жодної проблеми (ми scalaповертаємося назад), оскільки ми не використовуємо функцію циклу всередині callByNameфункції.

scala> callByName(1,loop(10))
res1: Int = 1
scala> 

callByValueфункція приймає вище loopметоди в якості параметра в результаті всередині функції або виразу обчислюється перед виконанням зовнішньої функції там loopфункціями , виконуваними рекурсивно , і ми ніколи не отримати scalaпідказки назад.

scala> def callByValue(x:Int,y:Int) = x
callByValue: (x: Int, y: Int)Int

scala> callByValue(1,loop(1))

0

Дивіться це:

    object NameVsVal extends App {

  def mul(x: Int, y: => Int) : Int = {
    println("mul")
    x * y
  }
  def add(x: Int, y: Int): Int = {
    println("add")
    x + y
  }
  println(mul(3, add(2, 1)))
}

y: => Int називається по імені. Додано те, що передається як дзвінок по імені (2, 1). Це буде оцінено ліниво. Таким чином, вихід на консоль буде "mul", а потім "add", хоча add, здається, називається першим. Виклик по імені виступає як вид передачі вказівника функції.
Тепер перейдіть від y: => Int до y: Int. На консолі буде показано "додати", а потім "mul"! Звичайний спосіб оцінювання.


-2

Я не думаю, що всі відповіді тут служать правильним обгрунтуванням:

Аргументи обчислюються за значенням лише один раз:

def f(x : Int, y :Int) = x

// following the substitution model

f(12 + 3, 4 * 11)
f(15, 4194304)
15

Ви можете бачити вище, що всі аргументи оцінюються, чи не потрібні, як правило, немає call-by-value можуть бути швидкими, але не завжди, як у цьому випадку.

Якби стратегія оцінювання була, call-by-nameто декомпозиція була б такою:

f(12 + 3, 4 * 11)
12 + 3
15

як ви бачите вище, нам ніколи не потрібно було оцінювати, 4 * 11а значить, заощадили трохи обчислень, які можуть бути корисними іноді.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.