As always, the terminology used by people is not entirely consistent. There are many inspired monads, but strictly speaking, not quite concepts. The term “indexed monad” is one of a number (including “monadic” and “parameterized monad” (the Atkey name for them)) of the terms used to characterize one such concept. (Another such concept, if you are interested, is Katsamata, the “parametric effect monad” indexed by a monoid, where the recoil is indexed neutrally and the connective is accumulated in its index.)
First of all, let's check the views.
IxMonad (m :: state -> state -> * -> *)
That is, the type of "calculation" (or "action", if you prefer, but I will stick to the "calculation"), looks like
m before after value
where before, after :: state and value :: * . The idea is to encompass means of safe interaction with an external system that has some predictable view of the state. The type of calculation tells you what state should be before , what state will be after and (as is the case with regular monads over * ), what type of value does the calculation.
Regular bits and pieces * -wise look like a monad, and state -wise looks like a domino game.
ireturn :: a -> miia -- returning a pure value preserves state ibind :: mija -> -- we can go from i to j and get an a, thence (a -> mjkb) -- we can go from j to k and get ab, therefore -> mikb -- we can indeed go from i to k and get ab
The concept of “Claysley arrow” (a function that performs the calculations) generated in this way
a -> mijb -- values a in, b out; state transition i to j
and we get the composition
icomp :: IxMonad m => (b -> mjkc) -> (a -> mijb) -> a -> mikc icomp fg = \ a -> ibind (ga) f
and, as always, laws accurately guarantee that ireturn and icomp give us a category
ireturn 'icomp' g = g f 'icomp' ireturn = f (f 'icomp' g) 'icomp' h = f 'icomp' (g 'icomp' h)
or in the comedy fake C / Java / whatever
g(); skip = g() skip; f() = f() {g(); h()}; f() = h(); {g(); f()}
Why bother? Model the “rules” of interaction. For example, you cannot eject a DVD if it is not on the disc, and you cannot insert the DVD into the disc if it is already there. So
data DVDDrive :: Bool -> Bool -> * -> * where -- Bool is "drive full?" DReturn :: a -> DVDDrive iia DInsert :: DVD -> -- you have a DVD DVDDrive True ka -> -- you know how to continue full DVDDrive False ka -- so you can insert from empty DEject :: (DVD -> -- once you receive a DVD DVDDrive False ka) -> -- you know how to continue empty DVDDrive True ka -- 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 = ka ibind (DInsert dvd j) k = DInsert dvd (ibind jk) ibind (DEject j) k = DEject j $ \ dvd -> ibind (j dvd) k
With this in mind, we can define "primitive" commands
dInsert :: DVD -> DVDDrive False True () dInsert dvd = DInsert dvd $ DReturn () dEject :: DVDrive True False DVD dEject = DEject $ \ dvd -> DReturn dvd
from which others are assembled with ireturn and ibind . Now I can write (lending do -notation)
discSwap :: DVD -> DVDDrive True True DVD discSwap dvd = do dvd' <- dEject; dInsert dvd ; ireturn dvd'
but not physically impossible
discSwap :: DVD -> DVDDrive True True DVD discSwap dvd = do dInsert dvd; dEject -- ouch!
In addition, you can directly define one primitive command
data DVDCommand :: Bool -> Bool -> * -> * where InsertC :: DVD -> DVDCommand False True () EjectC :: DVDCommand True False DVD
and then instantiate the generic template
data CommandIxMonad :: (state -> state -> * -> *) -> state -> state -> * -> * where CReturn :: a -> CommandIxMonad ciia (:?) :: cija -> (a -> CommandIxMonad cjkb) -> CommandIxMonad cikb instance IxMonad (CommandIxMonad c) where ireturn = CReturn ibind (CReturn a) k = ka ibind (c :? j) k = c :? \ a -> ibind (ja) k
In fact, we said what the primitive arrows of Claysley (what is "dominoes"), and then built a suitable concept of a "sequence of calculations" over them.
Note that for each indexed monad m “diagonal without changes” mii is a monad, but in the general case mij is not. In addition, values are not indexed, but calculations are indexed, so an indexed monad is not just the usual idea of creating a monad for any other category.
Now look again at the type of arrow Claysley
a -> mijb
We know that we must be able to start i , and we predict that any continuation will start from state j . We know a lot about this system! This is not a risky operation! When we put the DVD in the drive, it comes in! The DVD drive has nothing to do with the state after each command.
But this is not so in general when interacting with the world. Sometimes you may need to give up some control and let the world do what it likes. For example, if you are a server, you can offer your client a choice, and your session state will depend on what he chooses. The operation "offer selection" on the server does not determine the resulting state, but the server must be able to perform in any case. In the above sense, this is not a "primitive command", so indexed monads are not a good tool for modeling an unpredictable scenario.
Which tool is better?
type f :-> g = forall state. f state -> g state class MonadIx (m :: (state -> *) -> (state -> *)) where returnIx :: x :-> mx flipBindIx :: (a :-> mb) -> (ma :-> mb) -- tidier than bindIx
Scary cookies? Not entirely for two reasons. Firstly, it is more like what a monad is, because it is a monad, but more (state → *) and not * . Two, if you look at the type of arrow Claysley,
a :-> mb = forall state. a state -> mb state
You get a type of computation with the precondition a and the postcondition b , as in "Good old Hoar logic." Statements in the program logic took less than half a century to cross Curry-Howard correspondence and become Haskell types. The returnIx type says: “You can achieve any postcondition that is satisfied just by doing nothing,” which is the Hoare Logic rule for “skip”. The corresponding composition is the Hoare Logic rule for ";".
Let's finish by looking at the bindIx type, putting all the quantifiers in.
bindIx :: forall i. mai -> (forall j. aj -> mbj) -> mbi
These forall have the opposite polarity. We choose the initial state i and the calculation, which can begin with i , with the postcondition a . The world chooses any intermediate state j he likes, but he must give us proof that the postcondition b holds, and from any such state we can go to b . So, in order, we can reach condition b from state i . Having freed our “after” state, we can model unpredictable calculations.
Both IxMonad and MonadIx are useful. Both models of the validity of interactive computing relative to a changing state, predictable and unpredictable, respectively. Predictability is valuable when it can be obtained, but unpredictability is sometimes a fact of life. We hope that then this answer gives some idea of what indexed monads are, predicting when they begin to be useful and when they stop.