Щойно я дізнався, як працює лінива оцінка, і мені було цікаво: чому не лінива оцінка застосовується у кожному програмному забезпеченні, яке зараз виробляється? Чому все-таки використовують охочу оцінку?
Щойно я дізнався, як працює лінива оцінка, і мені було цікаво: чому не лінива оцінка застосовується у кожному програмному забезпеченні, яке зараз виробляється? Чому все-таки використовують охочу оцінку?
Відповіді:
Ледача оцінка потребує накладних витрат на бухгалтерію - ви повинні знати, чи її ще оцінювали і подібні речі. Швидке оцінювання завжди оцінюється, тому не потрібно знати. Особливо це стосується паралельних контекстів.
По-друге, банально перетворити прагнення оцінити до ледачого оцінювання, упакувавши його в функціональний об'єкт, який буде викликаний пізніше, якщо ви цього хочете.
По-третє, лінива оцінка передбачає втрату контролю. Що робити, якщо я ліниво оцінив читання файлу з диска? Або отримувати час? Це не прийнятно.
Бурхливе оцінювання може бути більш ефективним і більш контрольованим, і банально перетворюється на ледачу оцінку. Чому б ви хотіли лінивої оцінки?
readFile
це саме те , що мені потрібно. Крім того, перехід від ледачого до прагнення оцінювання так само тривіальний.
head [1 ..]
дає вам нетерпляча чиста мова, адже в Haskell це дає 1
?
Головним чином, тому що лінивий код і стан можуть погано змішуватися і викликати важкі помилки. Якщо стан залежного об'єкта зміниться, значення вашого ледачого об'єкта може бути неправильним при оцінці. Набагато краще, щоб програміст явно кодував об'єкт, щоб він був лінивим, коли він знає, що ситуація підходить.
Зі сторони, Haskell використовує оцінку Lazy для всього. Це можливо, оскільки це функціональна мова і не використовує стан (за винятком кількох виняткових обставин, коли вони чітко позначені)
set!
у використанні в ледачому інтерпретаторі схеми. > :(
Ледачі оцінки не завжди кращі.
Переваги ефективності лінивого оцінювання можуть бути великими, але не важко уникнути зайвих оцінок у нетерплячих умовах - безумовно, ледачий робить його легким і повним, але рідко є зайвою оцінкою в коді головну проблему.
Хороша річ про ледачу оцінку - це коли вона дозволяє писати чіткіший код; отримання 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, коли стан досить явний). Отже, у більшості мов лінь потрібно робити вручну, що робить речі менш зрозумілими і тим самим усуває одну з великих переваг лінивого евалу.
Крім того, лінивість має недоліки в роботі, оскільки вона несе значні накладні витрати на утримання неперевірених виразів; вони використовують накопичувач, і працювати з ними повільніше, ніж прості значення. Не рідкість з'ясувати, що вам доведеться нетерпляче кодувати ify, оскільки ледача версія - собака повільна - і часом важко міркувати про продуктивність.
Як це буває, немає абсолютної найкращої стратегії. Ледачий - це чудово, якщо ви можете писати кращий код, користуючись нескінченними структурами даних або іншими стратегіями, які він дозволяє використовувати, але оптимізація може бути простішою.
Ось короткий порівняння плюсів і мінусів бажаючих і лінивих оцінок:
Швидка оцінка:
Потенційні накладні витрати непотрібної оцінки речей.
Безперешкодне, швидке оцінювання.
Ледача оцінка:
Немає зайвої оцінки.
Бухгалтерський облік при кожному використанні цінності.
Отже, якщо у вас є багато виразів, які ніколи не доводиться оцінювати, лінь ліпше; але якщо ви ніколи не маєте виразу, який не потрібно оцінювати, лінь - це чиста надмір.
Тепер давайте подивимось на програмне забезпечення реального світу: скільки функцій, які ви пишете, не потребують оцінки всіх їхніх аргументів? Особливо із сучасними короткими функціями, які виконують лише одне, відсоток функцій, що належать до цієї категорії, дуже низький. Таким чином, лінива оцінка просто впроваджувала б бухгалтерію накладні більшу частину часу, без шансів насправді нічого врятувати.
Отже, ледача оцінка просто не приносить середнього рахунку, прагнення до оцінювання найкраще підходить для сучасного коду.
Як зазначає @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
Причина в тому, що в лівій строгій складці використовується хвостова рекурсія, а значить, вона накопичує повернене значення і не накопичує і не зберігає в пам'яті великий ланцюжок операцій. Це набагато швидше, ніж лінива складка праворуч, оскільки обидві функції так чи інакше повинні переглядати весь список, а права складка не може використовувати хвостову рекурсію. Отож, справа в тому, що ви повинні використовувати все, що найкраще підходить для завдання.