Simulating interacting stateful objects in Haskell - haskell

I'm currently writing a Haskell program that involves simulating an abstract machine, which has internal state, takes input and gives output. I know how to implement this using the state monad, which results in much cleaner and more manageable code.
My problem is that I don't know how to pull the same trick when I have two (or more) stateful objects interacting with one another. Below I give a highly simplified version of the problem and sketch out what I have so far.
For the sake of this question, let's assume a machine's internal state consists only of a single integer register, so that its data type is
data Machine = Register Int
deriving (Show)
(The actual machine might have multiple registers, a program pointer, a call stack etc. etc., but let's not worry about that for now.) After a previous question I know how to implement the machine using the state monad, so that I don't have to explicitly pass its internal state around. In this simplified example the implementation looks like this, after importing Control.Monad.State.Lazy:
addToState :: Int -> State Machine ()
addToState i = do
(Register x) <- get
put $ Register (x + i)
getValue :: State Machine Int
getValue = do
(Register i) <- get
return i
This allows me to write things like
program :: State Machine Int
program = do
addToState 6
addToState (-4)
getValue
runProgram = evalState program (Register 0)
This adds 6 to the register, then subtracts 4, then returns the result. The state monad keeps track of the machine's internal state so that the "program" code doesn't have to explicitly track it.
In object oriented style in an imperative language, this "program" code might look like
def runProgram(machine):
machine.addToState(6)
machine.addToState(-4)
return machine.getValue()
In that case, if I want to simulate two machines interacting with each other I might write
def doInteraction(machine1, machine2):
a = machine1.getValue()
machine1.addToState(-a)
machine2.addToState(a)
return machine2.getValue()
which sets machine1's state to 0, adding its value onto machine2's state and returning the result.
My question is simply, what is the paradigmatic way to write this kind of imperative code in Haskell? Originally I thought I needed to chain two state monads, but after a hint by Benjamin Hodgson in the comments I realised I should be able to do it with a single state monad where the state is a tuple containing both machines.
The problem is that I don't know how to implement this in a nice clean imperative style. Currently I have the following, which works but is inelegant and fragile:
interaction :: State (Machine, Machine) Int
interaction = do
(m1, m2) <- get
let a = evalState (getValue) m1
let m1' = execState (addToState (-a)) m1
let m2' = execState (addToState a) m2
let result = evalState (getValue) m2'
put $ (m1',m2')
return result
doInteraction = runState interaction (Register 3, Register 5)
The type signature interaction :: State (Machine, Machine) Int is a nice direct translation of the Python function declaration def doInteraction(machine1, machine2):, but the code is fragile because I resorted to threading state through the functions using explicit let bindings. This requires me to introduce a new name every time I want to change the state of one of the machines, which in turn means I have to manually keep track of which variable represents the most up-to-date state. For longer interactions this is likely to make the code error-prone and hard to edit.
I expect that the result will have something to do with lenses. The problem is that I don't know how to run a monadic action on only one of the two machines. Lenses has an operator <<~ whose documentation says "Run a monadic action, and set the target of Lens to its result", but this action gets run in the current monad, where the state is type (Machine, Machine) rather than Machine.
So at this point my question is, how can I implement the interaction function above in a more imperative / object-oriented style, using state monads (or some other trick) to implicitly keep track of the internal states of the two machines, without having to pass the states around explicitly?
Finally, I realise that wanting to write object oriented code in a pure functional language might be a sign that I'm doing something wrong, so I'm very open to being shown another way to think about the problem of simulating multiple stateful things interacting with each other. Basically I just want to know the "right way" to approach this sort of problem in Haskell.

I think good practice would dictate that you should actually make a System data type to wrap your two machines, and then you might as well use lens.
{-# LANGUAGE TemplateHaskell, FlexibleContexts #-}
import Control.Lens
import Control.Monad.State.Lazy
-- With these records, it will be very easy to add extra machines or registers
-- without having to refactor any of the code that follows
data Machine = Machine { _register :: Int } deriving (Show)
data System = System { _machine1, _machine2 :: Machine } deriving (Show)
-- This is some TemplateHaskell magic that makes special `register`, `machine1`,
-- and `machine2` functions.
makeLenses ''Machine
makeLenses ''System
doInteraction :: MonadState System m => m Int
doInteraction = do
a <- use (machine1.register)
machine1.register -= a
machine2.register += a
use (machine2.register)
Also, just to test this code, we can check at GHCi that it does what we want:
ghci> runState doInteraction (System (Machine 3) (Machine 4))
(7,System {_machine1 = Machine {_register = 0}, _machine2 = Machine {_register = 7}})
Advantages:
By using records and lens, there will be no refactoring if I decide to add extra fields. For example, say I want a third machine, then all I do is change System:
data System = System
{ _machine1, _machine2, _machine3 :: Machine } deriving (Show)
But nothing else in my existing code will change - just now I will be able to use machine3 like I use machine1 and machine2.
By using lens, I can scale more easily to nested structures. Note that I just avoided the very simple addToState and getValue functions completely. Since a Lens is actually just a function, machine1.register is just regular function composition. For example, lets say I want a machine to now have an array of registers, then getting or setting particular registers is still simple. We just modify Machine and doInteraction:
import Data.Array.Unboxed (UArray)
data Machine = Machine { _registers :: UArray Int Int } deriving (Show)
-- code snipped
doInteraction2 :: MonadState System m => m Int
doInteraction2 = do
Just a <- preuse (machine1.registers.ix 2) -- get 3rd reg on machine1
machine1.registers.ix 2 -= a -- modify 3rd reg on machine1
machine2.registers.ix 1 += a -- modify 2nd reg on machine2
Just b <- preuse (machine2.registers.ix 1) -- get 2nd reg on machine2
return b
Note that this is equivalent to having a function like the following in Python:
def doInteraction2(machine1,machine2):
a = machine1.registers[2]
machine1.registers[2] -= a
machine2.registers[1] += a
b = machine2.registers[1]
return b
You can again test this out on GHCi:
ghci> import Data.Array.IArray (listArray)
ghci> let regs1 = listArray (0,3) [0,0,6,0]
ghci> let regs2 = listArray (0,3) [0,7,3,0]
ghci> runState doInteraction (System (Machine regs1) (Machine regs2))
(13,System {_machine1 = Machine {_registers = array (0,3) [(0,0),(1,0),(2,0),(3,0)]}, _machine2 = Machine {_registers = array (0,3) [(0,0),(1,13),(2,3),(3,0)]}})
EDIT
The OP has specified that he would like to have a way of embedding a State Machine a into a State System a. lens, as always, has such a function if you go digging deep enough. zoom (and its sibling magnify) provide facilities for "zooming" out/in of State/Reader (it only makes sense to zoom out of State and magnify into Reader).
Then, if we want to implement doInteraction while keeping as black boxes getValue and addToState, we get
getValue :: State Machine Int
addToState :: Int -> State Machine ()
doInteraction3 :: State System Int
doInteraction3 = do
a <- zoom machine1 getValue -- call `getValue` with state `machine1`
zoom machine1 (addToState (-a)) -- call `addToState (-a)` with state `machine1`
zoom machine2 (addToState a) -- call `addToState a` with state `machine2`
zoom machine2 getValue -- call `getValue` with state `machine2`
Notice however that if we do this we really must commit to a particular state monad transformer (as opposed to the generic MonadState), since not all ways of storing state are going to be necessarily "zoomable" in this way. That said, RWST is another state monad transformer supported by zoom.

One option is to make your state transformations into pure functions operating on Machine values:
getValue :: Machine -> Int
getValue (Register x) = x
addToState :: Int -> Machine -> Machine
addToState i (Register x) = Register (x + i)
Then you can lift them into State as needed, writing State actions on multiple machines like so:
doInteraction :: State (Machine, Machine) Int
doInteraction = do
a <- gets $ getValue . fst
modify $ first $ addToState (-a)
modify $ second $ addToState a
gets $ getValue . snd
Where first (resp. second) is a function from Control.Arrow, used here with the type:
(a -> b) -> (a, c) -> (b, c)
That is, it modifies the first element of a tuple.
Then runState doInteraction (Register 3, Register 5) produces (8, (Register 0, Register 8)) as expected.
(In general I think you could do this sort of “zooming in” on subvalues with lenses, but I’m not really familiar enough to offer an example.)

You could also use Gabriel Gonzales' Pipes library for the case you've illustrated. The tutorial for the library is one of the best pieces of Haskell documentation in existence.
Below illustrates a simple example (untested).
-- machine 1 adds its input to current state
machine1 :: (MonadIO m) => Pipe i o m ()
machine1 = flip evalStateT 0 $ forever $ do
-- gets pipe input
a <- lift await
-- get current local state
s <- get
-- <whatever>
let r = a + s
-- update state
put r
-- fire down pipeline
yield r
-- machine 2 multiplies its input by current state
machine2 :: (MonadIO m) => Pipe i o m ()
machine2 = flip evalStateT 0 $ forever $ do
-- gets pipe input
a <- lift await
-- get current local state
s <- get
-- <whatever>
let r = a * s
-- update state
put r
-- fire down pipeline
yield r
You can then combine using the >-> operator. An example would be to run
run :: IO ()
run :: runEffect $ P.stdinLn >-> machine1 >-> machine2 >-> P.stdoutLn
Note that is possible, although a little more involved to have bi-directional pipes, which is gives you communications between both machines. Using some of the other pipes ecosystems, you can also have asynchronous pipes to model non-deterministic or parallel operation of machines.
I believe the same can be achieved with the conduit library, but I don't have much experience with it.

Related

How can I write this simple code using the state monad?

I'm a beginner at Haskell and I've come across a situation where I would like to use the state monad. (Or at least, I think I that's what I'd like to use.) There are a million tutorials for the state monad, but all of them seem to assume that my main goal is to understand it on a deep conceptual level, and consequently they stop just before the part where they say how to actually develop software with it. So I'm looking for help with a simplified practical example.
Below is a very simple version of what my current code looks like. As you can see, I'm threading state through my functions, and my question is simply how to re-write the code using the do notation so that I won't have to do that.
data Machine = Register Int
addToState :: Machine -> Int -> Machine
addToState (Register s) a = Register $ s+a
subtractFromState :: Machine -> Int -> Machine
subtractFromState (Register s) a = Register (s-a)
getValue :: Machine -> Int
getValue (Register s) = s
initialState = Register 0
runProgram = getValue (subtractFromState (addToState initialState 6) 4)
The code simulates a simple abstract machine that has a single register, and instructions to add to the register, subtract from it, and get its value. The "program" at the end initialises the register to 0, adds 6 to it, subtracts 4 and returns the result, which of course is 2.
I understand the purpose of the state monad (or at least think I do), and I expect that it will allow me to re-write this so that I end up with something like
runProgram :: ???????
runProgram = do
put 0
addToState 6
subtractFromState 4
value <- getValue
return value
However, despite all the tutorials I've read I still don't quite know how to transform my code into this form.
Of course, my actual machine's state is much more complicated, and I'm also passing around its output (which will be passed to another machine) and various other things, so I'm quite keen to simplify it. Knowing how to do it for this simplified example would be a very great help.
Update: after Lee's great answer I now know how to do this, but I'm stuck on how to write code in the same elegant form when I have multiple interacting machines. I've asked about that in a new question.
First you need to convert your existing functions to return State Machine a values:
import Control.Monad.State.Lazy
data Machine = Register Int
addToState :: Int -> State Machine ()
addToState i = do
(Register x) <- get
put $ Register (x + i)
subtractFromState :: Int -> State Machine ()
subtractFromState i = do
(Register x) <- get
put $ Register (x - i)
getValue :: State Machine Int
getValue = do
(Register i) <- get
pure i
then you can combine them into a stateful computation:
program :: State Machine Int
program = do
addToState 6
subtractFromState 4
getValue
finally you need can run this computation with evalState to get the final result and discard the state:
runProgram :: Int
runProgram = evalState program (Register 0)

How to limit code changes when introducing state?

I am a senior C/C++/Java/Assembler programmer and I have been always fascinated by the pure functional programming paradigm. From time to time, I try to implement something useful with it, e.g., a small tool, but often I quickly reach a point where I realize that I (and my tool, too) would be much faster in a non-pure language. It's probably because I have much more experience with imperative programming languages with thousands of idoms, patterns and typical solution approaches in my head.
Here is one of those situations. I have encountered it several times and I hope you guys can help me.
Let's assume I write a tool to simulate communication networks. One important task is the generation of network packets. The generation is quite complex, consisting of dozens of functions and configuration parameters, but at the end there is one master function and because I find it useful I always write down the signature:
generatePackets :: Configuration -> [Packet]
However, after a while I notice that it would be great if the packet generation would have some kind of random behavior deep down in one of the many sub-functions of the generation process. Since I need a random number generator for that (and I also need it at some other places in the code), this means to manually change dozens of signatures to something like
f :: Configuration -> RNGState [Packet]
with
type RNGState = State StdGen
I understand the "mathematical" necessity (no states) behind this. My question is on a higher (?) level: How would an experienced Haskell programmer have approached this situation? What kind of design pattern or work flow would have avoided the extra work later?
I have never worked with an experienced Haskell programmer. Maybe you will tell me that you never write signatures because you have to change them too often afterwards, or that you give all your functions a state monad, "just in case" :)
One approach that I've been fairly successful with is using a monad transformer stack. This lets you both add new effects when needed and also track the effects required by particular functions.
Here's a really simple example.
import Control.Monad.State
import Control.Monad.Reader
data Config = Config { v1 :: Int, v2 :: Int }
-- the type of the entire program describes all the effects that it can do
type Program = StateT Int (ReaderT Config IO) ()
runProgram program config startState =
runReaderT (runStateT program startState) config
-- doesn't use configuration values. doesn't do IO
step1 :: MonadState Int m => m ()
step1 = get >>= \x -> put (x+1)
-- can use configuration and change state, but can't do IO
step2 :: (MonadReader Config m, MonadState Int m) => m ()
step2 = do
x <- asks v1
y <- get
put (x+y)
-- can use configuration and do IO, but won't touch our internal state
step3 :: (MonadReader Config m, MonadIO m) => m ()
step3 = do
x <- asks v2
liftIO $ putStrLn ("the value of v2 is " ++ show x)
program :: Program
program = step1 >> step2 >> step3
main :: IO ()
main = do
let config = Config { v1 = 42, v2 = 123 }
startState = 17
result <- runProgram program config startState
return ()
Now if we want to add another effect:
step4 :: MonadWriter String m => m()
step4 = tell "done!"
program :: Program
program = step1 >> step2 >> step3 >> step4
Just adjust Program and runProgram
type Program = StateT Int (ReaderT Config (WriterT String IO)) ()
runProgram program config startState =
runWriterT $ runReaderT (runStateT program startState) config
To summarize, this approach lets us decompose a program in a way that tracks effects but also allows adding new effects as needed without a huge amount of refactoring.
edit:
It's come to my attention that I didn't answer the question about what to do for code that's already written. In many cases, it's not too difficult to change pure code into this style:
computation :: Double -> Double -> Double
computation x y = x + y
becomes
computation :: Monad m => Double -> Double -> m Double
computation x y = return (x + y)
This function will now work for any monad, but doesn't have access to any extra effects. Specifically, if we add another monad transformer to Program, then computation will still work.

Repeatedly applying function to game board in Haskell

I've created a chess game with Haskell and everything seems to be working. However, I'm trying to define the main function of the program so that each time a move is made (which takes two positions and a board as arguments) the resulting board is kept somewhere, so that it can then be used as an argument for the next move. The code looks something like this.
makeMove :: Position -> Position -> Board -> Board
makeMove pos1 pos2 board = ...
I'm aware of the do notation and have a basic understanding of IO in Haskell, but I'm still unsure on how to proceed.
I'm assuming you want your game to be relatively dynamic and to respond to input, hence the IO question.
I'll give a bit of background theory on imperative style commands and IO interpreted as functions, then look at this in Haskell and finally talk about your case from this point of view.
Some background on imperative commands
If this is stuff you know, apologies, but it might help anyway, or it might help others.
In Haskell, we obviously have no direct mutation of variables. But we can consider a (closely) related idea of 'functions on states' - commands which would, in an imperative paradigm, be seen as mutating variables, can be seen as a 'state transformer': a function which, given one state (of the program, world, whatever) outputs another one.
An example:
Suppose we have a state consisting of a single integer variable a. Use the notation x := y meaning 'assign the value of expression y to the variable x'. (In many modern imperative languages this is written x = y, but to disambiguate with the equality relation = we can use a slightly different symbol.) Then the command (call it C)
a := 0
can be seen as something which modifies the variable a. But if we have an abstract idea of a type of 'states', we can see the 'meaning' of C as a function from states to states. This is sometimes written 〚C〛.
So 〚C〛: states -> states, and for any state s, 〚C〛s = <the state where a = 0>. There are much more complicated state transformers that act on much more complicated kinds of state, but the principle is not more complicated than this!
An important way to make new state transformers from old ones is notated by the familiar semicolon. So if we have state transformers C1 and C2, we can write a new state transformer which 'does C1 and then C2' as C1;C2. This is familiar from many imperative programming languages. In fact, the meaning as a state transformer of this 'concatenation' of commands is
〚C1;C2〛: states -> states
〚C1;C2〛s = 〚C2〛(〚C1〛s)
i.e. the composition of the commands. So in a sense, in Haskell-like notation
(;) : (states -> states) -> (states -> states) -> states -> states
c1 ; c2 = c2 . c1
i.e. (;) is an operator on state-transformers which composes them.
Haskell's approach
Now, Haskell has some neat ways of bringing these concepts directly into the language. Instead of having a distinct type for commands (state modifiers without a type, per se) and expressions (which, depending on the imperative context, may also be allowed to modify the state as well as resulting in a value), Haskell somewhat combines these into one. IO () entities represent pure state modifying actions which don't have a meaning as an expression, and IO a entities (where a is not ()) represent (potential) state modifying actions whose meaning as an expression (like a 'return type') is of type a.
Now, since IO () is like a command, we want something like (;), and indeed, in Haskell, we have the (>>) and (>>=) ('bind operators') which act just like it. We have (>>) :: IO a -> IO b -> IO b and (>>=) :: IO a -> (a -> IO b) -> IO b. For a command (IO ()) or command-expression (IO a), the (>>) operator simply ignores the return if there is one, and gives you the operation of doing the two commands in sequence. The (>>=) on the other hand is for if we care about the result of the expression. The second argument is a function which, when applied to the result of the command-expression, gives another command/command-expression which is the 'next step'.
Now, since Haskell has no 'mutable variables', an IORef a-type variable represents a mutable reference variable, to an a-type variable. If ioA is an IORef a-type entity, we can do readIORef ioA which returns an IO a, the expression which is the result of reading the variable. If x :: a we can do writeIORef ioA x which returns an IO (), the command which is the result of writing the value x to the variable. To create a new IORef a, with value x we use newIORef x which gives an IO (IORef a) where the IORef a initially contains the value x.
Haskell also has do notation which you alluded to, which is a nice syntactic sugar for the above. Simply,
do a; b = a >> b
do v <- e; c = e >>= \v -> c
Your case
If we have some IO entity getAMove :: IO (Position, Position) (which might be a simple parser on some user input, or whatever suits your case), we can define
moveIO :: IORef Board -> IO ()
moveIO board =
readIORef board >>= \currentState -> -- read current state of the board
getAMove >>= \(pos1, pos2) -> -- obtain move instructions
writeIORef board (makeMove pos1 pos2 currentState) -- update the board per makeMove
This can also be written using do notation:
moveIO board = do
currentState <- readIORef board; -- read current state of the board
(pos1, pos2) <- getAMove; -- obtain move instructions
writeIORef board (makeMove pos1 pos2 currentState) -- update the board per makeMove
Then, whenever you need a command which updates an IORef Board based on a call to getAMove you can use this moveIO.
Now, if you make appropriate functions with the following signatures, a simple main IO loop can be devised:
-- represents a test of the board as to whether the game should continue
checkForContinue :: Board -> Bool
checkForContinue state = ...
-- represents some kind of display action of the board.
-- could be a simple line by line print.
displayBoardState :: Board -> IO ()
displayBoardState state = ...
-- represents the starting state of the board.
startState :: Board
-- a simple main loop
mainLoop :: IORef Board -> IO ()
mainLoop board = do
currentState <- readIORef board;
displayState currentState;
if checkForContinue currentState then
do moveIO board; mainLoop board
else return ()
main :: IO ()
main = do
board <- newIORef startState;
mainLoop board
You could use recursion to model state as follows:
main :: IO ()
main = do
let initialBoard = ...
gameLoop initialBoard
gameLoop :: Board -> IO ()
gameLoop board | gameOver board = putStrLn "Game over."
| otherwise = do
print board
move <- askUserToMove
let newBoard = applyMove move board
gameLoop newBoard
Here board is "changed" by computing a new one and recursively calling the game loop.

Is it possible to use IO inside State monad, without using StateT and ST

In the code below I manage a game, which owns a list of links.
At each step of the game, I change the game state updating the list of links modified.
As I am learning the State monad, I was trying to apply the State monad technique to this use case.
Nonetheless, at each turn, I need to get a piece of info from IO, using getLine
this gives such a code
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE RecordWildCards #-}
import Control.Monad
import Control.Monad.State.Strict
import qualified Data.List as List
import qualified Control.Monad.IO.Class as IOClass
type Node = Int
type Link = (Node,Node)
type Links = [Link]
type Gateway = Node
type Gateways = [Gateway]
data Game = Game { nbNodes :: Int, links :: Links, gateways :: Gateways }
computeNextTurn :: State Game Link
computeNextTurn = do
g <- get
inputLine <- IOClass.liftIO getLine -- this line causes problem
let node = read inputLine :: Int
let game#(Game _ ls gs) = g
let linkToSever = computeLinkToSever game node
let ls' = List.delete linkToSever ls
let game' = game{links = ls'}
put game'
return linkToSever
computeAllTurns :: State Game Links
computeAllTurns = do
linkToSever <- computeNextTurn
nextGames <- computeAllTurns
return (linkToSever : nextGames)
computeLinkToSever :: Game -> Node -> Link
computeLinkToSever _ _ = (0,1) -- just a dummy value
-- this function doesnt compute really anything for the moment
-- but it will depend on the value of node i got from IO
However I get an error at compilation:
No instance for (MonadIO Data.Functor.Identity.Identity)
arising from a use of liftIO
and I get the same style of error, if I try to use liftM and lift.
I have read some questions that are suggesting StateT and ST, which I don't grasp yet.
I am wondering if my current techique with a simple State is doomed to fail, and that indeed I can not use State, but StateT / ST ?
Or is there a possible operation to simply get the value from getLine, inside the State monad ?
As #bheklilr said in his comment, you can't use IO from State. The reason for that, basically, is that State (which is just shorthand for StateT over Identity) is no magic, so it's not going to be able to use anything more than
What you can already do in its base monad, Identity
The new operations provided by State itself
However, that first point also hints at the solution: if you change the base monad from Identity to some other monad m, then you get the capability to use the effects provided by m. In your case, by setting m to IO, you're good to go.
Note that if you have parts of your computation that don't need to do IO, but require access to your state, you can still express this fact by making their type something like
foo :: (Monad m) => Bar -> StateT Game m Baz
You can then compose foo with computations in StateT Game IO, but its type also makes it apparent that it can't possibly do any IO (or anything else base monad-specific).
You also mentioned ST in your question as possible solution. ST is not a monad transformer and thus doesn't allow you to import effects from some base monad.

