Що таке індексована монада?


98

Що таке індексована монада та мотивація цієї монади?

Я читав, що це допомагає відстежувати побічні ефекти. Але підпис і документація типу не ведуть мене нікуди.

Що може бути прикладом того, як це може допомогти відслідковувати побічні ефекти (чи будь-який інший дійсний приклад)?

Відповіді:


123

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

Перш за все, давайте перевіримо види.

IxMonad (m :: state -> state -> * -> *)

Тобто тип "обчислення" (або "дії", якщо ви хочете, але я буду дотримуватися "обчислення"), виглядає як

m before after value

де before, after :: stateі value :: *. Ідея полягає у захопі засобів для безпечної взаємодії із зовнішньою системою, яка має певне передбачуване поняття стану. Тип обчислень повідомляє вам про те, яким він повинен бути beforeстан, який afterвін буде працювати, і як (при регулярних монах над *), який тип valueобчислень виробляє.

Звичайні шматочки і шматки - так *само, як монада і - так stateсамо, як грають у доміно.

ireturn  ::  a -> m i i a    -- returning a pure value preserves state
ibind    ::  m i j a ->      -- we can go from i to j and get an a, thence
             (a -> m j k b)  -- we can go from j to k and get a b, therefore
             -> m i k b      -- we can indeed go from i to k and get a b

Поняття "стрілка Клейслі" (функція, яка дає обчислення), таким чином, генерується

a -> m i j b   -- values a in, b out; state transition i to j

і ми отримуємо склад

icomp :: IxMonad m => (b -> m j k c) -> (a -> m i j b) -> a -> m i k c
icomp f g = \ a -> ibind (g a) f

і, як завжди, закони саме це забезпечують ireturnі icompдають нам категорію

      ireturn `icomp` g = g
      f `icomp` ireturn = f
(f `icomp` g) `icomp` h = f `icomp` (g `icomp` h)

або, в комедії підробленої C / Java / що завгодно,

      g(); skip = g()
      skip; f() = f()
{g(); h()}; f() = h(); {g(); f()}

Навіщо турбуватися? Моделювати "правила" взаємодії. Наприклад, ви не можете вийняти DVD, якщо його немає в диску, і ви не можете вставити DVD у привід, якщо він вже є. Так

data DVDDrive :: Bool -> Bool -> * -> * where  -- Bool is "drive full?"
  DReturn :: a -> DVDDrive i i a
  DInsert :: DVD ->                   -- you have a DVD
             DVDDrive True k a ->     -- you know how to continue full
             DVDDrive False k a       -- so you can insert from empty
  DEject  :: (DVD ->                  -- once you receive a DVD
              DVDDrive False k a) ->  -- you know how to continue empty
             DVDDrive True k a        -- so you can eject when full

instance IxMonad DVDDrive where  -- put these methods where they need to go
  ireturn = DReturn              -- so this goes somewhere else
  ibind (DReturn a)     k  = k a
  ibind (DInsert dvd j) k  = DInsert dvd (ibind j k)
  ibind (DEject j)      k  = DEject j $ \ dvd -> ibind (j dvd) k

Маючи це на місці, ми можемо визначити "примітивні" команди

dInsert :: DVD -> DVDDrive False True ()
dInsert dvd = DInsert dvd $ DReturn ()

dEject :: DVDrive True False DVD
dEject = DEject $ \ dvd -> DReturn dvd

з якого збираються інші ireturnі ibind. Тепер я можу написати (запозичення do-нотація)

discSwap :: DVD -> DVDDrive True True DVD
discSwap dvd = do dvd' <- dEject; dInsert dvd ; ireturn dvd'

але не фізично неможливо

discSwap :: DVD -> DVDDrive True True DVD
discSwap dvd = do dInsert dvd; dEject      -- ouch!

Крім того, можна визначити свої примітивні команди безпосередньо

data DVDCommand :: Bool -> Bool -> * -> * where
  InsertC  :: DVD -> DVDCommand False True ()
  EjectC   :: DVDCommand True False DVD

а потім створити загальний шаблон

