Haskell语言有状态透镜

示例

镜头运算符具有在有状态上下文中运行的有用变体。他们通过替换获得~与=在运营商的名称。

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