STM with fclabels

I built a small game engine to manage a board of squares (currently used for playing a Conway's game of life). All the data is accessed throught lenses from fclabels and State. The engine couples user input and graphic rendering (usual game loop).
The computations between frames can sometimes be slow and long to execute. So I would like to use concurrency to manage the squares, using STM's TVar.
My data is currently represented like this:
data World = World {
… -- window configuration, not important
, _squares :: TVar [Square]
}
mkLabels [''World] -- creates labels, similar to mkLenses
My functions run in the Game Monad, which is defined as follow:
type Game a = StateT World IO a
Using the monadic versions of labels. I use Getters & Setters inside my monad.
I would like to know if there is a way to somehow write new labels that behave like these:
gets :: MonadState f m => Lens (->) f o -> m o
…
puts :: MonadState f m => Lens (->) f o -> o -> m ()
But that takes care of STM (gets would involve readTVar, puts would involve writeTvar, etc.).
If I understand you correctly, you want to define a lens tlens s.t.:
gets tlens
is the same as:
do tvar <- gets squares
sqs <- liftIO $ atomically $ readTVar tvar
return sqs
and where puts tlens sqs is the same as:
do tvar <- gets squares
liftIO $ atomically $ writeTVar tvar sqs
I think this can be answered by looking at the type of gets:
gets :: MonadState f m => Lens (->) f o -> m o
The lens parameter is pure and not monadic. To get at the contents of the TVar you'll need to run code in the IO-monad.
Moreover, the definition of gets in Data.Label.Monadic is (link) is:
gets lens = State.gets (Total.get lens)
where State is Control.Monad.State and Total is Data.Label.Total.
But State.gets takes a pure function, so again you're not going to be able to create a lens which will work with gets.

Resources