Zoom和自由单子的困难

5

我正在尝试使用自由单子和镜头(lens)进行编程,使用自由单子来创建自己的IO单子版本:

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

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

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

现在,我想要的是一种从GameState上下文中“放大”到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中移除列表方面并使用正常的镜头,那么代码就可以工作了。
有关如何编写此函数的任何想法吗?

5
改为 StateT GameState (Free MyIO) a 是否更方便?如果你后面想要将 Free MyIO 替换为 IO,那么这是通常的做法,因为 IO 没有变换器版本。 - bheklilr
1
此外,问题源于对列表进行索引不是安全操作。Lens库希望您返回一个可以默认的类型,而这个类型的标准类型类是Monoid。如果您只需将zoomPlayer的类型签名添加Monoid a =>,那么您就可以了。当然,这会限制您可以返回的内容,但除非您想编写更多代码,否则必须这样做。 - bheklilr
@bheklir 对不起,错误中的 a 是一个打字错误;实际上应该是 a1。对于造成的混淆我感到非常抱歉。看起来 FreeF 是期望成为单子的东西,但我没有办法为它编写一个实例。也许我可以暂时将其包装在列表中,然后再解包。 - Pubby
1个回答

2

如果您确定不会索引到不存在的播放器并且不介意一些不安全因素,您可以使用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'

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接