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

scala

78687 просмотра

16 ответа

38086 Репутация автора

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

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

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

Декларация:

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

Вызов

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

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

Автор: JAM Источник Размещён: 12.11.2012 01:33

Ответы (16)


503 плюса

47397 Репутация автора

Решение

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

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

14 плюса

1490 Репутация автора

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

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

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

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

Автор: resilva87 Размещён: 12.11.2012 01:45

48 плюса

1113 Репутация автора

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

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
Результат достигается за те же шаги.

Автор: Behrooz Tabesh Размещён: 27.07.2013 07:27

6 плюса

137 Репутация автора

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

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

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

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 Размещён: 09.10.2014 11:52

8 плюса

3821 Репутация автора

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

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

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 Размещён: 30.10.2014 05:42

2 плюса

2900 Репутация автора

Как я предполагаю, 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метода.

Автор: Harmeet Singh Taara Размещён: 19.04.2015 09:15

0 плюса

1032 Репутация автора

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))
Автор: Puneeth Reddy V Размещён: 11.09.2015 06:42

4 плюса

2496 Репутация автора

Как правило, параметры функций являются побочными параметрами; значение параметра определяется до его передачи в функцию. Но что если нам нужно написать функцию, которая принимает в качестве параметра выражение, которое мы не хотим оценивать, пока оно не будет вызвано внутри нашей функции? Для этого обстоятельства 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. Получение времени в нано секундах
Автор: sofiene zaghdoudi Размещён: 25.02.2016 11:04

-2 плюса

1264 Репутация автора

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

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

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

Автор: vivek Размещён: 02.08.2016 11:48

1 плюс

3863 Репутация автора

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

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

// 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 Размещён: 11.01.2017 10:05

0 плюса

2246 Репутация автора

Посмотри это:

    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. Консоль покажет «добавить», а затем «мул»! Обычный способ оценки.

Автор: Apurva Singh Размещён: 07.06.2017 08:17

1 плюс

3891 Репутация автора

Уже есть много фантастических ответов на этот вопрос в Интернете. Я напишу сборник нескольких объяснений и примеров, которые я собрал по этой теме, на случай, если кто-то может найти это полезным

ВВЕДЕНИЕ

по стоимости (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*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

От: 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
}
Автор: Ignacio Alorre Размещён: 13.07.2017 06:05

2 плюса

22028 Репутация автора

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

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

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

Пример 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)
 }
Автор: Ram Ghadiyaram Размещён: 30.07.2017 07:24

1 плюс

78168 Репутация автора

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

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

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

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

Автор: Maroun Размещён: 28.12.2017 08:54

2 плюса

38 Репутация автора

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

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

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

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

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

Автор: Nijanthan Vijayakumar Размещён: 10.02.2018 12:11

2 плюса

41 Репутация автора

Вот быстрый пример, который я кодировал, чтобы помочь моему коллеге, который в настоящее время проходит курс 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 Размещён: 27.12.2018 06:00
Вопросы из категории :
32x32