Чому ледача оцінка не використовується скрізь?


32

Щойно я дізнався, як працює лінива оцінка, і мені було цікаво: чому не лінива оцінка застосовується у кожному програмному забезпеченні, яке зараз виробляється? Чому все-таки використовують охочу оцінку?


2
Ось приклад того, що може статися, якщо ви змішаєте змінений стан і ледачу оцінку. alicebobandmallory.com/articles/2011/01/01/…
Jonas Elfström

2
@ JonasElfström: Будь ласка, не плутайте змінний стан з однією з можливих його реалізації. Стан, що змінюється, може бути реалізований за допомогою нескінченного ледачого потоку значень. Тоді у вас не виникає проблеми змінних змінних.
Джорджіо

В імперативних мовах програмування "лінива оцінка" вимагає свідомого зусилля програміста. Загальне програмування в імперативних мовах зробило це просто, але воно ніколи не буде прозорим. Відповідь на іншу сторону питання викликає ще одне запитання: "Чому функціональні мови програмування не використовуються скрізь?", А наразі відповідь просто "ні" як питання поточних справ.
rwong

2
Мови функціонального програмування не використовуються скрізь з тієї ж причини, що ми не використовуємо молотки на гвинтах, не кожна проблема може бути легко виражена функціональним входом -> вихідним способом, наприклад, графічний інтерфейс краще підходить для вираження в обов'язковому порядку .
ALXGTV

Далі є два класи функціональних мов програмування (або принаймні обидва претендують на функціональність), імперативні функціональні мови, наприклад, Clojure, Scala та декларативні, наприклад Haskell, OCaml.
ALXGTV

Відповіді:


38

Ледача оцінка потребує накладних витрат на бухгалтерію - ви повинні знати, чи її ще оцінювали і подібні речі. Швидке оцінювання завжди оцінюється, тому не потрібно знати. Особливо це стосується паралельних контекстів.

По-друге, банально перетворити прагнення оцінити до ледачого оцінювання, упакувавши його в функціональний об'єкт, який буде викликаний пізніше, якщо ви цього хочете.

По-третє, лінива оцінка передбачає втрату контролю. Що робити, якщо я ліниво оцінив читання файлу з диска? Або отримувати час? Це не прийнятно.

Бурхливе оцінювання може бути більш ефективним і більш контрольованим, і банально перетворюється на ледачу оцінку. Чому б ви хотіли лінивої оцінки?


10
Ледаче читання файлу з диска насправді дуже акуратне - для більшості моїх простих програм та сценаріїв Haskell - readFileце саме те , що мені потрібно. Крім того, перехід від ледачого до прагнення оцінювання так само тривіальний.
Тихон Єлвіс

3
Погодьтеся з вами всі, крім останнього абзацу. Ледача оцінка є більш ефективною, коли відбувається ланцюгова операція, і вона може мати більше контролю над тим, коли фактично потрібні дані
texasbruce

4
Закони функтора хотіли б поговорити з вами про "втрату контролю". Якщо ви пишете чисті функції, які працюють на незмінних типах даних, лінива оцінка - це знахідка. Такі мови, як haskell, принципово базуються на концепції ліні. На деяких мовах це громіздко, особливо в поєднанні з "небезпечним" кодом, але ви здаєтеся, що лінь за замовчуванням небезпечна чи погана. Це лише "небезпечно" в небезпечному коді.
сара

1
@DeadMG Якщо ви не переймаєтесь тим, припиняється чи ні ваш код ... Що head [1 ..]дає вам нетерпляча чиста мова, адже в Haskell це дає 1?
крапка з комою

1
Для багатьох мов реалізація ледачого оцінювання принаймні додасть складності. Іноді ця складність потрібна, а ледача оцінка підвищує загальну ефективність, особливо якщо те, що оцінюється, потрібно лише умовно. Однак, якщо це зроблено погано, це може ввести непомітні помилки або важко пояснити проблеми з продуктивністю через погані припущення при написанні коду. Є компроміс.
Берін Лорич

17

Головним чином, тому що лінивий код і стан можуть погано змішуватися і викликати важкі помилки. Якщо стан залежного об'єкта зміниться, значення вашого ледачого об'єкта може бути неправильним при оцінці. Набагато краще, щоб програміст явно кодував об'єкт, щоб він був лінивим, коли він знає, що ситуація підходить.

Зі сторони, Haskell використовує оцінку Lazy для всього. Це можливо, оскільки це функціональна мова і не використовує стан (за винятком кількох виняткових обставин, коли вони чітко позначені)


