How can I use Lenses to perform read-only monadic operation over a sequence held in some state?

StackOverflow https://stackoverflow.com/questions/21996966

  •  16-10-2022
  •  | 
  •  

Question

My data structure looks more or less like this (simplified for the purpose of the question)

data GameObject = GameObject { _num :: Int }
data Game = Game { _objects :: [GameObject] }

I use makeLenses to generate the accessors for both of those. Thus, I am able to do operations en masse like this:

-- loop :: MonadState with Game inside
loop = objects.traversed.num += 1

And this is great.


However, I can't find a way to do an equivalent of this (printing every num):

game <- get
let objects = _objects game
let objectNums = map _num objects
mapM_ print objectNums

I have tried things like

uses (objects.traversed.num) >>= mapM_ print

but the only way I see to use traversed with some state like that involves using monoids, and I don't want to combine the fields; I want to run a monadic action over a part of every element in the elements from some sequence that resides in my state.

So, is this possible using some provided Lens function or do I have to write that myself, somehow?

Était-ce utile?

La solution

What about

mapMOf_ (objects . traversed . num) print

or if you want to apply that to the state of a state monad,

get >>= mapMOf_ (objects . traversed . num) print

? (Perhaps there's a more lens-like way to combine the first line with the state of a state monad. If so, I'd like to learn about it myself.)

Autres conseils

This solution runs an action on state viewed by a lens

objects . traverse . num .=>> putStrLn . ("num: " ++) . show

Example:

{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TupleSections #-}

module Main where

import Control.Monad.IO.Class
import Control.Lens
--       (Getting, Lens', LensLike', lens, makeLenses, view, zoom)
import Control.Lens.Internal.Zoom -- (Focusing)
import Control.Monad.Trans.State.Lazy -- (StateT(StateT), execStateT)

viewM :: Monad m => Getting t s t -> (t -> m a) -> StateT s m a
viewM g f = StateT $ \s -> (, s) <$> f (view g s)

viewEachM ::
     Monad m => LensLike' (Focusing m c) t s -> (s -> m c) -> StateT t m c
viewEachM g f = zoom g $ id .=> f

infix 4 .=>, .=>>

(.=>) :: Monad m => Getting t s t -> (t -> m a) -> StateT s m a
g .=> f = viewM g f

(.=>>) :: Monad m => LensLike' (Focusing m c) t s -> (s -> m c) -> StateT t m c
g .=>> f = viewEachM g f

data GameObject = GameObject
  { _num :: Int
  } deriving (Show)

data Game = Game
  { _objects :: [GameObject]
  } deriving (Show)

makeLenses ''Game

makeLenses ''GameObject

defaultGame = Game {_objects = map GameObject [0 .. 3]}

action :: StateT Game IO ()
action = do
  id .=> putStrLn . ("game: " ++) . show
  objects .=> putStrLn . ("objects: " ++) . show
  objects .=> putStrLn . ("length of objects: " ++) . show . length
  -- use .=>> as alternative
  objects .=>> putStrLn . ("length of objects: " ++) . show . length
  -- if you use traverse you have to use .=>>
  objects . traverse . num .=>> putStrLn . ("num: " ++) . show

main :: IO ()
main = do
  execStateT action defaultGame
  return ()

output:

game: Game {_objects = [GameObject {_num = 0},GameObject {_num = 1},GameObject {_num = 2},GameObject {_num = 3}]}
objects: [GameObject {_num = 0},GameObject {_num = 1},GameObject {_num = 2},GameObject {_num = 3}]
length of objects: 4
length of objects: 4
num: 0
num: 1
num: 2
num: 3

resolver: lts-9.3

  • lens 4.15.4
  • transformers 0.5
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top