data CommandIxMonad :: (state -> state -> * -> *) ->
                        state -> state -> * -> * where
  CReturn  :: a -> CommandIxMonad c i i a
  (:?)     :: c i j a -> (a -> CommandIxMonad c j k b) ->
                CommandIxMonad c i k b

instance IxMonad (CommandIxMonad c) where
  ireturn = CReturn
  ibind (CReturn a) k  = k a
  ibind (c :? j)    k  = c :? \ a -> ibind (j a) k

Насправді ми сказали, що таке примітивні стрілки Клейслі (що таке «доміно»), а потім побудували над ними відповідне поняття «послідовності обчислень».

Зауважимо, що для кожної індексованої монади mдіагональ "без змін" m i iє монадою, але загалом m i jце не так. Більше того, значення не індексуються, а обчислення індексуються, тому індексована монада - це не просто звичайне уявлення про монаду, інстанційне для якоїсь іншої категорії.

Тепер ще раз подивіться на тип стрілки Клейслі

a -> m i j b

Ми знаємо, що ми повинні бути в стані, iщоб почати, і прогнозуємо, що будь-яке продовження розпочнеться з держави j. Ми багато знаємо про цю систему! Це не ризикована операція! Коли ми ставимо DVD у привід, він входить! DVD-накопичувач не може сказати, у якому стані знаходиться після кожної команди.

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

Який кращий інструмент?

type f :-> g = forall state. f state -> g state

class MonadIx (m :: (state -> *) -> (state -> *)) where
  returnIx    :: x :-> m x
  flipBindIx  :: (a :-> m b) -> (m a :-> m b)  -- tidier than bindIx

Страшне печиво? Не дуже, з двох причин. По-перше, це більше схоже на те, що таке монада, тому що це монада, але над (state -> *), ніж чим *. По-друге, якщо подивитися на тип стрілки Клейслі,

a :-> m b   =   forall state. a state -> m b state

ви отримуєте тип обчислень з попередньою умовою a та постумовою b, як у логіці Доброго Старого Хора. Твердження в програмній логіці зайняли менше півстоліття, щоб перетнути листування Кері-Говарда і стати типом Хаскелла. Тип returnIxтвердження говорить, що "ви можете досягти будь-якої післязастереження, яка дотримується, просто не роблячи нічого", що є правилом Лоріка Лоара для "пропустити". Відповідним складом є правило логіки Хоара для ";".

Давайте закінчимо, подивившись на тип bindIx, включивши всі кількісні показники.

bindIx :: forall i. m a i -> (forall j. a j -> m b j) -> m b i

Вони forallмають протилежну полярність. Ми вибираємо початковий стан iі обчислення, яке може починатися з i, після постуслової умови a. Світ вибирає будь-який проміжний стан, який jйому сподобається, але він повинен дати нам докази того, що є посткондиціоном b, і з будь-якої такої держави ми можемо продовжувати bвлаштовувати. Отже, послідовно, ми можемо досягти умови bвід держави i. Відпустивши свою владу над станами "після", ми зможемо моделювати непередбачувані обчислення.

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


1
Як ви можете передавати True/ Falseзначень як аргументи типу DVDDrive? Це якесь розширення, чи булеві фактично тут типи?
Бергі

8
@Bergi Булеві були "зняті", щоб існувати на рівні типу. Це можливо в Haskell за допомогою DataKindsрозширення та в мовах залежно введених текстів ... ну, це вже вся справа.
Дж. Абрахамсон

Не могли б ви трохи розширити MonadIx, можливо, на прикладах? Це краще на теоретичних засадах чи краще для практичного застосування?
Крістіан Конкл

2
@ChristianConkle Я розумію, що це не дуже корисно. Але ви ставите, що насправді зовсім інше питання. Місцево, коли я кажу, що MonadIx є "кращим", я маю на увазі в контексті моделювання взаємодій з непередбачуваним середовищем. Начебто, якщо ваш DVD-привід дозволений виплювати DVD, це не подобається, коли ви намагаєтесь вставити їх. Деякі практичні ситуації так само погано поводяться. Інші мають більш передбачуваність (тобто ви можете сказати, в якому стані починається будь-яке продовження, а не те, що операції не провалюються), і в цьому випадку з IxMonad простіше працювати.
pigworker

