5

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?

Pubby
  • 51,882
  • 13
  • 139
  • 180
  • 5
    Would it not be easier to instead have `StateT GameState (Free MyIO) a`? If you are wanting to later swap `Free MyIO` for `IO` then this would be the usual way, since there is no transformer version of `IO`. – bheklilr Apr 06 '15 at 19:56
  • 1
    Also, the problem stems from the fact that indexing a list is not a safe operation. The lens library wants you to return a type that can be defaulted, and the standard typeclass for this is `Monoid`. If you simply add `Monoid a => ` to the type signature of `zoomPlayer` then you'll be set. This does restrict what you can return, of course, but you'll have to unless you want to write more code. – bheklilr Apr 06 '15 at 22:06
  • @bheklir My bad, the `a`in the error was a typo; it was actually `a1`. I'm sorry for the confusion. It seems like `FreeF` is the thing it expects to be a monoid, but I don't have a way of writing an instance for that. Maybe I could temporarily wrap it in a list and then unwrap it. – Pubby Apr 07 '15 at 04:45

1 Answers1

2

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.

danidiaz
  • 26,936
  • 4
  • 45
  • 95