Search code examples
haskellhaskell-lens

haskell mtl/transformers equivalent of lens' zooming a state


I'm working on a miso-based webapp and I'm trying to wrap the model (state) of a Transition Action InnerModel () into a Transition Action ModelWrapper () where

type Miso.Transition action model = StateT model (Writer [Sub action])

and data ModelWrapper = ClientModel Clients.Model | ...

unfortunately I can't seem to find a way to modify the state's type, or am not sure at all what I have to do.

The documentation shows how to do deal mostly with the lens library. So far I've adapted things like .= into Control.Monad.State.modify, but I can't find an equivalent to zoom, which I need to for running a computation with the unwrapped model as state.

I've tried all of the following with no luck. the closest i got was with execStateT, but I couldn't keep the actions so it was useless.

The code below has my different attempts to tackle it and may provide some context.

updateModel ::
  Action -> 
  Transition Action ModelWrapper ()
updateModel ac = case ac of
  --ShowSection sect -> modify $ \mo -> mo{currentSection=sect}
  --UpdateSubmodel submo -> modify $ \mo -> mo{sectionModel=submo}
  UpdateSubmodel submo -> put submo
  SectionAct sact -> case sact of
    ActionClients clac -> do
      gets $ \(ModelClients mo) -> mo
      (Clients.updateModel sectionPipeback clac)
      --return ()
      --gets (\(ModelClients mo) -> mo)
      --modify ModelClients
      --modify $ \mo -> ModelClients mo
      --ModelClients mo <- get
      --let trans = (Clients.updateModel sectionPipeback clac)
      --    w = execStateT trans mo
      --put $ ModelClients mo
      --let (clmo, acts) = runWriter $ execStateT trans mo
      --let w = execStateT trans mo
      --StateT (ModelClients $ execWriter w) w ()
      --StateT (\ins -> writer )
      --execStateT trans mo
      --execStateT trans mo
      --let (clmo, acts) = runWriter $ execStateT trans mo
      --clmo <- lift $ execStateT trans mo
      --put $ ModelClients clmo
      --lift $ acts
      --pure acts
      --pure $ SeictionAct a
  NoOp -> return ()

Solution

  • zoom from lens is convenient because it uses a lens to capture both a getter and setter at the same time. But without a lens, you can explicitly deal with a getter and setter and do the same thing. Adding the imports:

    import Control.Monad.Trans.Class
    import Control.Monad.Trans.State.Strict
    

    You can then implement a zoom-like function:

    zoomy
      :: Monad m
      => (outer -> inner) -- ^ getter
      -> (inner -> outer -> outer) -- ^ setter
      -> StateT inner m a
      -> StateT outer m a
    zoomy getter setter action = do
      origOuter <- get
      (a, newInner) <- lift $ runStateT action (getter origOuter)
      let newOuter = setter newInner origOuter
      put newOuter
      pure a
    

    Or, if you want to play directly with the data constructors:

    zoomier
      :: Monad m
      => (outer -> inner) -- ^ getter
      -> (inner -> outer -> outer) -- ^ setter
      -> StateT inner m a
      -> StateT outer m a
    zoomier getter setter (StateT action) = StateT $ \origOuter -> do
      (a, newInner) <- action (getter origOuter)
      let newOuter = setter newInner origOuter
      pure (a, newOuter)