Haskell: Чем отличаются нестрогие и ленивые? - PullRequest
63 голосов
/ 22 августа 2011

Я часто читаю, что lazy - это не то же самое, что не строгий , но мне трудно понять разницу.Они кажутся взаимозаменяемыми, но я понимаю, что они имеют разные значения.Буду признателен за помощь в понимании разницы.

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

Non-Strict Def:

Функция f называется строгой, если при применении к неопределенному выражению онатакже не в состоянии прекратить.Другими словами, f строгое, если значение f bot равно | .Для большинства языков программирования все функции являются строгими.Но это не так в Хаскеле.В качестве простого примера рассмотрим const1, функцию константы 1, определяемую следующим образом:

const1 x = 1

Значение бота const1 в Haskell равно 1. С практической точки зрения, поскольку const1 этого не делает "нужно "значение его аргумента, он никогда не пытается его оценить, и, следовательно, никогда не попадает в непрерывное вычисление.По этой причине нестрогие функции также называются «ленивыми функциями» и, как говорят, оценивают свои аргументы «лениво» или «по необходимости».

- A Нежное введение вHaskell: Функции

Мне очень нравится это определение.Кажется, это лучшее, что я мог найти для строгого понимания.const1 x = 1 также ленив?

Нестрогость означает, что сокращение (математический термин для оценки) происходит извне,

, так что если у вас есть (a + (b c)) затем сначала вы уменьшаете +, затем вы уменьшаете внутреннее (b c).

- Haskell Wiki: Lavy vs nonsrict

Haskell Wiki действительно смущает меня.Я понимаю, что они говорят о порядке, но я не вижу, как (a+(b*c)) будет оценивать не строго, если он был передан _|_?

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

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

- Википедия: Стратегия оценки

Ленивая защита:

Ленивая оценка, с другой стороны, означает оценку выражения только тогда, когда необходимы его результаты (обратите внимание на переход от «сокращения» к «оценке»).Поэтому, когда механизм оценки видит выражение, он создает структуру данных thunk, содержащую любые значения, необходимые для оценки выражения, плюс указатель на само выражение.Когда результат действительно необходим, механизм оценки вызывает выражение, а затем заменяет блок на результат для дальнейшего использования....

Очевидно, что между выражением thunk и частично оцененным выражением существует сильная связь.Следовательно, в большинстве случаев термины «ленивый» и «нестрогий» являются синонимами.Но не совсем.

- Haskell Wiki: Lavy против нестрогих

Это похоже на конкретный ответ на Haskell. Я полагаю, что ленивый означает гром, а не строгий означает частичную оценку. Это сравнение слишком упрощено? Означает ли ленивый всегда значительный разброс, а нестрогий всегда означает частичную оценку.

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

- Википедия: Ленивая оценка

Императивные примеры

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

Короткое замыкание

f1() || f2()

C #, Python и другие языки с "yield"

public static IEnumerable Power(int number, int exponent)
{
    int counter = 0;
    int result = 1;
    while (counter++ < exponent)
    {
        result = result * number;
        yield return result;
    }
}

