admin管理员组文章数量:1182577
This is a simplified example of situations I somewhat commonly run into when working with Control.Lens
. It intends to represent a turn-based two-player game.
data PlayerState = PlayerState { _score :: Int, ... }
$(makeLenses ''PlayerState)
data Player = PlayerA | PlayerB
data GameState = GameState { _playerA, _playerB :: PlayerState,
_curPlayer :: Player }
$(makeLenses ''GameState)
Getting a lens for the current player's state given the current player is simple:
playerState :: Player -> Lens' Game PlayerState
playerState PlayerA = playerA
playerState PlayerB = playerB
However, I'd like to be able to focus on the current player's state within a game state:
curPlayerState :: Lens' GameState PlayerState
curPlayerState = ???
-- enabling things along the lines of
-- someGame&curPlayerState.score += 42
One way of implementing the above is using a separate setter and getter:
curPlayerState = lens getter setter
where getter g = g ^. (playerState (g ^. curPlayer))
setter g s = g & (playerState (g ^. curPlayer)) .~ s
However, this seems like I'm underusing or misusing Control.Lens
, as both getter
and setter
combine the same lenses in pretty much the same way, only differing in how the resulting lens is used.
Is there a more general way of combining a Lens' s a
and an a -> Lens' s b
into a Lens' s b
? It feels like I want something similar to >>=
, but I've not yet been able to find the right combination of combinators to achieve the desired result with my lack of understanding of the actual types underlying Lens'
.
This is a simplified example of situations I somewhat commonly run into when working with Control.Lens
. It intends to represent a turn-based two-player game.
data PlayerState = PlayerState { _score :: Int, ... }
$(makeLenses ''PlayerState)
data Player = PlayerA | PlayerB
data GameState = GameState { _playerA, _playerB :: PlayerState,
_curPlayer :: Player }
$(makeLenses ''GameState)
Getting a lens for the current player's state given the current player is simple:
playerState :: Player -> Lens' Game PlayerState
playerState PlayerA = playerA
playerState PlayerB = playerB
However, I'd like to be able to focus on the current player's state within a game state:
curPlayerState :: Lens' GameState PlayerState
curPlayerState = ???
-- enabling things along the lines of
-- someGame&curPlayerState.score += 42
One way of implementing the above is using a separate setter and getter:
curPlayerState = lens getter setter
where getter g = g ^. (playerState (g ^. curPlayer))
setter g s = g & (playerState (g ^. curPlayer)) .~ s
However, this seems like I'm underusing or misusing Control.Lens
, as both getter
and setter
combine the same lenses in pretty much the same way, only differing in how the resulting lens is used.
Is there a more general way of combining a Lens' s a
and an a -> Lens' s b
into a Lens' s b
? It feels like I want something similar to >>=
, but I've not yet been able to find the right combination of combinators to achieve the desired result with my lack of understanding of the actual types underlying Lens'
.
1 Answer
Reset to default 4It's easy enough to write the combinator you're asking for.
unsafeBind :: Lens' s a -> (a -> Lens' s b) -> Lens' s b
unsafeBind a f b s = f (s ^. a) b s
But the fact that we mention s
twice on the right hand side should make your hackles rise. Indeed, this isn't safe. Consider:
aBitOdd :: Player -> Lens' GameState (Player, PlayerState)
aBitOdd pl f s = case pl of
PlayerA -> f (_curPlayer s, _playerA s) <&> \(cur', a') -> s { _curPlayer = cur', _playerA = a' }
PlayerB -> f (_curPlayer s, _playerB s) <&> \(cur', b') -> s { _curPlayer = cur', _playerB = b' }
bad :: Lens' GameState (Player, PlayerState)
bad = unsafeBind curPlayer aBitOdd
exampleGS :: GameState
exampleGS = GameState
{ _playerA = PlayerState { _score = 0 }
, _playerB = PlayerState { _score = 1 }
, _curPlayer = PlayerA
}
exampleUpdate :: (Player, PlayerState)
exampleUpdate = (PlayerB, PlayerState { _score = 2 })
You may verify that both aBitOdd PlayerA
and aBitOdd PlayerB
are valid lenses by themselves. But bad
violates the law saying that set and then get is the identity:
> (set bad exampleUpdate exampleGS ^. bad) == exampleUpdate
False
Now, for the exact invocation you're proposing (unsafeBind curPlayer playerState
), there's no problem. So, take inspiration from the implementation of unsafeBind
, and write this:
curPlayerState ps gs = case gs ^. curPlayer of
PlayerA -> playerA ps gs
PlayerB -> playerB ps gs
-- OR, if playerState is useful on its own in other contexts,
curPlayerState ps gs = playerState (gs ^. curPlayer) ps gs
版权声明:本文标题:haskell - Combining "Lens' s a" and "a -> Lens' s b" into a " 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1738291095a2073168.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
currentPlayerState
lens by hand. It avoids the issues with the general case when it's limited to that kind of specific purpose. – Carl Commented Jan 27 at 3:05