Search code examples
haskellhaskell-lens

Using lenses for accessing data inside functors


I find lenses very useful for accessing deeply nested data, but frequently "containers" like MVar or TVars throw off some of the nice properties of lenses.

For example:

data SomeStruct = SomeStruct { _b :: Int
                             }
makeLenses ''SomeStruct

data AppState = AppState { _a :: SomeStruct
                         }
makeLenses ''AppState

data App = App { _state :: AppState
               }
makeLenses ''App

I can make new lenses using very nice left-to-right composition:

let v = App (AppState (SomeStruct 3))
in v^.state.a.b

However, if _state were of type TVar, the left-to-right composition breaks down, and lenses feel a lot clunkier to use:

t <- newTVarIO $ AppState (SomeStruct 3)
let v = App t
atomically $ (^.a.b) <$> readTVar (v^.state)

^.a.b gets pushed to the left hand side despite ^.state being the innermost lens. Is there some way I could deal with these sorts of "container" types and lenses in a more ergonomic way?


Solution

  • There is a library (formerly part of lens proper) called lens-action that helps mixing getters and folds with monading actions without yanking you too much out of the lensy world.

    For example, for the type

    data App = App { _state :: TVar AppState }
    

    We could write

    ghci> :t \v -> v^!state.act readTVar.a.b
    \v -> v^!state.act readTVar.a.b :: App -> STM Int
    

    The idea is that instead of using the typical view function (^.) we use its monadic counterpart (^!). And we insert monadic actions using functions like act or acts. Normal getters and folds don't need to be lifted and composition is still done with plain (.).