镜头运算符具有在有状态上下文中运行的有用变体。他们通过替换获得~与=在运营商的名称。
(+~) :: Num a => ASetter s t a a -> a -> s -> t (+=) :: (MonadState s m, Num a) => ASetter' s a -> a -> m ()
注意:有状态变体不应更改类型,因此它们具有Lens'或Simple Lens'签名。
如果需要将具有镜头功能的操作链接起来,通常看起来像这样:
change :: A -> A change a = a & lensA %~ operationA & lensB %~ operationB & lensC %~ operationC
这项工作得益于的关联性&。不过,有状态版本更为清晰。
change a = flip execState a $ do lensA %= operationA lensB %= operationB lensC %= operationC
如果lensX是的话id,当然只需将其抬起即可直接执行整个操作modify。
假设此示例状态:
data Point = Point { _x :: Float, _y :: Float } data Entity = Entity { _position :: Point, _direction :: Float } data World = World { _entities :: [Entity] } makeLenses ''Point makeLenses ''Entity makeLenses ''World
我们可以编写类似于经典命令式语言的代码,同时仍然允许我们使用Haskell的优点:
updateWorld :: MonadState World m => m () updateWorld = do -- move the first entity entities . ix 0 . position . x += 1 -- do some operation on all of them entities . traversed . position %= \p -> p `pointAdd` ... -- or only on a subset entities . traversed . filtered (\e -> e ^.position.x> 100) %= ...