Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 832
Pubby Avatar asked Nov 01 '22 07:11

Pubby


1 Answers

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.

like image 169
danidiaz Avatar answered Nov 15 '22 08:11

danidiaz