Вызов по имени против вызова по значению в Scala, требуется уточнение
Насколько я понимаю, в Scala функцию можно вызывать либо
- по стоимости или
- по имени
Например, учитывая следующие объявления, знаем ли мы, как будет вызываться функция?
Декларация:
def f (x:Int, y:Int) = x;
Вызов
f (1,2)
f (23+55,5)
f (12+3, 44*11)
Какие правила, пожалуйста?
17 ответов
В приведенном вами примере используется только вызов по значению, поэтому я приведу новый, более простой пример, показывающий разницу.
Сначала предположим, что у нас есть функция с побочным эффектом. Эта функция печатает что-то, а затем возвращает 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
Таким образом, вы можете видеть, что в версии с вызовом по значению, побочный эффект переданного вызова функции (something()
) произошло только один раз. Однако в версии с именем по имени побочный эффект произошел дважды.
Это связано с тем, что функции вызова по значению вычисляют значение переданного выражения перед вызовом функции, поэтому каждый раз к одному и тому же значению обращаются. Однако функции вызова по имени повторно вычисляют значение переданного выражения при каждом обращении к нему.
Вот пример от Мартина Одерского:
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
Результат достигается в течение тех же шагов.
В вашем примере все параметры будут оценены до того, как она будет вызвана в функции, так как вы определяете их только по значению. Если вы хотите определить свои параметры по имени, вы должны передать блок кода:
def f(x: => Int, y:Int) = x
Таким образом, параметр x
не будет оцениваться, пока не будет вызвана в функции.
Этот небольшой пост здесь также объясняет это хорошо.
Чтобы повторить точку зрения @ Бена в вышеприведенных комментариях, я думаю, что лучше всего рассматривать "вызов по имени" как просто синтаксический сахар. Парсер просто оборачивает выражения в анонимные функции, чтобы их можно было вызывать на более позднем этапе, когда они используются.
По сути, вместо определения
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
Я попытаюсь объяснить простым вариантом использования, а не просто предоставив пример
Представьте, что вы хотите создать "приложение наггера", которое будет нагнетать вас каждый раз, с тех пор как вы в последний раз были ворчливыми
Изучите следующие реализации:
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())
}
}
В вышеупомянутой реализации наггер будет работать только при передаче по имени, причина в том, что при передаче по значению оно будет использоваться повторно, и, следовательно, значение не будет переоцениваться, в то время как при передаче по имени значение будет переоцениваться каждые время доступа к переменным
Как правило, параметры функций являются побочными параметрами; значение параметра определяется до его передачи в функцию. Но что если нам нужно написать функцию, которая принимает в качестве параметра выражение, которое мы не хотим оценивать, пока оно не будет вызвано внутри нашей функции? Для этого обстоятельства 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. Получение времени в нано секундах
Как я полагаю, 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
метод.
При вызове по значению значение выражения предварительно вычисляется во время вызова функции, и это конкретное значение передается в качестве параметра соответствующей функции. Одно и то же значение будет использоваться во всей функции.
Принимая во внимание, что при вызове по имени само выражение передается функции в качестве параметра и вычисляется только внутри функции, когда вызывается этот конкретный параметр.
Разницу между 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 и его тип данных. Этот конкретный символ указывает на то, что функция вызывается по типу имени.
Другими словами, аргументы функции вызова по значению оцениваются один раз перед входом в функцию, но аргументы функции вызова по имени оцениваются внутри функции только тогда, когда они необходимы.
Надеюсь это поможет!
Вот быстрый пример, который я кодировал, чтобы помочь моему коллеге, который в настоящее время проходит курс 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:
Простой пример / вариант использования вызова по имени находится ниже функции, которая принимает функцию в качестве параметра и дает истекшее время.
/**
* 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)
}
Параметры обычно передаются по значению, что означает, что они будут оцениваться перед заменой в теле функции.
Вы можете заставить параметр вызываться по имени, используя двойную стрелку при определении функции.
// 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) -> ...
Изучение примера поможет вам лучше понять разницу.
Давайте определим простую функцию, которая возвращает текущее время:
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
Результат должен объяснить разницу. Фрагмент доступен здесь.
Уже есть много фантастических ответов на этот вопрос в Интернете. Я напишу сборник нескольких объяснений и примеров, которые я собрал по этой теме, на случай, если кто-то может найти это полезным
ВСТУПЛЕНИЕ
вызов по значению (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)
}
}
В задержанном методе
Получение времени в нано секундах
Param: 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
От: /questions/31174139/kogda-ispolzovat-vyizov-po-imeni-i-vyizov-po-znacheniyu/31174154#31174154
Простой пример производительности: регистрация.
Давайте представим такой интерфейс:
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
}
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))
Видеть это:
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. Консоль покажет "добавить", а затем "мул"! Обычный способ оценки.
Я не думаю, что все ответы здесь дают правильное обоснование:
При вызове по значению аргументы вычисляются только один раз:
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
и, следовательно, сэкономил немного вычислений, которые иногда могут быть полезны.
Оценка переменных Scala объяснена здесь лучше https://sudarshankasar.medium.com/evaluation-rules-in-scala-1ed988776ae8