1
Коли ви "позичите" нотацію do у відповіді, може бути корисним сказати, що це дійсно синтаксис з RebindableSyntaxрозширенням. Згадка про інші необхідні розширення було б непогано, як і вищезгаданеDataKinds
гігабайти

46

Існує щонайменше три способи визначення індексованої монади, яку я знаю.

Я буду називати ці параметри як індексовані монади à la X , де X перебуває над комп’ютерними науковцями Боб Аткі, Конором МакБрайдом та Домініком Орчардом, оскільки саме так я думаю про них. Частини цих споруд мають набагато більш відому історію та приємніші інтерпретації через теорію категорій, але я вперше дізнався про них, пов’язані з цими назвами, і намагаюся утримати цю відповідь не надто езотерично.

Atkey

Стиль індексованої монади Боба Аткі полягає в роботі з двома додатковими параметрами, щоб мати справу з індексом монади.

З цим ви отримуєте визначення, які люди наводили в інших відповідях:

class IMonad m where
  ireturn  ::  a -> m i i a
  ibind    ::  m i j a -> (a -> m j k b) -> m i k b

Ми також можемо визначити індексовані коонади à la Atkey. Я фактично отримую багато пробігу з тих, що знаходяться в lensкодовій базі .

Макбрайд

Наступною формою індексованої монади є визначення Конора Макбріда з його статті "Клейслі Стріли несамовитої фортуни" . Він замість цього використовує єдиний параметр для індексу. Це робить індексоване визначення монади має досить розумну форму.

Якщо ми визначимо природне перетворення за допомогою параметричності наступним чином

type a ~> b = forall i. a i -> b i 

тоді ми можемо записати визначення Макбріда як

class IMonad m where
  ireturn :: a ~> m a
  ibind :: (a ~> m b) -> (m a ~> m b)

Це відчувається зовсім інакше, ніж Аткі, але він більше схожий на звичайну Монаду, замість того, щоб будувати монаду (m :: * -> *), ми будуємо її (m :: (k -> *) -> (k -> *).

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

data (:=) :: a i j where
   V :: a -> (a := i) i

Тепер ви можете це зробити

ireturn :: IMonad m => (a := j) ~> m (a := j)

який розширюється до

ireturn :: IMonad m => (a := j) i -> m (a := j) i

можна викликати лише тоді, коли j = i, і тоді уважне читання ibindможе повернути вас таким же, як і Atkey ibind. Вам потрібно обійти ці (: =) структури даних, але вони відновлюють силу презентації Atkey.

З іншого боку, презентація Atkey недостатньо сильна, щоб відновити всі можливості версії McBride. Влада суворо здобута.

Ще одна приємна річ - це те, що індексована монада Макбріда явно є монадою, це просто монада в іншій категорії функторів. Він працює над endofunctors на категорії функторів (k -> *)до , (k -> *)а не категорії функторів від *до *.

Весела вправа - з’ясувати, як зробити перетворення McBride в Atkey для індексованих комонів . Я особисто використовую тип даних "At" для побудови "під ключ" в папері McBride. Я насправді підійшов до Боба Аткі на ICFP 2013 і зазначив, що перетворив його зсередини, зробив його «пальто». Він здавався помітно занепокоєним. Рядок краще розігрувався в моїй голові. =)

Фруктовий сад

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

https://github.com/dorchard/effect-monad/blob/master/docs/ixmonad-fita14.pdf


1
Я правий, що монада Орчарда рівнозначна Аткі, тому що ми можемо переходити від першого до другого, беручи моноїд ендоморфізму, і йти назад за допомогою CPS, що кодує моноїдні додатки в стані переходу?
András Kovács

Це звучить для мене правдоподібно.
Едвард КМЕТТ

Це, виходячи з того, що він сказав мені на ICFP 2013, я вважаю, що Орчард мав намір сім'ї його типу діяти як справжній моноїд, а не як довільна категорія, де деякі стрілки не можуть підключитися, тому до історії може бути більше ніж це, оскільки конструкція Atkey дозволяє вам легко обмежити деякі дії Kleisli від з'єднання з іншими - багато в чому саме це і є версією McBride.
Едвард КМЕТТ

