Как всегда, терминология, которую используют люди, не совсем последовательна. Есть множество идей, вдохновленных монадами, но, строго говоря, не совсем. Термин «индексированная монада» является одним из числа (включая «монаду» и «параметризованную монаду» (имя Атки для них)) терминов, используемых для характеристики одного такого понятия. (Еще одно такое понятие, если вам интересно, - это «монада параметрических эффектов» Кацуматы, индексируемая моноидом, где return индексируется нейтрально, а привязка накапливается в своем индексе.)
Прежде всего, проверим виды.
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
, как в старой доброй логике Хоара. Утверждениям в программной логике потребовалось менее полувека, чтобы пересечь соответствие Карри-Ховарда и стать типами Haskell. Тип returnIx
говорит: «Вы можете достичь любого выполненного постусловия, просто ничего не делая», что является правилом логики Хоара для «пропуска». Соответствующая композиция - это правило логики Хоара для ";".
Давайте закончим, рассмотрев тип bindIx
, добавив в него все квантификаторы.
bindIx :: forall i. m a i -> (forall j. a j -> m b j) -> m b i
Эти forall
s имеют противоположную полярность. Мы выбираем начальное состояние i
и вычисление, с которого можно начинать i
, с постусловием a
. Мир выбирает любое промежуточное состояние, которое j
ему нравится, но он должен предоставить нам свидетельство того, что постусловие b
выполняется, и из любого такого состояния мы можем продолжить, чтобы b
удержаться. Итак, последовательно мы можем достичь состояния b
из состояния i
. Освободив контроль над состояниями «после», мы можем моделировать непредсказуемые вычисления.
Оба IxMonad
и MonadIx
полезны. Обе модели достоверности интерактивных вычислений в отношении изменения состояния, предсказуемого и непредсказуемого, соответственно. Предсказуемость ценится, когда ее можно получить, но непредсказуемость иногда является фактом жизни. Надеюсь, что этот ответ дает некоторое представление о том, что такое индексированные монады, предсказывая как то, когда они начнут быть полезными, так и когда они прекратятся.
True
/False
values в качестве аргументов типаDVDDrive
? Это какое-то расширение, или здесь действительно вводятся логические значения?