Вызов по имени против вызова по значению в Scala, требуется уточнение

239

Насколько я понимаю, в Scala функцию можно вызывать либо

  • по стоимости или
  • по имени

Например, учитывая следующие объявления, знаем ли мы, как будет вызываться функция?

Декларация:

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

Вызов

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

Какие правила, пожалуйста?

Джеймс Райцев
источник

Ответы:

540

В приведенном вами примере используется только вызов по значению, поэтому я приведу новый, более простой пример, показывающий разницу.

Во-первых, давайте предположим, что у нас есть функция с побочным эффектом. Эта функция печатает что-то, а затем возвращает 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()) произошел только один раз. Тем не менее, в версии по имени, побочный эффект произошел дважды.

Это связано с тем, что функции вызова по значению вычисляют значение переданного выражения перед вызовом функции, поэтому каждый раз к одному и тому же значению обращаются. Вместо этого функции вызова по имени повторно вычисляют значение переданного выражения при каждом обращении к нему.

DHG
источник
296
Я всегда думал, что эта терминология излишне запутана. Функция может иметь несколько параметров, которые различаются по своему названию по сравнению с состоянием по значению. Так что это не то , что функция является вызов по имени или вызов по значению, это то , что каждый из его параметров можно передать -по-имя или передавать по значению. Кроме того, «вызов по имени» не имеет ничего общего с именами . => 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, я думаю, вы имели в виду test (7,8) вместо test (7,14)
talonx
1
Пример взят из Coursera, принципа в программировании scala. Лекция 1.2. Вызов по имени должен прочитать, def test (x:Int, y: => Int) = x * xчто параметр y никогда не используется.
Др Джерри
1
Хороший пример! Взято с Coursera MOOC :)
alxsimo
Это хорошее объяснение разницы, но оно не касается вопроса, который задают, а именно, какой из этих двух вопросов вызывает Scala
db1234
16

В вашем примере все параметры будут оценены до того, как она будет вызвана в функции, так как вы определяете их только по значению . Если вы хотите определить свои параметры по имени, вы должны передать блок кода:

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

Таким образом, параметр xне будет оцениваться до тех пор, пока он не будет вызван в функции.

Этот небольшой пост здесь также объясняет это хорошо.

resilva87
источник
10

Чтобы повторить точку зрения @ Бена в вышеприведенных комментариях, я думаю, что лучше всего рассматривать «вызов по имени» как просто синтаксический сахар. Парсер просто оборачивает выражения в анонимные функции, чтобы их можно было вызывать на более позднем этапе, когда они используются.

По сути, вместо определения

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
user1496984
источник
Я думаю, что вы имели в виду: <! - language: lang-scala -> def callAlsoByName (x: () => Int) = {println ("x1 =" + x ()) println ("x2 =" + x ( ))} и затем: <! - language: lang-js -> callAlsoByName (() => что-то ()) Я не думаю, что вам нужны фигурные скобки вокруг что-то () в последнем вызове. Примечание. Я попытался отредактировать ваш ответ, но рецензенты отклонили его, заявив, что это должен быть комментарий или отдельный ответ.
lambdista
Очевидно, вы не можете использовать подсветку синтаксиса в комментариях, поэтому просто игнорируйте часть "<! - language: lang-scala ->"! Я бы отредактировал свой комментарий, но вы можете сделать это только в течение 5 минут! :)
lambdista
1
Я недавно столкнулся с этим также. Это нормально, если концептуально думать так, но scala различает => 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())
    }
}

В вышеприведенной реализации наггер будет работать только при передаче по имени, причина в том, что при передаче по значению оно будет использоваться повторно, и поэтому значение не будет переоцениваться, в то время как при передаче по имени значение будет переоцениваться каждые время доступа к переменным

guykaplan
источник
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: /> Скалярный Test.scala 
 2. Скала Тест
 3. В отложенном методе
 4. Получение времени в нано секундах
 5. Param: 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метод вызывается дважды, потому что, когда мы обращаемся к значению метода xin callByName, вызывается его определение somethingметода.

Хармит Сингх Таара
источник
2

Вызов по значению - это общий случай использования, который объясняется многими ответами

Вызов по имени передает кодовый блок вызывающей стороне, и каждый раз, когда вызывающая сторона обращается к параметру, выполняется кодовый блок и вычисляется значение.

Ниже я попытаюсь продемонстрировать более простой способ вызова по имени с примерами использования.

Пример 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 (со scala) использует ведение журналов с использованием вызова по имени, чтобы увидеть Loggingчерту, в которой лениво оценивает, используется ли log.isInfoEnabledприведенный ниже метод.

protected def logInfo(msg: => String) {
     if (log.isInfoEnabled) log.info(msg)
 }
Рам Гадиярам
источник
2

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

Принимая во внимание, что при вызове по имени само выражение передается функции в качестве параметра и вычисляется только внутри функции, когда вызывается этот конкретный параметр.

Разницу между Call by Name и Call by Value в 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 ()) системное время nano предварительно рассчитывается, и это предварительно вычисленное значение было передано параметром в вызов функции.

Но при вызове функции 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
========================
Madpoptart
источник
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) -> ... 
iblamefish
источник
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

Механизм вызова по имени передает кодовый блок на вызов, и каждый раз, когда вызов обращается к параметру, выполняется кодовый блок и вычисляется значение. В следующем примере, 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*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
}

Теперь мы собираемся определить две функции, которые принимают аргументы 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
}
Игнасио Алорре
источник
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))
Пунит Редди V
источник
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 - это вызов по имени. То, что передается как вызов по имени, это add (2, 1). Это будет оцениваться лениво. Таким образом, вывод на консоль будет «mul», затем «add», хотя add, кажется, вызывается первым. Вызов по имени действует как передача указателя на функцию.
Теперь измените с y: => Int на y: Int. Консоль покажет «добавить», а затем «мул»! Обычный способ оценки.

Апурва Сингх
источник
-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и, следовательно, сохранять немного вычислений, которые иногда могут быть полезны.

Вивек
источник