Як ніколи, термінологія, яку люди використовують, не є цілком послідовною. Існує безліч натхненних монадів, але строго кажучи - не зовсім понять. Термін "індексована монада" - це один із ряду (включаючи "монадіш" та "параметризовану монаду" (ім'я Аткі для них)) термінів, які використовуються для характеристики одного такого поняття. (Інше таке поняття, якщо вас цікавить, - це "параметричний монада ефекту" Кацумата, індексований моноїдом, де повернення індексується нейтрально, а зв'язок накопичується в його індексі.)
Перш за все, давайте перевіримо види.
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
є корисним. Обидві моделі обґрунтованості інтерактивних обчислень щодо мінливого стану, передбачувані та непередбачувані відповідно. Передбачуваність цінна, коли ти можеш її отримати, але непередбачуваність - це іноді факт життя. Сподіваємось, тоді ця відповідь дає деяку вказівку на те, що таке індексовані монади, передбачуючи, коли вони почнуть бути корисними, і коли вони зупиняться.
True
/False
значень як аргументи типуDVDDrive
? Це якесь розширення, чи булеві фактично тут типи?