有状态的镜头

镜头操作符具有在有状态上下文中运行的有用变体。它们是通过在运算符名称中用 = 替换~获得的。

(+~) :: 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) %= ...