Як суто функціональні мови програмування мають справу з швидко змінюються даними?


22

Які структури даних ви можете використовувати, щоб отримати можливість видалення та заміни O (1)? Або як ви можете уникнути ситуацій, коли вам потрібні зазначені структури?


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

4
@FrustratedWithFormsDesigner Суто функціональні мови програмування вимагають, щоб усі змінні були незмінними, що, в свою чергу, вимагає структур даних, які створюють нові версії себе при "модифікації".
Довал

5
Чи знаєте ви про роботу Окасакі над суто функціональними структурами даних?

2
Однією з можливостей є визначення монади для змінних даних (див., Наприклад, haskell.org/ghc/docs/4.08/set/sec-marray.html ). Таким чином, змінні дані обробляються аналогічно IO.
Джорджіо

1
@CodesInChaos: однак такі незмінні структури, як правило, мають набагато більше накладних витрат, ніж прості масиви. Як результат, є дуже велика практична різниця. Ось чому будь-яка суто функціональна мова, яка спрямована на програмування загального призначення, повинна мати спосіб використання змінних структур, якимось безпечним способом, сумісним із чистою семантикою. STМонада в Haskell робить це чудово.
Ліворуч близько

Відповіді:


32

Існує велика кількість структур даних, що використовують лінощі та інші хитрощі для досягнення амортизованого постійного часу або навіть (для деяких обмежених випадків, таких як черги ) постійних оновлень часу для багатьох видів проблем. Кандидатська дисертація Кріса Окасакі "Чисто функціональні структури даних" та однойменна книга - це найкращий приклад (можливо, перший великий), але з цього часу ця сфера розвинулася . Ці структури даних, як правило, не тільки суто функціональні в інтерфейсі, але також можуть бути реалізовані в чистому Haskell і подібних мовах, і є повністю стійкими.

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

Є й інші варіанти, які можна вважати шахрайством, але дуже ефективні щодо зусиль щодо впровадження та ефективності в реальному світі. Наприклад, лінійні типи або унікальні типи дозволяють оновити на місці в якості стратегії реалізації концептуально чистої мови, не дозволяючи програмі утримуватися до попереднього значення (пам'ять, яка буде вимкнена). Це менш загально, ніж стійкі структури даних: Наприклад, ви не можете легко скласти журнал скасування, зберігаючи всі попередні версії стану. Це все ще потужний інструмент, хоча AFAIK ще не доступний на основних функціональних мовах.

Інший варіант безпечного введення змінного стану у функціональну обстановку - це STмонада в Хаскелл. Він може бути реалізований без мутації та заборонених unsafe*функцій, він веде себе так, ніби це був просто фантазійний обгортку, який передає стійку структуру даних неявно (див. State). Але завдяки деякому типовому системному хитруванню, яке застосовує порядок оцінювання та запобігає уникненню, його можна сміливо реалізувати за допомогою мутації на місці з усіма перевагами від продуктивності.


Можливо, варто згадати і блискавки, що дають вам можливість робити швидкі зміни у фокусі списку чи дерева
jk.

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

досить чесно, не перейшов за посиланнями
jk.

9

Одна дешева змінна структура - це аргумент стека.

Погляньте на типовий факторний розрахунок у стилі SICP:

(defn fac (n accum) 
    (if (= n 1) 
        accum 
        (fac (- n 1) (* accum n)))

(defn factorial (n) (fac n 1))

Як бачимо, другий аргумент до facвикористовується як змінний акумулятор, щоб містити продукт, що швидко змінюється n * (n-1) * (n-2) * .... Немає видно змінної змінної, і немає способу ненавмисно змінити акумулятор, наприклад, з іншої нитки.

Це, звичайно, обмежений приклад.

Ви можете отримати незмінні пов'язані списки з дешевою заміною головного вузла (і шляхом розширення будь-якої частини, що починається з голови): ви просто зробите нову точку голови на той самий наступний вузол, що і стара стара голова. Це добре працює з багатьма алгоритмами обробки списків (що завгодно fold).

Ви можете отримати досить хороші показники з асоціативних масивів на основі, наприклад, на HAMT . За логікою ви отримуєте новий асоціативний масив із зміненою парою ключ-значення. Реалізація може обмінюватися більшістю загальних даних між старими та новоствореними об'єктами. Однак це не O (1); зазвичай ви отримуєте щось логарифмічне, принаймні, в гіршому випадку. З іншого боку, дерева, що непорушні, зазвичай не зазнають покарання за продуктивність порівняно з деревами, що змінюються. Звичайно, для цього потрібні певні накладні об'єм пам’яті, як правило, далеко не надмірні.

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

Clojure має перехідні процеси, які є змінними "тінями" незмінних структур даних, які не протікають за межами локальної сфери. Clean використовує Uniques для досягнення чогось подібного (якщо я правильно пам’ятаю). Іржа допомагає робити подібні речі зі статистично перевіреними унікальними покажчиками.


1
+1, також для згадування унікальних типів у «Чистому».
Джорджо

@ 9000 Я думаю, що чув, що у Haskell є щось подібне до перехідних Clojure - хтось мене виправить, якщо я не прав.
пав

@paul: У мене є дуже побіжні знання про Haskell, тому, якщо ви могли б надати мою інформацію (принаймні ключове слово для google), я б із задоволенням включив посилання на відповідь.
9000

1
@paul Я не так впевнений. Але у Haskell є метод створення чогось подібного до ML refта обмеження їх у певному обсязі. Побачити IORefабо STRef. І, звичайно, є TVars і MVars, які схожі, але з розумною паралельною семантикою (stm for TVars та mutex на основі MVars)
Daniel Gratzer

2

Те, що ви запитуєте, трохи надто широке. O (1) зняття та заміна з якої посади? Голова послідовності? Хвіст? Довільна позиція? Структура даних, що використовується, залежить від цих деталей. Однак, 2-3 пальця здаються однією з найбільш універсальних стійких структур даних:

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

(...)

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

Як правило, стійкі структури даних мають логарифмічну ефективність при зміні довільних позицій. Це може бути або не бути проблемою, оскільки константа в алгоритмі O (1) може бути високою, а логарифмічне уповільнення може бути "поглинене" в більш повільний загальний алгоритм.

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

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