Search code examples
haskellstate-monadhaskell-lensfree-monad

Difficulty with zoom and free monads


I am mucking around with free monads and lens, using the free monad to create my own version of the IO monad:

data MyIO next
    = LogMsg String next
    | GetInput (String -> next)
    deriving (Functor)

I am stacking this on top of a state monad like so: FreeT MyIO (State GameState) a where GameState is:

data GameState = GameState { _players :: [PlayerState] }

Now, what I would like to have is a way to "zoom-into" a PlayerState from a GameState context. Something like this:

zoomPlayer :: Int -> FreeT MyIO (State PlayerState) a -> FreeT MyIO (State GameState) a
zoomPlayer i prog = hoistFreeT (zoom (players . element i)) prog

But I'm getting this error:

No instance for (Data.Monoid.Monoid a1)
  arising from a use of ‘_head’

This error seems related to the fact that players . element i is a traversal; if I remove the list aspect from _players and use normal lens then the code works.

Any ideas on how to write this function?


Solution

  • If you are sure you'll never index into a non-existing player and don't mind a little unsafety, you can use the unsafeSingular combinator to turn a Traversal into a Lens, like this:

    zoomPlayer :: Int -> FreeT MyIO (State PlayerState) a -> FreeT MyIO (State GameState) a
    zoomPlayer i prog = hoistFreeT (zoom (players . unsafeSingular (element i))) prog
    

    (Also, perhaps I would use ix instead of element, but that's unrelated to the problem.)

    We can also construct safe indexing lenses for always-infinite sequences, like streams defined using Cofree from the free package:

    import Control.Lens (Lens', _Wrapped')
    import Control.Comonad.Cofree (Cofree, telescoped)
    import Data.Functor.Identity
    import Control
    
    sureIx :: Int -> Lens' (Cofree Identity a) a
    sureIx i = telescoped $ replicate i _Wrapped'
    

    But a game is unlikely to have infinite players.