zoom 和 free monads 有困难

Difficulty with zoom and free monads

我正在使用免费的 monad 和 lens,使用免费的 monad 创建我自己的 IO monad 版本:

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

我将其堆叠在状态 monad 之上,如下所示:FreeT MyIO (State GameState) a 其中 GameState 是:

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

现在,我想要的是一种从 GameState 上下文中 "zoom-into" 一个 PlayerState 的方法。像这样:

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

但是我收到这个错误:

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

这个错误似乎与players . element i是遍历有关;如果我从 _players 中删除列表方面并使用普通镜头,那么代码就可以工作了。

关于如何编写这个函数有什么想法吗?

如果您确定永远不会索引到一个不存在的玩家并且不介意一点不安全,您可以使用 unsafeSingular 组合器将 Traversal 变成 Lens,像这样:

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

(此外,也许我会使用 ix 而不是 element,但这与问题无关。)

我们还可以为永远无限的序列构造安全的索引透镜,例如使用 free 包中的 Cofree 定义的流:

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'

但是一个游戏不可能有无限的玩家。