Так, мінливий стан + ледача оцінка = смерть. Я думаю, що єдиний момент, який я програв у фіналі SICP, полягав set!у використанні в ледачому інтерпретаторі схеми. > :(
Тихон Єлвіс

3
"ледачий код і стан можуть погано змішуватися": Це дійсно залежить від того, як ви реалізуєте стан. Якщо ви реалізуєте це за допомогою спільних змінних змінних, і ви залежаєте від порядку оцінювання, щоб ваш стан був узгодженим, значить, ви праві.
Джорджіо

14

Ледачі оцінки не завжди кращі.

Переваги ефективності лінивого оцінювання можуть бути великими, але не важко уникнути зайвих оцінок у нетерплячих умовах - безумовно, ледачий робить його легким і повним, але рідко є зайвою оцінкою в коді головну проблему.

Хороша річ про ледачу оцінку - це коли вона дозволяє писати чіткіший код; отримання 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, оскільки ледача версія - собака повільна - і часом важко міркувати про продуктивність.

Як це буває, немає абсолютної найкращої стратегії. Ледачий - це чудово, якщо ви можете писати кращий код, користуючись нескінченними структурами даних або іншими стратегіями, які він дозволяє використовувати, але оптимізація може бути простішою.


Чи вдасться дійсно розумному компілятору значно зменшити накладні витрати. чи навіть скористатися лінню для додаткових оптимізацій?
Тихон Єлвіс

3

Ось короткий порівняння плюсів і мінусів бажаючих і лінивих оцінок:

  • Швидка оцінка:

    • Потенційні накладні витрати непотрібної оцінки речей.

    • Безперешкодне, швидке оцінювання.

  • Ледача оцінка:

    • Немає зайвої оцінки.

    • Бухгалтерський облік при кожному використанні цінності.

Отже, якщо у вас є багато виразів, які ніколи не доводиться оцінювати, лінь ліпше; але якщо ви ніколи не маєте виразу, який не потрібно оцінювати, лінь - це чиста надмір.

Тепер давайте подивимось на програмне забезпечення реального світу: скільки функцій, які ви пишете, не потребують оцінки всіх їхніх аргументів? Особливо із сучасними короткими функціями, які виконують лише одне, відсоток функцій, що належать до цієї категорії, дуже низький. Таким чином, лінива оцінка просто впроваджувала б бухгалтерію накладні більшу частину часу, без шансів насправді нічого врятувати.

Отже, ледача оцінка просто не приносить середнього рахунку, прагнення до оцінювання найкраще підходить для сучасного коду.


1
"Бухгалтерський облік при кожному використанні значення.": Я не думаю, що накладні витрати більше, ніж, скажімо, перевірка нульових посилань на такій мові, як Java. В обох випадках вам потрібно перевірити один біт інформації (оцінюється / очікує проти нульового / ненульового) і робити це потрібно щоразу, коли ви використовуєте значення. Так, так, накладні витрати є, але вони мінімальні.
Джорджіо

1
"Скільки функцій, які ви пишете, не потребують оцінки всіх їхніх аргументів?": Це лише один приклад програми. Що з рекурсивними, нескінченними структурами даних? Чи можете ви реалізувати їх з нетерпінням? Можна використовувати ітератори, але рішення не завжди є таким стислим. Звичайно, ви, мабуть, не пропускаєте чогось, чого у вас ніколи не було можливості широко використовувати.
Джорджіо

2
"Отже, ледача оцінка просто не платить в середньому, прагнення до оцінки краще підходить для сучасного коду.": Це твердження не дотримується: воно дійсно залежить від того, що ви намагаєтеся реалізувати.
Джорджіо

1
@Giorgio Накладні витрати можуть здатися вам не дуже, але умовні умови - це одне з речей, на які всмоктуються сучасні процесори: Помилкова прогнозована гілка зазвичай примушує повний конвеєр, викидаючи роботу більш ніж десяти циклів процесора. Ви не хочете зайвих умов у внутрішньому циклі. Доплата за десять циклів додатково за аргумент функції майже неприйнятна для коду, що відрізняється від продуктивності, як кодування речі в Java. Ви маєте рацію, що лінива оцінка дає змогу виконувати деякі хитрощі, які не можна легко виконати при бажанні оцінки. Але переважній більшості коду ці хитрощі не потрібні.
cmaster

2
Здається, це відповідь через недосвідченість мов із ледачою оцінкою. Наприклад, що з нескінченними структурами даних?
Андрес Ф.

3

Як зазначає @DeadMG, для лінивої оцінки потрібні накладні витрати. Це може бути дорогим відносно бажаючих оцінювати. Розглянемо це твердження:

i = (243 * 414 + 6562 / 435.0 ) ^ 0.5 ** 3

Для розрахунку знадобиться трохи розрахунку. Якщо я використовую ледачу оцінку, то мені потрібно перевіряти, чи вона була оцінена кожен раз, коли я її використовую. Якщо це всередині сильно використовуваної щільної петлі, то накладні витрати значно збільшуються, але користі немає.

При бажанні оцінювання та гідному компіляторі формула розраховується під час компіляції. Більшість оптимізаторів перенесуть завдання з будь-яких циклів, у яких воно відбувається, якщо це доречно.

Ледача оцінка найкраще підходить для завантаження даних, до яких нечасто звертаються та мають високі накладні витрати для отримання. Тому більше підходить для кращих випадків, ніж основна функціональність.

Загалом, це найкраща практика оцінювати речі, до яких часто звертаються, якомога раніше. Ледача оцінка не працює з цією практикою. Якщо ви завжди будете отримувати доступ до чогось, все ліниве оцінювання - це додаткові накладні витрати. Вартість / вигода від використання ледачої оцінки зменшується, оскільки доступ до предмета стає менш ймовірним.

Завжди використання лінивої оцінки також передбачає ранню оптимізацію. Це погана практика, яка часто призводить до коду, який є набагато складнішим і дорогим, що може бути інакше. На жаль, передчасна оптимізація часто призводить до коду, який працює повільніше, ніж простіший код. Поки ви не зможете виміряти ефект від оптимізації, погана ідея оптимізувати ваш код.

Уникнення передчасної оптимізації не суперечить правильній практиці кодування. Якщо належні практики не були застосовані, початкові оптимізації можуть полягати у застосуванні належних методів кодування, таких як переміщення обчислень із циклів.


1
Ви ніби сперечаєтесь із недосвідченості. Я пропоную вам прочитати документ "Чому питання функціонального програмування" Вадлера. Він присвячує основний розділ, що пояснює, чому ледаче оцінювання (натяк: це мало стосується продуктивності, ранньої оптимізації або "завантаження нечасто доступних даних" та всього, що стосується модульності).
Андрес Ф.

@AndresF Я прочитав документ, на який ви посилаєтесь. Я погоджуюсь із використанням ледачих оцінок у таких випадках. Рання оцінка може бути невідповідною, але я б заперечував, що повернення під-дерева для вибраного ходу може мати значну користь, якщо додаткові ходи можна легко додати. Однак побудова цієї функціональності може бути передчасною оптимізацією. Поза функціональним програмуванням у мене виникають суттєві проблеми із використанням ледачого оцінювання та невикористанням ледачої оцінки. Є повідомлення про значні витрати на продуктивність, що виникають внаслідок ледачої оцінки функціонального програмування.
BillThor

2
Як от? Існують звіти про значні витрати на ефективність і при використанні нетерплячої оцінки (витрати у вигляді або непотрібної оцінки, а також неприпинення програми). Майже будь-яка інша (неправильно) використана функція є витратою, подумайте про це. Модульність сама по собі може дорожчати; питання полягає в тому, чи варто того
Андрес Ф.

3

Якщо нам, можливо, доведеться повністю оцінити вираз, щоб визначити його значення, то ледача оцінка може бути недоліком. Скажімо, у нас довгий список булевих значень і ми хочемо з'ясувати, чи всі вони правдиві:

[True, True, True, ... False]

Для цього нам слід переглянути кожен елемент у списку, незважаючи ні на що, тому немає можливості ліниво відрізати оцінку. Ми можемо скористатись краткою, щоб визначити, чи всі булі значення списку істинні. Якщо ми використовуємо право згину, яке використовує ледачу оцінку, ми не отримуємо жодної користі від ледачого оцінювання, оскільки нам потрібно переглянути кожен елемент у списку:

foldr (&&) True [True, True, True, ... False] 
> 0.27 secs

Права складка в цьому випадку буде набагато повільнішою, ніж сувора ліва складка, яка не використовує ледачу оцінку:

foldl' (&&) True [True, True, True, ... False] 
> 0.09 secs

Причина в тому, що в лівій строгій складці використовується хвостова рекурсія, а значить, вона накопичує повернене значення і не накопичує і не зберігає в пам'яті великий ланцюжок операцій. Це набагато швидше, ніж лінива складка праворуч, оскільки обидві функції так чи інакше повинні переглядати весь список, а права складка не може використовувати хвостову рекурсію. Отож, справа в тому, що ви повинні використовувати все, що найкраще підходить для завдання.


"Отже, справа в тому, що ви повинні використовувати все, що найкраще для задачі, що знаходиться під рукою". +1
Джорджіо
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.