- MSDN: выход (c #)

Callbacks

int f1() { return 1;}
int f2() { return 2;}

int lazy(int (*cb1)(), int (*cb2)() , int x) {
    if (x == 0)
        return cb1();
    else
        return cb2();
}

int eager(int e1, int e2, int x) {
    if (x == 0)
         return e1;
    else
         return e2;
}

lazy(f1, f2, x);
eager(f1(), f2(), x);

Вопросы * * 1111 Я знаю, что ответ прямо передо мной со всеми этими ресурсами, но я не могу понять его. Кажется, что определение слишком легко отклонить как подразумеваемое или очевидное. Я знаю, у меня много вопросов. Не стесняйтесь отвечать на любые вопросы, которые вы считаете актуальными. Я добавил эти вопросы для обсуждения. const1 x = 1 тоже ленивый? Как оценка из "внутреннего" не строгая? Это потому, что внутрь допускается сокращение ненужных выражений, как в const1 x = 1? Сокращения, кажется, соответствуют определению ленивых. означает ли ленивый всегда thunks и нестрогий всегда означает частичная оценка ? Это просто обобщение? Являются ли следующие императивные понятия ленивыми, нестрогими, обоими или нет? Короткое замыкание Использование доходности Передача обратных вызовов, чтобы задержать или избежать исполнения Является ли ленивым подмножеством нестрогих или наоборот, или они взаимоисключающие. Например, возможно ли быть нестрогим , не будучи ленивым , или ленивым , не будучи нестрогим ? Достигнут ли лукавство несправедливости Хаскеля? Спасибо, ТАК!

Ответы [ 6 ]

57 голосов
/ 22 августа 2011

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

Нестрогий относится к семантике : математическому значению выражения. Мир, к которому относится нестрогий, не имеет понятия времени работы функции, потребления памяти или даже компьютера. Он просто говорит о том, какие значения в доменной карте и какие значения в кодомене. В частности, строгая функция должна отображать значение & perp; ("bottom" - см. ссылку на семантику выше для получения дополнительной информации об этом) на & perp ;; не строгая функция не может этого делать.

Ленивый относится к рабочему поведению: способ выполнения кода на реальном компьютере. Большинство программистов думают о программах оперативно, так что это, вероятно, то, что вы думаете. Ленивая оценка относится к реализации с использованием thunks - указателей на код, которые заменяются значением при первом запуске. Обратите внимание на несемантические слова: «указатель», «первый раз», «выполнено».

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

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

16 голосов
/ 22 августа 2011

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

Оптимистическая оценка означает, что даже если подвыражение может не потребоваться для оценки суперэкспрессии, мы все равно оценим некоторые из них с использованием некоторой эвристики. Если подвыражение не заканчивается достаточно быстро, мы приостанавливаем его оценку до тех пор, пока оно действительно не понадобится. Это дает нам преимущество перед отложенной оценкой, если подвыражение необходимо позже, поскольку нам не нужно генерировать thunk. С другой стороны, мы не теряем слишком много, если выражение не заканчивается, поскольку мы можем прервать его достаточно быстро.

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

6 голосов
/ 22 августа 2011

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

Одно существенное отличие - , когда оцениваются термины . Для этого существует множество стратегий, начиная от «как можно скорее» до «только в последний момент». Термин нетерпеливая оценка иногда используется для стратегий, склоняющихся к первому, в то время как ленивая оценка правильно относится к семейству стратегий, сильно склоняющихся к последним. Различие между «ленивой оценкой» и соответствующими стратегиями, как правило, заключается в том, когда и где сохраняется результат оценки чего-либо, а не отбрасывается в сторону. На этом основана знакомая техника запоминания в Haskell - присвоение имени структуре данных и индексация в ней. Напротив, язык, который просто объединяет выражения друг с другом (как в оценке «по имени»), может не поддерживать это.

Другое различие - , какие термины оцениваются , варьируясь от «абсолютно всего» до «как можно меньше». Поскольку любое значение, фактически используемое для вычисления окончательного результата, не может быть проигнорировано, разница здесь заключается в том, сколько лишних терминов оценивается. Помимо сокращения объема работы, которую должна выполнить программа, игнорирование неиспользуемых терминов означает, что возникшие ошибки не возникнут. Когда проводится различие, строгость относится к свойству оценки всего рассматриваемого (например, в случае строгой функции это означает термины, к которым она применяется. Она не делает обязательно означает подвыражения внутри аргументов), в то время как не является строгим означает оценку только некоторых вещей (либо путем задержки оценки, либо путем полного отказа от терминов).

Должно быть легко увидеть, как они взаимодействуют сложным образом; решения вовсе не являются ортогональными, поскольку крайности, как правило, несовместимы. Например:

  • Очень строгая оценка исключает некоторое количество рвения; если вы не знаете, понадобится ли термин, вы еще не можете оценить его.

  • Очень строгая оценка делает нерешительность несколько неактуальной; если вы все оцениваете, решение , когда сделать это, будет менее значимым.

Альтернативные определения все же существуют. Например, по крайней мере в Haskell, «строгая функция» часто определяется как функция, которая достаточно аргументирует свои аргументы, чтобы функция оценивалась как | всякий раз, когда любой аргумент делает; обратите внимание, что согласно этому определению id является строгим (в тривиальном смысле), потому что форсирование результата id x будет иметь точно такое же поведение, что и форсирование x в одиночку.

5 голосов
/ 22 августа 2011

Это началось как обновление, но начало становиться длинным.

Laziness / Call-by-need является запомненной версией call-by-name, где, если аргумент функции оценивается, это значение сохраняется для последующего использования. В «чистом» (безэффектном) режиме это приводит к тем же результатам, что и вызов по имени; когда аргумент функции используется два или более раз, вызов по потребности почти всегда быстрее.
Императивный пример - Видимо, это возможно. Есть интересная статья на Lazy Imperative Languages ​​. Там написано, что есть два метода. Один требует замыканий, второй использует сокращения графов. Поскольку C не поддерживает замыкания, вам необходимо явно передать аргумент вашему итератору. Вы можете обернуть структуру карты и, если значение не существует, рассчитать его, иначе вернуть значение.
Примечание : Haskell реализует это с помощью «указателей на код, которые заменяются значением при первом запуске» - luqui.
Это не строгий вызов по имени, но с обменом / запоминанием результатов.

Call-By-Name - При оценке call-by-name аргументы функции не оцениваются до вызова функции, а скорее подставляются непосредственно в тело функции (с использованием замены, избегающей захвата), а затем оставляется для оценки всякий раз, когда они появляются в функции. Если аргумент не используется в теле функции, аргумент никогда не оценивается; если он используется несколько раз, он переоценивается каждый раз, когда появляется.
Императивный пример: обратные вызовы
Примечание: Это не является строгим, поскольку позволяет избежать оценки, если не используется.

Не строгий = При нестрогом вычислении аргументы функции не оцениваются, если они фактически не используются при оценке тела функции.
Императивный пример : короткое замыкание
Примечание : _ | _ является способом проверки, если функция не является строгой

Так что функция может быть не строгой, но не ленивой. Функция, которая ленива, всегда не является строгой. Call-By-Need частично определяется Call-By-Name , который частично определяется Non-Strict

Отрывок из «Ленивые императивные языки»

2,1. НЕСТРОЧНАЯ СЕМАНТИКА VS. Ленивая оценка Мы должны сначала уточнить различие между «не строгой семантикой» и «ленивой оценкой». Не строгая семантика - это те, которые указывают, что выражение не является оценивается до тех пор, пока это не понадобится примитивной операции. Может быть различные типы не строгой семантики. Например, не строгие вызовы процедур не оценивают аргументы, пока их значения требуется. Конструкторы данных могут иметь не строгую семантику, в которой составные данные собраны из неоцененных частей Ленивая оценка, также называется отложенной оценкой, это метод, обычно используемый для реализовать не строгую семантику. В разделе 4 два метода обычно используются для реализации ленивых оценки очень кратко резюмированы.

CALL BY VALUE, CALL BY LAZY И CALL BY NAME «Вызов по значению» - это общее имя, используемое для вызовов процедур со строгой семантикой. В вызове по значению языка каждый аргумент вызова процедуры оценивается до вызова процедуры; значение затем передается процедура или вложение выражения. Другое имя для вызова по значению «Стремительная» оценка. Вызов по значению также известен как «аппликативный порядок» оценка, потому что все аргументы оцениваются до того, как функция применяется к ним. «Звоните ленивым» (используя терминологию Уильяма Клингера в [8]) это имя, данное вызовам процедур, которые не являются строгими семантика. На языках с вызовом по ленивым вызовам процедур, аргументы не оцениваются, прежде чем подставляются в процедуру тело. Звонок по ленивой оценке также известен как «нормальный»оценка порядка, из-за порядка (слева направо справа) оценки выражения. «Звонок по имени» является Конкретная реализация вызова по ленивому, используемая в Алголе-60 [18]. Разработчики Algol-60 предполагали, что параметры по имени физически подставляется в тело процедуры, заключенное в круглые скобки и с подходящими изменениями имени, чтобы избежать конфликтов, прежде чем тело было оценены.

ПОЗВОНИТЕ Ленивый VS. ЗВОНОК ПО НУЖНО Звонок по необходимости является продление звонка на ленивого, вызвано наблюдением, что ленивый Оценка может быть оптимизирована путем запоминания значения данного задержанное выражение, однажды принудительное, так что значение не должно быть пересчитывается, если это необходимо снова. Звоните по необходимости оценки, следовательно, расширяет вызов ленивыми методами, используя запоминание, чтобы избежать необходимость повторной оценки. Фридман и Мудрый были среди самые ранние призывы к оценке потребностей, предлагая "самоубийство суспензии ", которые самоуничтожались, когда их впервые оценивали, заменяя себя своими ценностями.

0 голосов
/ 07 апреля 2019

Насколько я понимаю, «нестрогая» означает попытку уменьшить рабочую нагрузку на достижение завершения при меньшем объеме работы.

В то время как «ленивая оценка» и тому подобное пытаютсяуменьшить общую рабочую нагрузку на , избегая полного завершения (надеюсь, навсегда).

из ваших примеров ...

f1() || f2()

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

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

int f1() { return 1;}
int f2() { return 2;}

int lazy(int (*cb1)(), int (*cb2)() , int x) {
    if (x == 0)
        return cb1();
    else
        return cb2();
}

int eager(int e1, int e2, int x) {
    if (x == 0)
         return e1;
    else
         return e2;
}

lazy(f1, f2, x);
eager(f1(), f2(), x);
0 голосов
/ 22 августа 2011

Если мы говорим на общем жаргоне информатики, то "ленивый" и "нестрогий", как правило, являются синонимами - они обозначают одну и ту же общую идею, которая выражается по-разному в разных ситуациях.1002 * Однако в данном конкретном специализированном контексте они могли приобретать разные технические значения.Я не думаю, что вы можете сказать что-то точное и универсальное о том, какая разница между «ленивым» и «нестрогим» может быть в ситуации, когда есть разница.

...