2
Щоб розширити питання про "уважне читання ibind": Введіть псевдонім типу Atkey m i j a = m (a := j) i. Використовуючи це як визначення mAtkey, відновлює два підписи, які ми шукаємо: ireturnAtkin :: a -> m (a := i) iта ibindAtkin :: m (a := j) i -> (a -> m (b := k) j) -> m (b := k) i. Перший з них отримують композиції: ireturn . V. Другий за допомогою (1) побудови функції forall j. (a := j) j -> m (b := k) jза узгодженням шаблону, а потім передаче відновленого aдо другого аргументу ibindAtkin.
WorldSEnder

23

Як простий сценарій, припустимо, у вас є державна монада. Тип стану є складним великим, але всі ці стани можна розділити на два набори: червоний і синій. Деякі операції в цій монаді мають сенс лише в тому випадку, якщо поточний стан є синім. Серед них деякі збережуть стан синього кольору ( blueToBlue), а інші зроблять стан червоним ( blueToRed). У звичайній монаді ми могли писати

blueToRed  :: State S ()
blueToBlue :: State S ()

foo :: State S ()
foo = do blueToRed
         blueToBlue

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

data Red
data Blue

-- assume a new indexed State monad
blueToRed  :: State S Blue Red  ()
blueToBlue :: State S Blue Blue ()

foo :: State S ?? ?? ()
foo = blueToRed `ibind` \_ ->
      blueToBlue          -- type error

Помилка типу спрацьовує тому, що другий індекс blueToRed( Red) відрізняється від першого індексу blueToBlue( Blue).

Як інший приклад, з індексованими монадами ви можете дозволити монаді стану змінити тип свого стану, наприклад, ви могли б мати

data State old new a = State (old -> (new, a))

Ви можете використати вище, щоб створити стан, який є гетерогенним стеком статичного типу. Операції мали б тип

push :: a -> State old (a,old) ()
pop  :: State (a,new) new a

В якості іншого прикладу, припустимо, вам потрібна монада з обмеженим доступом IO, яка не дозволяє отримати доступ до файлів. Ви можете використовувати напр

openFile :: IO any FilesAccessed ()
newIORef :: a -> IO any any (IORef a)
-- no operation of type :: IO any NoAccess _

Таким чином, дії, що мають тип IO ... NoAccess (), статично гарантують, що не мають доступу до файлів. Натомість дія типу IO ... FilesAccessed ()може отримати доступ до файлів. Маючи індексовану монаду, це означає, що вам не потрібно будувати окремий тип для обмеженого вводу-виводу, який вимагає дублювати всі функції, що не стосуються файлів, в обох типах вводу-виводу.


18

Індексована монада - це не конкретна монада, як, наприклад, монада стану, а своєрідне узагальнення поняття монади з додатковими параметрами типу.

Тоді як "стандартне" монадичне значення має тип, Monad m => m aзначення в індексованій монаді було б IndexedMonad m => m i j aде iі jє типом індексу, iтобто типом індексу на початку монадичного обчислення та jв кінці обчислення. Певним чином, ви можете розглядати iяк певний тип введення та jяк тип виводу.

Використовуючи Stateяк приклад, обчислювальний State s aстан підтримує стан типу sпротягом усього обчислення і повертає результат типу a. Індексована версія, IndexedState i j a- це обчислювальний стан, коли стан може змінюватися на інший тип під час обчислення. Початковий стан має тип iі стан, а кінець обчислення має тип j.

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


5

Можливо, важливо поглянути, як використовується індексація в залежних типах (наприклад, в agda). Це може пояснити, як індексація допомагає взагалі, а потім перекласти цей досвід монадам.

Індексація дозволяє встановлювати зв’язки між окремими екземплярами типів. Тоді ви можете обґрунтувати деякі значення, щоб встановити, чи має таке співвідношення.

Наприклад (в agda) ви можете вказати, що деякі натуральні числа пов'язані _<_, а тип повідомляє, які вони числа. Тоді ви можете вимагати, щоб якась функція була надана свідком m < n, тому що лише тоді функція працює правильно - і без надання такого свідка програма не буде компілюватися.

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

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

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