Когда использовать позывные и позывные?

Я понимаю базовую концепцию call-by-name и call-by-value, и я также рассмотрел несколько примеров. Тем не менее, я не очень понимаю, когда использовать call-by-name. Каким будет сценарий в реальном мире, когда вызов по имени будет иметь значительное преимущество или прирост производительности по сравнению с другим типом вызова? Каким должен быть правильный подход к мышлению для выбора типа вызова при разработке метода?

Ответ 1

Есть много мест, где call-by-name может повысить производительность или даже правильность.

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

trait Logger {
  def info(msg: => String)
  def warn(msg: => String)
  def error(msg: => String)
}

И затем используется так:

logger.info("Time spent on X: " + computeTimeSpent)

Если метод info ничего не делает (потому что, скажем, уровень ведения журнала был настроен выше этого), то computeTimeSpent никогда не будет вызван, экономя время. Это часто случается с регистраторами, где часто видят манипуляции с строкой, которые могут быть дорогими по сравнению с выполняемыми задачами.

Пример правильности: логические операторы.

Вероятно, вы видели такой код:

if (ref != null && ref.isSomething)

Скажите, что вы объявили метод && следующим образом:

trait Boolean {
  def &&(other: Boolean): Boolean
}

тогда всякий раз, когда ref есть null, вы получите сообщение об ошибке, потому что isSomething будет вызван в ссылку null перед передачей &&. По этой причине фактическое объявление:

trait Boolean {
  def &&(other: => Boolean): Boolean =
    if (this) this else other
}

Таким образом, можно действительно задаться вопросом, когда использовать call-by-value. Фактически, на языке программирования Haskell все работает так же, как работает по имени (похоже, но не одинаково).

Есть хорошие причины не использовать call-by-name: он медленнее, он создает больше классов (что означает, что программа занимает больше времени для загрузки), она потребляет больше памяти, и она достаточно различна, что многие из них имеют трудные рассуждения о он.

Ответ 2

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

Чтобы увидеть разницу, рассмотрите этот пример (полностью нефункциональное программирование с только побочными эффектами;)). Предположим, вы хотите создать функцию, которая измеряет, сколько времени займет какая-то операция. Вы можете сделать это с помощью вызова по имени:

def measure(action: => Unit) = {
    println("Starting to measure time")
    val startTime = System.nanoTime
    action
    val endTime = System.nanoTime
    println("Operation took "+(endTime-startTime)+" ns")
}

measure {
    println("Will now sleep a little")
    Thread.sleep(1000)
}

Вы получите результат (YMMV):

Starting to measure time
Will now sleep a little
Operation took 1000167919 ns

Но если вы меняете только подпись measure на measure(action: Unit), поэтому она использует pass-by-value, результатом будет:

Will now sleep a little
Starting to measure time
Operation took 1760 ns

Как вы можете видеть, action оценивается до начала measure, а также прошедшее время близко к 0 из-за действия, которое уже было выполнено до вызова метода.

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

Ответ 3

Простым способом, который может быть объяснено, является

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

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

Ответ 4

Если параметр "один за другим" используется более одного раза в функции, параметр оценивается более одного раза.

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