Почему большинство языков программирования используют энергичную оценку для аргументов, передаваемых в функцию? - PullRequest
6 голосов
/ 10 января 2012

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

Мне кажется, что было бы гораздо разумнее оценивать аргументы только тогда, когда функция их использует, то есть лениво. Это имеет больше смысла для меня, потому что кажется, что это принесет выигрыш в производительности: зачем оценивать вещи, которые даже не нужны?

Более того, предположим, что вы хотели реализовать функцию if, которая принимает логическое значение, и объект, который должен возвращаться, если логическое значение истинно, и другой объект, который должен возвращаться, если логическое значение равно false:

object if(bool condition, object valueIfTrue, object valueIfFalse) {
  if(condition) return valueIfTrue;
  return valueIfFalse;
}

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

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

Ответы [ 5 ]

5 голосов
/ 10 января 2012

Есть несколько причин, по которым я с нетерпением ждал оценки, обе из которых важны:

  1. Стремление к оценке означает, что побочные эффекты возникают немедленно и всегда.Если вы используете ленивую оценку, вы не можете полагаться на побочные эффекты того, что вы сделали ранее, чтобы они вступили в силу.
  2. Ленивые вычисления приносят с собой определенное количество памяти.Обычно для хранения результата вычисления требуется гораздо меньше памяти, чем для хранения thunk , который описывает вычисление.Это может привести к использованию слишком большого количества памяти (т. Е. Между временем и памятью) и, что более важно, труднее определить характеристики памяти программы / алгоритма.

Ленивая оценка может бытьмощный инструмент, но не без затрат.Чисто функциональные языки имеют тенденцию избегать проблемы # 1, потому что они не имеют побочных эффектов (в общем), но все еще время от времени укушаются проблемой # 2.Языки, которые допускают отложенную оценку (макросы LISP являются формой этого, хотя и не то же самое, что ленивая оценка), могут иметь лучшее из обоих миров, но за счет больших усилий со стороны программиста.

3 голосов
/ 10 января 2012

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

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

Как говорится, ничто не мешает вам использовать обе стратегии в разных местах. Я не удивлюсь, если гибридный подход используется в нетривиальных программах.

3 голосов
/ 10 января 2012

Вариант 1 - загрузить все аргументы в регистры, вызвать функцию

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

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

2 голосов
/ 10 января 2012

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

0 голосов
/ 12 января 2012

В меловом периоде было несколько языков, которые делали это. СНОБОЛ, например. У ALGOL 68 была возможность «звонить по имени», которая делала что-то вроде этого. И C (как и многие его производные) делает это в одной очень специфической ситуации, которую он описывает как «короткое замыкание» логического выражения. В целом, это почти всегда источник большего количества путаницы и ошибок, чем включения питания.

...