Кажется, что ленивая оценка выражений может привести к тому, что программист потеряет контроль над порядком выполнения кода. У меня возникают проблемы с пониманием, почему это было бы приемлемо или желательно программистом.
Как можно использовать эту парадигму для создания предсказуемого программного обеспечения, которое работает как задумано, когда у нас нет гарантии, когда и где выражение будет оценено?
functional-programming
haskell
akashchandrakar
источник
источник
head . sort
естьO(n)
сложность из-за лени (нетO(n log n)
). См. Ленивая Оценка и Сложность Времени .Ответы:
Многие ответы относятся к таким вещам, как бесконечные списки и выигрыш в производительности от неоцененных частей вычислений, но этого не хватает большей мотивации для лени: модульности .
Классический аргумент изложен в широко цитируемой статье Джона Хьюза «Почему функциональное программирование имеет значение» (ссылка в формате PDF) . Ключевым примером в этой статье (раздел 5) является игра в крестики-нолики с использованием алгоритма поиска альфа-бета. Ключевой момент (стр. 9):
Программа Tic-Tac-Toe может быть написана как функция, которая генерирует все игровое дерево, начиная с заданной позиции, и отдельная функция, которая его использует. Во время выполнения это по сути не генерирует целое дерево игры, а только те его части, которые действительно нужны потребителю. Мы можем изменить порядок и комбинацию, в которой производятся альтернативы, путем изменения потребителя; нет необходимости менять генератор вообще.
На нетерпеливом языке вы не можете написать это таким образом, потому что вы, вероятно, потратили бы слишком много времени и памяти на создание дерева. Таким образом, вы в конечном итоге либо:
источник
Когда выражение не имеет побочных эффектов, порядок вычисления выражений не влияет на их значение, поэтому порядок не влияет на поведение программы. Так что поведение совершенно предсказуемо.
Теперь побочные эффекты - это другое дело. Если побочные эффекты могут возникать в любом порядке, поведение программы действительно будет непредсказуемым. Но на самом деле это не так. Ленивые языки, такие как Haskell, делают его прозрачным для ссылок, то есть следят за тем, чтобы порядок, в котором вычисляются выражения, никогда не влиял на их результат. В Haskell это достигается путем принудительного выполнения всех операций с видимыми пользователем побочными эффектами внутри монады ввода-вывода. Это гарантирует, что все побочные эффекты происходят именно в том порядке, который вы ожидаете.
источник
Если вы знакомы с базами данных, очень частый способ обработки данных:
select * from foobar
Вы не контролируете, как генерируется результат и каким образом (индексы? Полное сканирование таблицы?) Или когда (должны ли все данные генерироваться сразу или постепенно при запросе?). Все, что вы знаете: если есть больше данных, вы получите их, когда попросите.
Ленивая оценка довольно близка к тому же. Скажем, у вас есть бесконечный список, определенный как т.е. последовательность Фибоначчи - если вам нужно пять чисел, вы получите пять чисел; если вам нужно 1000, вы получите 1000. Хитрость заключается в том, что среда выполнения знает, что и где и когда предоставлять. Это очень, очень удобно.
(Java-программисты могут эмулировать это поведение с помощью итераторов - другие языки могут иметь что-то похожее)
источник
Collection2.filter()
(как и другие методы этого класса) в значительной степени реализована ленивая оценка: результат «выглядит» как обычныйCollection
, но порядок выполнения может быть неинтуитивным (или, по крайней мере, неочевидным). Кроме того,yield
в Python есть (и похожая функция в C #, имя которой я не помню), которая даже ближе к поддержке отложенных вычислений, чем обычный Iterator.Попробуйте запросить в вашей базе данных список первых 2000 пользователей, чьи имена начинаются с «Ab» и старше 20 лет. Также они должны быть мужчинами.
Вот небольшая диаграмма.
Как вы можете видеть из этого ужасного ужасного взаимодействия, «база данных» фактически ничего не делает, пока не будет готова справиться со всеми условиями. Это ленивая загрузка результатов на каждом шаге и применение новых условий каждый раз.
В противоположность получению первых 2000 пользователей, возвращая их, фильтруя их по «Ab», возвращая их, фильтруя их более 20, возвращая их, и фильтруя для мужчин и, наконец, возвращая их.
Ленивая загрузка в двух словах.
источник
Дизайнеру не нужно заботиться о порядке, в котором оцениваются выражения, если результат одинаков. Откладывая оценку, можно избежать оценки некоторых выражений в целом, что экономит время.
Вы можете увидеть ту же идею при работе на более низком уровне: многие микропроцессоры могут выполнять инструкции не по порядку, что позволяет им более эффективно использовать свои различные исполнительные блоки. Ключ заключается в том, что они смотрят на зависимости между инструкциями и избегают переупорядочения, где это могло бы изменить результат.
источник
Есть несколько аргументов для ленивой оценки, я думаю, это убедительно
Модульность С ленивой оценкой вы можете разбить код на части. Например, предположим, у вас есть проблема «найти первые десять взаимных ссылок элементов в списке списка, чтобы их было меньше 1». В чем-то вроде Хаскелла вы можете написать
но это просто неверно в строгом языке, так как если вы дадите его,
[2,3,4,5,6,7,8,9,10,11,12,0]
вы будете делить на ноль. Посмотрите ответ sacundim, почему это здорово на практикеБольше вещей работает Строго (каламбур) больше программ завершается не строгой оценкой, чем строгой оценкой. Если ваша программа завершается с «энергичной» оценочной стратегией, она заканчивается с «ленивой», но обратное неверно. В качестве конкретных примеров этого явления вы получаете такие вещи, как бесконечные структуры данных (которые на самом деле просто классные). Другие программы работают на ленивых языках.
Оптимальность Оценка по требованию асимптотически оптимальна по времени. Хотя основные ленивые языки (по сути, это Haskell и Haskell) не обещают вызов по требованию, вы можете более или менее рассчитывать на модель оптимальной стоимости. Анализаторы строгости (и спекулятивная оценка) снижают накладные расходы на практике. Пространство - более сложный вопрос.
Принудительная чистка с использованием ленивых вычислений делает недисциплинированную работу с побочными эффектами полной болью, потому что, как вы выразились, программист теряет контроль. Это хорошая вещь. Прозрачность ссылок значительно упрощает программирование, рефракторинг и рассуждение о программах. Строгие языки просто неизбежно уступают давлению нечистых кусочков - чему-то, что Haskell и Clean прекрасно сопротивлялись. Это не означает, что побочные эффекты всегда являются злом, но контролировать их настолько полезно, что одной этой причины достаточно, чтобы использовать ленивые языки.
источник
Предположим, у вас есть много дорогих вычислений, но вы не знаете, какие из них действительно понадобятся или в каком порядке. Вы можете добавить сложный протокол mother-may-i, чтобы заставить потребителя выяснить, что доступно, и инициировать вычисления, которые еще не сделаны. Или вы можете просто предоставить интерфейс, который действует так, как будто все расчеты выполнены.
Также предположим, что у вас есть бесконечный результат. Множество всех простых чисел, например. Очевидно, что вы не можете рассчитать набор заранее, поэтому любая операция в области простых чисел должна быть ленивой.
источник
с ленивой оценкой вы не теряете контроль над выполнением кода, это все еще абсолютно детерминировано. Хотя с этим трудно привыкнуть.
Ленивая оценка полезна, потому что это способ сокращения лямбда-члена, который заканчивается в некоторых случаях, когда тщательная оценка потерпит неудачу, но не наоборот. Это включает в себя: 1) когда вам нужно связать результат вычисления до того, как вы фактически выполните вычисление, например, когда вы строите структуру циклического графа, но хотите сделать это в функциональном стиле 2) когда вы определяете бесконечную структуру данных, но работаете с этой структурой использовать только часть структуры данных.
источник