有狀態的鏡頭

鏡頭操作符具有在有狀態上下文中執行的有用變體。它們是通過在運算子名稱中用 = 替換~獲得的。

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