Working out the details of a type indexed free monad

前端 未结 6 1998
孤城傲影
孤城傲影 2021-02-05 12:51

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

6条回答
  •  栀梦
    栀梦 (楼主)
    2021-02-05 13:13

    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 f a as many layers of f wrapped around values of type a, where (>>=) performs substitution and grafts new layers of f in 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.

提交回复
热议问题