Різниця між штатом, ST, IORef та MVar


91

Я працюю через Напишіть собі схему за 48 годин (я приблизно до 85 годин), і я дійшов до частини про Додавання змінних та призначень . У цій главі є великий концептуальний стрибок, і я би хотів, щоб це було зроблено у два етапи з гарним рефакторингом між ними, а не стрибком прямо до остаточного рішення. У будь-якому разі ...

Я заблукав з безліччю різних класів , які , здається, служать тієї ж мети: State, ST, IORef, і MVar. Перші три згадуються в тексті, тоді як останній, як видається, є улюбленою відповіддю на багато питань StackOverflow щодо перших трьох. Здається, всі вони мають стан між послідовними викликами.

Що таке кожен із них і чим вони відрізняються один від одного?


Зокрема, ці речення не мають сенсу:

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

і

Модуль IORef дозволяє використовувати змінні зі станом у монаді IO .

Все це робить рядок type ENV = IORef [(String, IORef LispVal)]заплутаним - чому другий IORef? Що зламається, якщо я type ENV = State [(String, LispVal)]замість цього напишу ?

Відповіді:


119

Державна монада: модель змінної держави

Монада штату - це суто функціональне середовище для програм із станом, з простим API:

  • отримати
  • поставити

Документація в пакеті mtl .

Державна монада зазвичай використовується, коли потрібна держава в одному потоці управління. Насправді він не використовує змінний стан для його реалізації. Натомість програма параметризується значенням стану (тобто стан є додатковим параметром до всіх обчислень). Здається, стан мутується лише в одному потоці (і не може бути спільним між потоками).

Монада ST та STRef

Монада ST - двоюрідний брат монади IO.

Це дозволяє довільний змінний стан , реалізований як справжня змінна пам'ять на машині. API робиться безпечним у програмах без побічних ефектів, оскільки параметр типу rank-2 запобігає виходу значень, які залежать від змінного стану, з локальної області дії.

Таким чином, це дозволяє контролювати змінність в інших програмах.

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

Основний API:

  • Control.Monad.ST
  • runST - розпочати нове обчислення ефекту пам'яті.
  • І STRefs : вказівники на (локальні) мінливі клітини.
  • Також часто зустрічаються масиви на основі ST (наприклад, вектор).

Подумайте про це як про менш небезпечний брат монади IO. Або IO, де ви можете лише читати та писати в пам’ять.

IORef: STRef в IO

Це STRef (див. Вище) в монаді IO. Вони не мають таких самих гарантій безпеки, як STRef щодо місцевості.

MVars: IORefs з замками

Як STRefs або IORefs, але з прикріпленим замком для безпечного одночасного доступу з декількох потоків. IORefs і STRefs безпечні лише в багатопотоковій обстановці при використанні atomicModifyIORef(атомна операція порівняння та обміну). MVars - це більш загальний механізм безпечного обміну змінним станом.

Як правило, в Haskell використовуйте MVars або TVars (змінні комірки на основі STM), над STRef або IORef.


3
Що означає M у MVars та T у TVars? Я здогадуюсь "Змінюваний", "Транзакційний". Цікаво, як ST означає State Thread.
CMCDragonkai

10
Чому ви кажете, що MVarслід віддавати перевагу над STRef? STRefгарантує, що лише один потік може його мутувати (і що не можуть відбуватися інші типи введення-виведення) - безумовно, це краще, якщо мені не потрібен одночасний доступ до змінного стану?
Бенджамін Ходжсон

@CMCDragonkai Я завжди вважав, що M означає мютекс, але я ніде не можу це задокументувати.
Ендрю Тадеус Мартін

37

Добре, я почну з IORef. IORefзабезпечує значення, яке змінюється в монаді вводу-виводу. Це просто посилання на деякі дані, і, як і будь-яке посилання, є функції, які дозволяють змінювати дані, на які вони посилаються. У Haskell всі ці функції працюють в IO. Ви можете сприймати це як базу даних, файл чи інший зовнішній сховище даних - ви можете отримувати та встановлювати дані в ньому, але для цього потрібно пройти IO. Причина IO взагалі необхідна в тому, що Хаскелл чистий ; компілятору потрібен спосіб дізнатись, на які дані вказує посилання в будь-який момент часу (прочитайте блог sigfpe "Ви могли б винайти монади" ).

MVars - це в основному те саме, що IORef, за винятком двох дуже важливих відмінностей. MVarє примітивом одночасності, тому призначений для доступу з декількох потоків. Друга різниця полягає в тому, що an MVar- це поле, яке може бути повним або порожнім. Отже, там, де IORef Intзавжди є знак Int(або є внизу), символ an MVar Intможе мати Intабо він може бути порожнім. Якщо потік намагається прочитати значення з порожнього MVar, він буде блокувати, поки MVarне заповниться (іншим потоком). В основному MVar a, еквівалент IORef (Maybe a)додатковій семантиці, яка корисна для паралельності.

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

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

STце щось дещо інше. Основною структурою даних в STє STRef, яка схожа на, IORefале з іншою монадою. STСистема типу монади використовує обман (в «державних нитках» Документи згадують) , щоб гарантувати , що змінюються дані не можуть уникнути монад; тобто при запуску обчислення ST ви отримуєте чистий результат. Причина ST цікава в тому, що це примітивна монада, така як IO, що дозволяє обчисленням виконувати низькорівневі маніпуляції на байтових масивах і покажчиках. Це означає, що він STможе забезпечити чистий інтерфейс під час використання низькорівневих операцій із змінними даними, тобто це дуже швидко. З точки зору програми, це ніби STобчислення виконується в окремому потоці з локальним сховищем потоків.


17

Інші зробили основні речі, але щоб відповісти на пряме запитання:

Все це робить тип рядка ENV = IORef [(String, IORef LispVal)] заплутаним. Чому другий IORef? Що зламається, якщо я type ENV = State [(String, LispVal)]замість цього зроблю ?

Lisp - це функціональна мова зі змінним станом та лексичним обсягом. Уявіть, що ви закрили змінну змінну. Тепер у вас є посилання на цю змінну, яка висить всередині якоїсь іншої функції - скажімо (у псевдокоді в стилі haskell) (printIt, setIt) = let x = 5 in (\ () -> print x, \y -> set x y). Тепер у вас є дві функції - одна друкує x, а інша встановлює її значення. Коли ви оцінюєте printIt, ви хочете для пошуку по імені х у вихідній середовищі , в якій printItбуло визначено, але ви хочете для запиту значення , що ім'я пов'язується в середовищі , в якій printItзнаходиться називається (після того, як setItможна було назвати будь-яке число раз ).

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

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