Ответ 1
Как всегда, используемая людьми терминология не совсем последовательна. Существует множество вдохновленных монад, но строго говоря, не совсем понятий. Термин "индексированная монада" является одним из ряда (включая "монадная" и "параметризованная монада" (имя Atkey для них)) терминов, используемых для характеристики одного такого понятия. (Еще одно такое понятие, если вам интересно, это Кацамата, "монада параметрического эффекта", индексируемая моноидом, где отдача индексируется нейтрально, а связка накапливается в ее индексе.)
Прежде всего, давайте проверим виды.
IxMonad (m :: state -> state -> * -> *)
То есть тип "вычисления" (или "действие", если вы предпочитаете, но я буду придерживаться "вычисления"), выглядит как
m before after value
где before, after :: state
и value :: *
. Идея состоит в том, чтобы охватить средства безопасного взаимодействия с внешней системой, которая имеет некоторое предсказуемое представление о состоянии. Тип вычисления сообщает вам, какое состояние должно быть before
, какое состояние будет after
и (как в случае с обычными монадами над *
), какой тип value
производит вычисление.
Обычные биты и кусочки *
-wise похожи на монаду, а state
-wise похоже на игру в домино.
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
-notation)
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
"диагональ без изменений" mii
является монадой, но в общем случае mij
- нет. Кроме того, значения не индексируются, а вычисления индексируются, поэтому индексированная монада - это не просто обычная идея создания монады для какой-либо другой категории.
Теперь посмотрите еще раз на тип стрелки Клейсли
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
говорит: "Вы можете достичь любого постусловия, которое выполняется, просто ничего не делая", что является правилом Hoare Logic для "skip". Соответствующая композиция является правилом Hoare Logic для ";".
Давайте закончим, посмотрев на тип 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
полезны. Обе модели валидности интерактивных вычислений относительно изменяющегося состояния, предсказуемые и непредсказуемые, соответственно. Предсказуемость ценна, когда ее можно получить, но непредсказуемость иногда является фактом жизни. Надеемся, что тогда этот ответ дает некоторое представление о том, что такое индексированные монады, предсказывая, когда они начинают приносить пользу и когда они прекращаются.