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

180

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

  • по значению или
  • по имени

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

Декларация:

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

Вызов

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

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

Теги:

16 ответов

433
Лучший ответ

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

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

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

  • 0
    Было бы правильно сказать, что объявление функции определяет, как будет вызываться функция?
  • 0
    @ Джем: Да, я думаю, что это разумное утверждение. Объявление функции определяет поведение по значению или по имени.
Показать ещё 11 комментариев
42

Вот пример из Мартина Одерского:

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

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

test (2,3)

вызов по значению: test (2,3) → 2 * 2 → 4
по имени: test (2,3) → 2 * 2 → 4
Здесь результат достигается с тем же числом шагов.

test (3+4,8)

вызов по значению: test (7,8) → 7 * 7 → 49
позвоните по имени: (3 + 4) (3 + 4) → 7 (3 + 4) → 7 * 7 → 49
Здесь вызов по значению выполняется быстрее.

test (7,2*4)

вызов по значению: test (7,8) → 7 * 7 → 49
позвоните по имени: 7 * 7 → 49
Здесь вызов по имени быстрее

test (3+4, 2*4) 

вызов по значению: test (7,2 * 4) → test (7, 8) → 7 * 7 → 49
позвоните по имени: (3 + 4) (3 + 4) → 7 (3 + 4) → 7 * 7 → 49
Результат достигается на тех же шагах.

  • 1
    В третьем примере для CBV, я думаю, вы имели в виду тест (7,8) вместо теста (7,14)
  • 1
    Пример взят из Coursera, принципа в программировании scala. Лекция 1.2. Вызов по имени должен читать def test (x:Int, y: => Int) = x * x заметьте, что параметр y никогда не используется.
Показать ещё 2 комментария
14

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

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

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

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

7

Я попытаюсь объяснить простым примером использования, а не просто примером

Представьте, что вы хотите создать приложение "nagger" , которое будет на вас навеки каждый раз, начиная с момента последнего обращения.

Изучите следующие реализации:

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())
    }
}

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

6

Чтобы итератировать @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
  • 0
    Я думаю, что вы имели в виду: <! - language: lang-scala -> def callAlsoByName (x: () => Int) = {println ("x1 =" + x ()) println ("x2 =" + x ( ))} и затем: <! - language: lang-js -> callAlsoByName (() => кое-что ()) Я не думаю, что вам нужны фигурные скобки вокруг что-то () в этом последнем вызове. Примечание. Я попытался отредактировать ваш ответ, но рецензенты отклонили его, заявив, что это должен быть комментарий или отдельный ответ.
  • 0
    Очевидно, вы не можете использовать подсветку синтаксиса в комментариях, поэтому просто игнорируйте часть "<! - language: lang-scala ->"! Я бы отредактировал свой комментарий, но вы можете сделать это только в течение 5 минут! :)
Показать ещё 1 комментарий
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. scala Test
 3. In delayed method
 4. Getting time in nano seconds
 5. Param: 81303808765843
 6. Getting time in nano seconds
3

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

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

Прохождение примера поможет вам лучше понять разницу.

Определить простую функцию, которая возвращает текущее время:

def getTime = System.currentTimeMillis

Теперь мы определим функцию с помощью name, которая печатает два раза с задержкой на секунду:

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

Результат должен объяснить разницу. Фрагмент доступен здесь.

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

call-by-name (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)
       }
    }

В отложенном методе Получение времени в nano seconds
Параметр: 2027245119786400

PROS AND CONS ДЛЯ КАЖДОГО ДЕЛА

CBN: + Заканчивается чаще * проверка ниже выше завершения * + Имеет то преимущество, что аргумент функции не оценивается, если соответствующий параметр не используется при оценке тела функции -Это медленнее, он создает больше классов (что означает, что программа занимает больше времени для загрузки), и она потребляет больше памяти.

ОЦК: + Он часто экспоненциально более эффективен, чем CBN, потому что он избегает повторной перераспределения аргументов, которые влекут за собой выражения, вызываемые по имени. Он оценивает каждый аргумент функции только один раз + Он играет намного лучше с императивными эффектами и побочными эффектами, потому что вы, как правило, знаете намного лучше, когда выражения будут оцениваться. -Это может привести к циклу во время оценки параметров * проверить ниже выше завершения *

Что делать, если завершение не гарантируется?

-Если оценка CBV выражения e завершается, тогда CBN-оценка e также заканчивается - Другое направление неверно

Пример без прерывания

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

Рассмотрим выражение first (1, loop)

CBN: первый (1, цикл) → 1 CBV: first (1, loop) → уменьшить аргументы этого выражения. Поскольку один представляет собой цикл, он бесконечно уменьшает аргументы. Он не завершает

РАЗЛИЧИЯ В КАЖДОМ СЛУЧАЕ СЛУЧАЕВ

Пусть определите тест метода, который будет

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

В этом случае call-by-value выполняет меньше шагов

Тест 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

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

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

ПРИМЕРЫ, КОТОРЫЕ ЛУЧШЕ ИСПОЛЬЗОВАТЬ ИСПОЛЬЗОВАНИЕ CALL-BY-NAME

От: https://stackoverflow.com/questions/19035450/when-to-use-call-by-name-and-call-by-value

Простой пример производительности: ведение журнала.

Представьте себе такой интерфейс:

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

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

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

// 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) -> ... 
0

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

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

Разницу между 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 и его тип данных. Этот конкретный символ указывает, что функция имеет вызов по типу имени.

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

Надеюсь, это поможет!

0

Согласно Мартин Одерский:

Обе стратегии сводятся к тем же конечным значениям, что и:

  • приведенное выражение состоит из чистых функций и
  • завершаются обе оценки.

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

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

Изображение 6064

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", хотя добавление, кажется, называется первым. Вызов по имени действует как вид передачи указателя функции.
Теперь измените с y: = > Int на y: Int. Консоль покажет "добавить", а затем "mul"! Обычный способ оценки.

0

Я не думаю, что все ответы здесь делают правильное оправдание:

При вызове по значению аргументы вычисляются только один раз:

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

0

CallByName вызывается при использовании и callByValue вызывается всякий раз, когда встречается оператор.

Например: -

У меня бесконечный цикл, т.е. если вы выполните эту функцию, мы никогда не получим приглашение scala.

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

a 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> 
Функция

a callByValue принимает выше метод loop как параметр, в результате внутри функции или выражения оценивается перед выполнением внешней функции там посредством функции loop, выполняемой рекурсивно, и мы никогда не получаем приглашение scala назад.

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

scala> callByValue(1,loop(1))

Ещё вопросы

Сообщество Overcoder
Наверх
Меню