Как всегда, терминология, которую используют люди, не совсем последовательна. Есть множество идей, вдохновленных монадами, но, строго говоря, не совсем. Термин «индексированная монада» является одним из числа (включая «монаду» и «параметризованную монаду» (имя Атки для них)) терминов, используемых для характеристики одного такого понятия. (Еще одно такое понятие, если вам интересно, - это «монада параметрических эффектов» Кацуматы, индексируемая моноидом, где 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
Эти foralls имеют противоположную полярность. Мы выбираем начальное состояние iи вычисление, с которого можно начинать i, с постусловием a. Мир выбирает любое промежуточное состояние, которое jему нравится, но он должен предоставить нам свидетельство того, что постусловие bвыполняется, и из любого такого состояния мы можем продолжить, чтобы bудержаться. Итак, последовательно мы можем достичь состояния bиз состояния i. Освободив контроль над состояниями «после», мы можем моделировать непредсказуемые вычисления.
Оба IxMonadи MonadIxполезны. Обе модели достоверности интерактивных вычислений в отношении изменения состояния, предсказуемого и непредсказуемого, соответственно. Предсказуемость ценится, когда ее можно получить, но непредсказуемость иногда является фактом жизни. Надеюсь, что этот ответ дает некоторое представление о том, что такое индексированные монады, предсказывая как то, когда они начнут быть полезными, так и когда они прекратятся.
True/Falsevalues в качестве аргументов типаDVDDrive? Это какое-то расширение, или здесь действительно вводятся логические значения?