I\'ve been using a free monad to build a DSL. As part of the language, there is an input command, the goal is to reflect what types are expected by the input primit
Shortly about indexed monads: They are monads indexed by monoids. For comparison default monad:
class Monad m where
return :: a -> m a
bind :: m a -> (a -> m b) -> m b
-- or `bind` alternatives:
fmap :: (a -> b) -> m a -> m b
join :: m (m a) -> m a
A monoid is a type equiped with mempty - identity element, and (<>) :: a -> a -> a binary associative operation. Raised to type-level we could have Unit type, and Plus associative binary type operation. Note, a list is a free monoid on value level, and HList is on a type level.
Now we can define indexed monoid class:
class IxMonad m where
type Unit
type Plus i j
return :: a -> m Unit a
bind :: m i a -> (a -> m j b) -> m (Plus i j) b
--
fmap :: (a -> b) -> m i a -> m i b
join :: m i (m j a) -> m (Plus i j) a
You can state monad laws for indexed version. You'll notice that for indexes to align, they must obey monoid laws.
With free monad you want equip a Functor with return and join operations. With slightly altererd your definition works:
data FreeIx f i a where
Return :: a -> FreeIx f '[] a -- monoid laws imply we should have `[] as index here!
Free :: f (FreeIx f k a) -> FreeIx f k a
bind :: Functor f => FreeIx f i a -> (a -> FreeIx f j b) -> FreeIx f (Append i j) b
bind (Return a) f = f a
bind (Free x) f = Free (fmap (flip bind f) x)
I have to admit, I'm not 100% sure how Free constructor indexes are justified, but they seem to work. If we consider the function wrap :: f (m a) -> m a of MonadFree class with a law:
wrap (fmap f x) ≡ wrap (fmap return x) >>= f
and a comment about Free in free package
In practice, you can just view a
Free fa as many layers offwrapped around values of typea, where(>>=)performs substitution and grafts new layers offin for each of the free variables.
then the idea is that wrapping values doesn't affect the index.
Yet, you want to lift any f value to an arbitrary indexed monadic value. This is a very reasonable requirement. But the only valid definition forces lifted value to have '[] - Unit or mempty index:
liftF :: Functor f => f a -> FreeIx f '[] a
liftF = Free . fmap Return
If you try to change Return definition to :: a -> FreeIx f k a (k, not [] -- pure value could have an arbitrary index), then bind definition won't type check.
I'm not sure if you can make the free indexed monad work with small corrections only. One idea is to lift an arbitrary monad into an indexed monad:
data FreeIx m i a where
FreeIx :: m a -> FreeIx m k a
liftF :: Proxy i -> f a -> FreeIx f i a
liftF _ = FreeIx
returnIx :: Monad m => a -> FreeIx m i a
returnIx = FreeIx . return
bind :: Monad m => FreeIx m i a -> (a -> FreeIx m j b) -> FreeIx m (Append i j) b
bind (FreeIx x) f = FreeIx $ x >>= (\x' -> case f x' of
FreeIx y -> y)
This approach feels a bit like cheating, as we could always re-index the value.
Another approach is to remind Functor it's a indexed functor, or start right away with indexed functor as in Cirdec's answer.