Як часто seq використовується у виробничому коді Haskell?


23

У мене є досвід написання невеликих інструментів у Haskell, і мені здається, що це дуже інтуїтивно використовувати, особливо для запису фільтрів (за допомогою interact), які обробляють їх стандартний вхід і передають його на стандартний вихід.

Нещодавно я спробував використати один такий фільтр у файлі, який був приблизно в 10 разів більшим, ніж зазвичай, і я отримав Stack space overflowпомилку.

Прочитавши (наприклад, тут і тут ), я визначив два вказівки щодо економії місця в стеці (досвідчені Haskellers, будь ласка, виправте мене, якщо я напишу щось, що не відповідає):

  1. Уникайте рекурсивних викликів функцій, які не є рекурсивними (це справедливо для всіх функціональних мов, які підтримують оптимізацію хвостових викликів).
  2. Введіть, seqщоб змусити раннє оцінювання суб-виразів, щоб вирази не зростали занадто великими до їх скорочення (це характерно для Haskell або, принаймні, для мов, що використовують ледачу оцінку).

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

Оскільки я не є досвідченим програмістом Haskell, я хотів запитати, чи введення seqтаким способом є звичайною практикою, і як часто ви бачите seqу виробничому коді Haskell. Або є якісь методи, які дозволяють уникнути seqзанадто частого використання і все ж використовувати мало місця у стеці?


1
Оптимізації, як описаний вами вид, майже завжди роблять код трохи менш елегантним.
Роберт Харві

@Robert Harvey: Чи існують альтернативні методи, щоб зменшити кількість використання стека? Я маю на увазі, я думаю, що мені доведеться переписати свої функції по-різному, але я не маю поняття, чи є добре налагоджені методи. Моєю першою спробою було використання хвостово-рекурсивних функцій, що допомогло, але не дозволило мені повністю вирішити свою проблему.
Джорджо

Відповіді:


17

На жаль, є випадки, коли доводиться використовувати seqдля того, щоб отримати ефективну / добре працюючу програму для великих даних. Тож у багатьох випадках без виробничого коду не обійтися. Ви можете знайти більше інформації в реальному світі Haskell, глава 25. Профілювання та оптимізація .

Однак є можливості, як уникнути використання seqбезпосередньо. Це може зробити код чистішим та надійнішим. Деякі ідеї:

  1. Замість цього використовуйте трубопроводи , труби або ітератиinteract . Як відомо, у ледачого IO виникають проблеми з управлінням ресурсами (не лише з пам'яттю), а ітератори призначені саме для подолання цього. (Я б запропонував взагалі уникати лінивого вводу-виводу незалежно від того, наскільки великі ваші дані - див . Проблема з ледачим введення-виведення .)
  2. Замість того, щоб seqбезпосередньо використовувати (або створити власні) комбінатори, такі як foldl ' або foldr' або суворі версії бібліотек (наприклад, Data.Map.Strict або Control.Monad.State.Strict ), призначені для суворих обчислень.
  3. Використовуйте розширення BangPatterns . Це дозволяє замінити seqсуворе узгодження шаблону. Оголошення суворих конструкторських полів також може бути корисним у деяких випадках.
  4. Можливо також використовувати Стратегії для примусового оцінювання. Бібліотека стратегій здебільшого спрямована на паралельні обчислення, але також має методи для присвоєння значення WHNF ( rseq) або full NF ( rdeepseq). Існує багато корисних методів роботи з колекціями, комбінування стратегій тощо.

+1: Дякую за корисні підказки та посилання. Точка 3 видається досить цікавою (і найпростішим рішенням для мене зараз користуватися). Щодо пропозиції 1, я не бачу, як уникнути лінивого IO може покращити: Наскільки я розумію, лінивий IO повинен бути кращим для фільтра, який повинен обробляти (можливо, дуже довгий) потік даних.
Джорджіо

2
@Giorgio Я додав посилання на Haskell Wiki про проблеми з Lazy IO. З ледачим IO у вас може бути дуже важко керувати ресурсами. Наприклад, якщо ви не повністю прочитали вхід (наприклад, через ледачу оцінку), ручка файлу залишається відкритою . А якщо зайти та закрити ручку файлу вручну, часто трапляється, що через ледачу оцінку читання відкладається, і ви закриваєте ручку, перш ніж прочитати весь вхід. І часто важко уникнути проблем із пам’яттю із ледачим IO.
Петро Пудлак

Нещодавно у мене виникла ця проблема, і в моїй програмі не вистачало дескрипторів файлів. Тому я замінив лінивий IO на суворий IO, використовуючи строгий ByteString.
Джорджо
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.