有状态的镜头
镜头操作符具有在有状态上下文中运行的有用变体。它们是通过在运算符名称中用 =
替换~
获得的。
(+~) :: 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) %= ...