Предположим, у нас есть функция
square(x) = x * x
и мы хотим оценить square(1+2)
.
В по стоимости мы делаем
square(1+2)
square(3)
3*3
9
В по имени мы делаем
square(1+2)
(1+2)*(1+2)
3*(1+2)
3*3
9
Обратите внимание, что поскольку мы используем аргумент дважды, мы оцениваем его дважды. Это было бы расточительно, если бы оценка аргумента заняла много времени. Это проблема, которая устраняется по требованию.
В по требованию мы делаем что-то вроде следующего:
square(1+2)
let x = 1+2 in x*x
let x = 3 in x*x
3*3
9
На шаге 2 вместо копирования аргумента (как в вызове по имени) мы даем ему имя. Затем на шаге 3, когда мы замечаем, что нам нужно значение x
, мы оцениваем выражение для x
. Только тогда мы заменим.
Кстати, если выражение аргумента приводит к чему-то более сложному, например, к замыканию, может быть больше перемешивания let
с, чтобы исключить возможность копирования. Формальные правила несколько сложны для записи.
Обратите внимание, что нам "нужны" значения для аргументов примитивных операций, таких как +
и *
, но для других функций мы используем подход "name, wait and see". Мы бы сказали, что примитивные арифметические операции являются «строгими». Это зависит от языка, но обычно примитивные операции строги.
Также обратите внимание, что «оценка» по-прежнему означает уменьшение до значения. Вызов функции всегда возвращает значение, а не выражение. (Один из других ответов получил это неправильно.) OTOH, ленивые языки обычно имеют ленивые конструкторы данных, которые могут иметь компоненты, которые оцениваются по необходимости, то есть, когда извлекаются. Таким образом, вы можете получить «бесконечный» список - возвращаемое вами значение представляет собой ленивую структуру данных. Но вызов по требованию против вызова по значению является отдельной проблемой от ленивых против строгих структур данных. Схема имеет ленивые конструкторы данных (потоки), хотя поскольку Схема является вызовом по значению, конструкторы являются синтаксическими формами, а не обычными функциями. И Haskell - это вызов по имени, но у него есть способы определения строгих типов данных.
Если это помогает думать о реализациях, то одна реализация call-by- name заключается в переносе каждого аргумента в thunk; когда аргумент необходим, вы вызываете thunk и используете значение. Одна реализация call-by- need похожа, но эффект запоминается; он выполняет вычисление только один раз, затем сохраняет его и после этого просто возвращает сохраненный ответ.