5

我正在使用免费的单子和镜头,使用免费的单子来创建我自己的 IO 单子版本:

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

我将它堆叠在一个状态单子的顶部,如下所示:FreeT MyIO (State GameState) a哪里GameState是:

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

现在,我想要的是一种PlayerStateGameState上下文“放大” a 的方法。像这样的东西:

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并使用普通镜头,那么代码就可以工作。

关于如何编写这个函数的任何想法?

4

1 回答 1

2

如果你确定你永远不会索引到一个不存在的播放器并且不介意一点不安全,你可以使用unsafeSingular组合器将 aTraversal变成 a 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,但这与问题无关。)

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

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'

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

于 2015-04-09T06:44:00.087 回答