Я только что узнал, как работает ленивая оценка, и мне было интересно: почему ленивая оценка не применяется в каждом производимом в настоящее время программном обеспечении? Почему по-прежнему использовать нетерпеливые оценки?
Я только что узнал, как работает ленивая оценка, и мне было интересно: почему ленивая оценка не применяется в каждом производимом в настоящее время программном обеспечении? Почему по-прежнему использовать нетерпеливые оценки?
Ответы:
Ленивая оценка требует затрат на ведение бухгалтерского учета - вы должны знать, оценивались ли они еще и тому подобное. Жесткая оценка всегда оценивается, поэтому вам не нужно знать. Это особенно верно в параллельных контекстах.
Во-вторых, легко преобразовать готовую оценку в ленивую оценку, упаковав ее в функциональный объект для последующего вызова, если вы того пожелаете.
В-третьих, ленивая оценка подразумевает потерю контроля. Что если я лениво оценил чтение файла с диска? Или получать время? Это не приемлемо.
Стремительная оценка может быть более эффективной и более управляемой, и тривиально преобразуется в ленивую оценку. Зачем вам ленивая оценка?
readFile
- именно то , что мне нужно. Кроме того, переход от ленивых к нетерпеливым оценкам так же тривиален.
head [1 ..]
дает вам на жадно оцененном чистом языке, потому что в Haskell это дает 1
?
Главным образом потому, что ленивый код и состояние могут плохо смешиваться и приводить к трудностям при поиске ошибок. Если состояние зависимого объекта изменяется, значение вашего ленивого объекта может быть неправильным при оценке. Намного лучше, чтобы программист явно кодировал объект, чтобы быть ленивым, когда он / она знает, что ситуация является подходящей.
С другой стороны, Haskell использует оценку Lazy для всего. Это возможно, потому что это функциональный язык и не использует состояние (за исключением нескольких исключительных случаев, когда они четко обозначены)
set!
ленивого интерпретатора Scheme. > :(
Ленивые оценки не всегда лучше.
Преимущества ленивых вычислений для производительности могут быть значительными, но нетрудно избежать большинства ненужных оценок в нетерпеливых средах - конечно, ленивый делает их простыми и полными, но редко является ненужной оценкой в коде серьезной проблемой.
Хорошая вещь о ленивой оценке - это когда вы можете написать более четкий код; получение 10-го простого числа путем фильтрации списка бесконечных натуральных чисел и получения 10-го элемента этого списка является одним из наиболее кратких и ясных способов продолжения: (псевдокод)
let numbers = [1,2...]
fun is_prime x = none (map (y-> x mod y == 0) [2..x-1])
let primes = filter is_prime numbers
let tenth_prime = first (take primes 10)
Я полагаю, что было бы довольно трудно выразить вещи так кратко без лени.
Но лень не ответ на все вопросы. Начнем с того, что ленивость нельзя применять прозрачно при наличии состояния, и я считаю, что состояние не может быть обнаружено автоматически (если вы не работаете, скажем, в Haskell, когда состояние достаточно явное). Таким образом, в большинстве языков ленивость должна выполняться вручную, что делает вещи менее ясными и, таким образом, устраняет одно из больших преимуществ ленивого eval.
Кроме того, у ленивости есть недостатки производительности, поскольку это влечет за собой значительные накладные расходы на хранение необработанных выражений; они используют память и работают медленнее, чем простые значения. Нередко бывает, что вам приходится набирать код, потому что ленивая версия очень медленная, и иногда трудно понять, почему она работает.
Поскольку это имеет место, не существует абсолютно лучшей стратегии. Lazy - это замечательно, если вы можете писать более качественный код, используя преимущества бесконечных структур данных или других стратегий, которые он позволяет вам использовать, но его легче оптимизировать.
Вот краткое сравнение плюсов и минусов нетерпеливой и ленивой оценки:
Жадная оценка:
Потенциальные накладные расходы на ненужную оценку вещей.
Беспрепятственный, быстрая оценка.
Ленивая оценка:
Нет ненужной оценки.
Накладные расходы при каждом использовании значения.
Итак, если у вас много выражений, которые никогда не нужно оценивать, ленивый лучше; тем не менее, если у вас никогда не будет выражения, которое не нужно оценивать, lazy - это просто накладные расходы.
Теперь давайте взглянем на программное обеспечение реального мира: сколько написанных вами функций не требуют оценки всех своих аргументов? Особенно с современными короткими функциями, которые делают только одно, процент функций, попадающих в эту категорию, очень низок. Таким образом, ленивая оценка в большинстве случаев просто привносит накладные расходы на ведение бухгалтерского учета, без возможности что-либо реально сохранить.
Следовательно, ленивая оценка просто не окупается в среднем, более подходящая оценка лучше подходит для современного кода.
Как отметил @DeadMG, Ленивая оценка требует затрат на ведение бухгалтерского учета. Это может быть дорого по сравнению с нетерпением оценки. Рассмотрим это утверждение:
i = (243 * 414 + 6562 / 435.0 ) ^ 0.5 ** 3
Это займет немного расчета для расчета. Если я использую ленивую оценку, то мне нужно проверять, оценивалась ли она каждый раз, когда я ее использую. Если это внутри интенсивно используемой тугой петли, тогда накладные расходы значительно возрастают, но это не дает никакой выгоды.
С энергичной оценкой и приличным компилятором формула рассчитывается во время компиляции. Большинство оптимизаторов будут перемещать назначение из любых циклов, в которых оно происходит, если это необходимо.
Ленивая оценка лучше всего подходит для загрузки данных, доступ к которым осуществляется нечасто и которые требуют больших затрат на извлечение. Поэтому он больше подходит для крайних случаев, чем основные функции.
В целом, хорошей практикой является оценка вещей, к которым часто обращаются, как можно раньше. Ленивая оценка не работает с этой практикой. Если вы всегда будете получать доступ к чему-либо, все ленивые оценки будут делать, это добавить накладные расходы. Стоимость / польза от использования отложенной оценки уменьшается, так как доступ к элементу становится менее вероятным.
Всегда использовать ленивую оценку также подразумевает раннюю оптимизацию. Это плохая практика, которая часто приводит к тому, что код становится гораздо более сложным и дорогим, что в противном случае могло бы иметь место. К сожалению, преждевременная оптимизация часто приводит к тому, что код работает медленнее, чем простой код. Пока вы не сможете измерить эффект оптимизации, оптимизировать код будет плохой идеей.
Избегание преждевременной оптимизации не противоречит хорошей практике кодирования. Если передовой опыт не применялся, первоначальная оптимизация может состоять из применения передовых методов кодирования, таких как перемещение вычислений из циклов.
Если мы потенциально должны полностью оценить выражение, чтобы определить его значение, то ленивая оценка может быть недостатком. Допустим, у нас есть длинный список логических значений, и мы хотим выяснить, все ли они истинны:
[True, True, True, ... False]
Чтобы сделать это, нам нужно посмотреть на каждый элемент в списке, несмотря ни на что, поэтому нет возможности лениво обрезать оценку. Мы можем использовать сгиб, чтобы определить, являются ли все логические значения в списке истинными. Если мы используем сложное право, которое использует ленивую оценку, мы не получим никаких преимуществ от ленивой оценки, потому что мы должны рассмотреть каждый элемент в списке:
foldr (&&) True [True, True, True, ... False]
> 0.27 secs
В этом случае сгибание вправо будет намного медленнее, чем сгибание вправо, которое не использует ленивую оценку:
foldl' (&&) True [True, True, True, ... False]
> 0.09 secs
Причина в том, что строгое сгибание влево использует хвостовую рекурсию, что означает, что она накапливает возвращаемое значение и не накапливает и не сохраняет в памяти большую цепочку операций. Это намного быстрее, чем ленивое правое сгибание, потому что обе функции в любом случае должны просматривать весь список, а правое сгибание не может использовать хвостовую рекурсию. Итак, суть в том, что вы должны использовать все, что лучше для поставленной задачи.