Что такое звонок?
Я хочу знать, что такое позывной.
Хотя я искал в Википедии и нашел это здесь: http://en.wikipedia.org/wiki/Evaluation_strategy,
но не мог понять правильно.
Если кто-нибудь может объяснить пример и указать разницу с позором, это будет большой помощью.
Ответы
Ответ 1
Представьте себе функцию:
fun add(a, b) {
return a + b
}
И тогда мы называем это:
add(3 * 2, 4 / 2)
В языке, называемом по имени, это будет оцениваться так:
-
a = 3 * 2 = 6
-
b = 4 / 2 = 2
-
return a + b = 6 + 2 = 8
Функция вернет значение 8
.
В вызове по требованию (также называемом ленивым языком) это оценивается так:
-
a = 3 * 2
-
b = 4 / 2
-
return a + b = 3 * 2 + 4 / 2
Функция вернет выражение 3 * 2 + 4 / 2
. До сих пор почти нет вычислительных ресурсов. Все выражение будет вычисляться только в том случае, если его значение необходимо - скажем, мы хотели напечатать результат.
Почему это полезно? Две причины. Во-первых, если вы случайно включили мертвый код, он не взвешивает вашу программу и, следовательно, может быть намного более эффективным. Во-вторых, это позволяет делать очень классные вещи, например, эффективно вычислять с помощью бесконечных списков:
fun takeFirstThree(list) {
return [list[0], list[1], list[2]]
}
takeFirstThree([0 ... infinity])
Язык по имени будет висел там, пытаясь создать список от 0 до бесконечности. Ленточный язык просто вернет [0,1,2]
.
Ответ 2
Предположим, что мы имеем функцию
square(x) = x * x
и мы хотим оценить square(1+2)
.
В call-by-value мы выполняем
-
square(1+2)
-
square(3)
-
3*3
-
9
В call-by-name мы выполняем
-
square(1+2)
-
(1+2)*(1+2)
-
3*(1+2)
-
3*3
-
9
Обратите внимание: поскольку мы дважды используем аргумент, мы его дважды оцениваем. Это было бы расточительно, если бы оценка аргументов длилась долго. Это проблема, связанная с необходимостью исправления.
В call-by-need мы делаем что-то вроде следующего:
-
square(1+2)
-
let x = 1+2 in x*x
-
let x = 3 in x*x
-
3*3
-
9
На шаге 2 вместо копирования аргумента (например, по вызову по имени) мы даем ему имя. Затем на шаге 3, заметив, что нам нужно значение x
, мы вычисляем выражение для x
. Только тогда мы подставим.
Кстати, если выражение аргумента произвело что-то более сложное, например закрытие, может быть больше перетасовки let
вокруг, чтобы исключить возможность копирования. Формальные правила несколько сложны для записи.
Обратите внимание, что нам нужны "значения" для аргументов для примитивных операций, таких как +
и *
, но для других функций мы используем подход "имя, подождать и видеть". Мы бы сказали, что примитивные арифметические операции являются "строгими". Это зависит от языка, но обычно большинство примитивных операций строги.
Обратите внимание также, что "оценка" по-прежнему означает снижение до значения. Вызов функции всегда возвращает значение, а не выражение. (Один из других ответов ошибся.) OTOH, у ленивых языков обычно есть ленивые конструкторы данных, которые могут иметь компоненты, которые оцениваются по необходимости, т.е. При извлечении. То, как вы можете иметь "бесконечный" список --- возвращаемое вами значение - это ленивая структура данных. Но call-by-need vs call-by-value - это отдельная проблема от ленивых и строгих структур данных. Схема имеет ленивые конструкторы данных (потоки), хотя, поскольку схема является позывным, конструкторы представляют собой синтаксические формы, а не обычные функции. И Haskell - это вызов по имени, но он имеет способы определения строгих типов данных.
Если это помогает подумать о реализациях, то одна реализация call-by- name заключается в том, чтобы обернуть каждый аргумент в thunk; когда аргумент нужен, вы вызываете thunk и используете значение. Одна реализация call-by- нужна аналогична, но thunk memoizing; он запускает вычисление только один раз, затем он сохраняет его и после этого возвращает сохраненный ответ.
Ответ 3
Простой, но наглядный пример:
function choose(cond, arg1, arg2) {
if (cond)
do_something(arg1);
else
do_something(arg2);
}
choose(true, 7*0, 7/0);
Теперь скажем, что мы используем стратегию оценки с нетерпением, тогда он будет рассчитывать как 7*0
, так и 7/0
с нетерпением. Если это ленивая оцененная стратегия (по требованию), она просто отправит выражения 7*0
и 7/0
через функцию, не оценивая их.
Разница? вы ожидали бы выполнить do_something(0)
, потому что используется первый аргумент, хотя он фактически зависит от стратегии оценки:
Если язык оценивает с нетерпением, то он, как указано, сначала оценит 7*0
и 7/0
, а что 7/0
? Ошибка разделения по нуле.
Но если стратегия оценки будет ленивой, она увидит, что ей не нужно вычислять деление, она будет называть do_something(0)
, как и ожидалось, без ошибок.
В этом примере ленивая стратегия оценки может спасти выполнение от ошибок. Аналогичным образом, он может сохранить выполнение от ненужной оценки, которую он не будет использовать (так же, как здесь не использовался 7/0
).