I'm trying to implement a lexer in Haskell. For easy console input and output, I've used an intermediate data type Transition Table.
type TransitionTable = [(Int, Transitions String Int)]
type Transitions a b = [(a, b)]
I want to take input from the user for all the states and transitions. I do not want to take the total number of states before hand. I want it to keep taking input for the transitions of each state until the user types "--" . If the user types "---", the current state is discarded and the input terminates.
After a lot of attempts I came up with this, which I think is horrible code.
-- |A function to emulate the while loop for easy IO functionality.
-- Defination:- while #comparator #func #start:
-- *comparator #arg: A function which returns True or False on the basis of #arg.
-- The loop stops when False is returned.
-- *func: The function which is executed repeadly.
-- It is responsible for returning the next #arg for the comparator on the basis of the current #arg.
-- *start: The starting value of #arg to pass to the comparator.
while :: (Monad m) => (a -> Bool) -> (a -> m a) -> a -> m a
while comparator func start =
if comparator start then do
nxt <- func start
while comparator func nxt
else
return start
-- |A modification of putStr which flushes out stdout. Corrents buffer problems.
myPutStr :: String -> IO ()
myPutStr str = putStr str >> hFlush stdout >> return ()
-- Takes input from the console to generate a TransitionTable.
inputTransitionTable :: IO TransitionTable
inputTransitionTable = do
putStrLn "Type -- for next state and --- for completing input entering."
retVal <- while notFinished takeInfo (0, [])
return (snd retVal)
where
-- Returns True when input entry is over.
notFinished (i, _) = i > -1
-- Takes the current state number and the incomplete corrosponding transition table which is populated
-- with user input. Input ends when user enters "---". State number is set to -1 when input is over.
takeInfo (i, states) = do
putStrLn ("Adding transitions to state " ++ show i ++ ": ")
retVal <- while entryNotFinished takeStateInfo ("", [])
let (inpStr, stateInfo) = retVal
case inpStr == "---" of
True -> return (-1, states)
False -> return (i+1, states ++ [(i, stateInfo)])
-- Checks if input entry is over. Returns False if finished.
entryNotFinished (s, _)
| s == "--" || s == "---" = False
| otherwise = True
-- Takes the input state number along with the corresponding transitions.
-- Input ends when the user enters "--".
takeStateInfo (str, state_info) = do
myPutStr "\tEnter transitions symbol: "
symbol <- getLine
if symbol == "--" || symbol == "---" then
return (symbol, state_info)
else do
myPutStr "\t\tEnter the transition state number: "
state' <- getLine
let state = read state' :: Int
return (str, (symbol, state):state_info)
Basically this is how it runs:
*Main> x <- inputTransitionTable
Type -- for next state and --- for completing input entering.
Adding transitions to state 0:
Enter transitions symbol: a
Enter the transition state number: 1
Enter transitions symbol: b
Enter the transition state number: 2
Enter transitions symbol: --
Adding transitions to state 1:
Enter transitions symbol: a
Enter the transition state number: 2
Enter transitions symbol: b
Enter the transition state number: 3
Enter transitions symbol: --
Adding transitions to state 2:
Enter transitions symbol: a
Enter the transition state number: 3
Enter transitions symbol: --
Adding transitions to state 3:
Enter transitions symbol: --
Adding transitions to state 4:
Enter transitions symbol: ---
(0.03 secs, 344420 bytes)
-- Output
*Main> prettyPrintTransitionTable x
State Transitions
0 ("b",2) ("a",1)
1 ("b",3) ("a",2)
2 ("a",3)
3
Is there a better way to do this?
It could be as simple as this, if you add the "deriving Read" declarations and don't care about interaction.
main = do
allInput <- getContents -- scarfs all stdin up to eof
let inLines = lines allInput
let (tableLines, _:otherlines) = break (== "endtable") inLines
let table = ((read $ unlines tableLines) :: TransitionTable)
-- process otherlines here
As others have suggested, for a tasks related to parsing you should look at Parsec. While I have no experience with it, I can still suggest an alternative way of writing your parsing application.
module Main where
import Control.Monad (liftM)
computeTransitions :: [String] -> [(Int, [(String, Int)])]
computeTransitions is = foldl folder [] is
where
getState states = if null states then (0, []) else last states
getTransition transitions = if null transitions then 0 else (snd $ head transitions)
prepend state transition = let (c, ts) = state in (c, transition:ts)
swapLastState states state = if null states then [state] else init states ++ [state]
folder states i =
let currentState = getState states
currentTransition = getTransition (snd currentState)
in case i == "--" of False -> swapLastState states (prepend currentState (i, currentTransition + 1))
True -> states ++ [((fst currentState) + 1, [])]
main = do
inputLines <- liftM (takeWhile (/="---")) (liftM lines getContents)
let result = computeTransitions inputLines
mapM_ (\(s, t) -> putStrLn $ show s ++ "\t" ++ show t) result
I don't know if in your requirements was to print out the intermediary messages, but the computation of the transitions can be transformed to a fold operation (or foldM if you want to print intermediary messages); and instead of the "while" loop I've used the takeWhile function lifted to the Monadic space (so I can apply it on the type IO [String]).
Also note that getContents is lazy in evaluation, and in combination with lines will perform as a "while read line".
Edit:
With #pat's suggestion (and what hlint came up with), here is the refactored version:
module Main where
import Control.Monad (liftM)
computeTransitions :: [String] -> [(Int, [(String, Int)])]
computeTransitions = foldl folder []
where
getState [] = (0, [])
getState states = last states
getTransition [] = 0
getTransition ((_, t):_) = t
prepend (c,ts) transition = (c, transition:ts)
swapLastState [] state = [state]
swapLastState states state = init states ++ [state]
folder states i =
let currentState = getState states
currentTransition = getTransition (snd currentState)
in if i == "--"
then states ++ [(fst currentState + 1, [])]
else swapLastState states (prepend currentState (i, currentTransition + 1))
main = do
inputLines <- liftM (takeWhile (/="---") . lines) getContents
mapM_ (\(s, t) -> putStrLn $ show s ++ "\t" ++ show t) (computeTransitions inputLines)
Related
=== Evaluation of HStatement (bar if and selection) ===
evalStatement_ :: Env -> HStatement -> IOThrowsError ()
evalStatement_ env (Do cond expr) = evalVal env cond >>= \x -> case x of
HBool False -> return ()
HBool True -> do
traverse_ (evalStatement_ env) expr
evalStatement_ env (Do cond expr)
evalStatement_ env (Skip skip) = return ()
evalStatement_ env (Print (HString val)) = getVar env val >>= \x -> liftIO $ putStrLn $ show x
evalStatement_ env (Print val) = evalVal env val >>= \x -> liftIO $ putStrLn $ show x
evalStatement_ env (Eval val) = do
result <- evalVal env val
return ()
=== Representation of Selection & If ===
parseIf :: Parser HStatement
parseIf = do
string "("
cond <- parseArith
string ")->"
spaces
expr <- many1 $ parseStatements
spaces
return $ If (cond, expr)
parseSelection :: Parser HStatement
parseSelection = do
_ <- string "if"
spaces
selection <- many1 $ parseIf
spaces
_ <- string "fi"
spaces
return $ Selection selection
N.B : If evaluation of selection is changed to the below, then the program runs and terminates and does give output:
evalStatement_ env (Selection if_ selection fi_ n) = evalStatement_ env (selection !! randIdx n) >>= \res -> if res == ()
then return ()
else return ()
The output however gives varying amounts of the even integers between 1 and 10. For example one output would print all even integers and another prints on the number 6.
tldr; is there a way to execute a random function from a list of functions randomly and if the result is not ideal, reexecute the function to execute a random function until the result is idea?
I'm trying to write a function which executes a random entry in a list of functions. Each entry in the list is constructed in the following way: If (HVal, HStatement) -- If (Guard,Statement) where
HVal:
data HVal
= HInteger Integer
HBool Bool
HString String
HList [HVal]
Length HVal
Arith HVal Op HVal
Assign String HVal
deriving (Eq, Read)
HStatement:
data HStatement
= Eval HVal
| Print HVal
| Do HVal [HStatement]
| If (HVal, [HStatement])
| Selection [HStatement]
deriving (Eq, Read)
What I tried so far was using Asyncs race function as per my question yesterday. My thinking behind this was if there exists a list of n entries in a list that are constructed as If (HVal, HStatement), then running a race function over a list that only contain a list of HStatements whose guards were evaluated to true would return the function that executes the fastest of the true guards.
Trying to incorporate this raceAll behaviour into my code base proved to be too difficult to me due to the constraint of IO. I redid the approach by considering using a random number generator.
So now I'm generating a random index of the list of guard statements pairs. I execute the entry in this list and perform a case analysis. If the output is () then I call the function again otherwise I return the output. To do this I'm using two functions wherein selection represents a list of if's:
evalStatement_ env (If (cond, expr)) = evalVal env cond >>= \x -> case x of
HBool False -> return ()
HBool True -> traverse_ (evalStatement_ env) expr
evalStatement_ env (Selection selection) = evalStatement_ env (selection !! randIdx 1) >>= \res -> case res of -- randIdx produces an index between 0 and 1 representing the two branches in the selection block that could be taken
() -> evalStatement_ env (Selection selection)
_ -> return $ res
randIdx n = unsafePerformIO (getStdRandom (randomR (0, n - 1)))
Take the following program as example:
f := [1 2 3 4 5 6 7 8 9 10]
n := 0
N := len(f)
Do (n < N)->
a := f.n
if ((a % 2) = 0)-> print(a)
((a % 1) = 1)-> print(a)
fi
n := n + 1
Od
What occurs here is that the program gives no output at all and doesn't terminate. What I would have expected to happen was that a random index is generated between 0 and the number of possible branches minus one. Then this would have been evaluated and if it returned a value, this would have been taken otherwise if it was the unit type, a new random index would have been generated and that would have been used.
I can execute the program however if the function definition for selection is traverse_ (evalStatement_ env) selection but I'm just unsure on how to achieve this pseudo randomness. Any help would be appreciated!
You say,
If the output is () then I call the function again otherwise I return the output.
This is a strange thing to say, because there is no "otherwise" -- if your thing returns () sometimes, it can never return anything but (), because there is no other value with the same type! In particular, this means it is impossible to reach the _ branch of your case.
In your language as shown here, statements fundamentally do not compute data. Perhaps you should change your Selection constructor to take an [(HVal, HStatement)] instead of an [HStatement] (representing pairs of the computation that returns something interesting that you can case on together with the statement to execute in some appropriate branch of that case), or modify the type that statements compute to something richer than ().
So I'm trying to make a little program that can take in data captured during an experiment, and for the most part I think I've figured out how to recursively take in data until the user signals there is no more, however upon termination of data taking haskell throws Exception: <<loop>> and I can't really figure out why. Here's the code:
readData :: (Num a, Read a) => [Point a] -> IO [Point a]
readData l = do putStr "Enter Point (x,y,<e>) or (d)one: "
entered <- getLine
if (entered == "d" || entered == "done")
then return l
else do let l = addPoint l entered
nl <- readData l
return nl
addPoint :: (Num a, Read a) => [Point a] -> String -> [Point a]
addPoint l s = l ++ [Point (dataList !! 0) (dataList !! 1) (dataList !! 2)]
where dataList = (map read $ checkInputData . splitOn "," $ s) :: (Read a) => [a]
checkInputData :: [String] -> [String]
checkInputData xs
| length xs < 2 = ["0","0","0"]
| length xs < 3 = (xs ++ ["0"])
| length xs == 3 = xs
| length xs > 3 = ["0","0","0"]
As far as I can tell, the exception is indication that there is an infinite loop somewhere, but I can't figure out why this is occurring. As far as I can tell when "done" is entered the current level should simply return l, the list it's given, which should then cascade up the previous iterations of the function.
Thanks for any help. (And yes, checkInputData will have proper error handling once I figure out how to do that.)
<<loop>> basically means GHC has detected an infinite loop caused by a value which depends immediately on itself (cf. this question, or this one for further technical details if you are curious). In this case, that is triggered by:
else do let l = addPoint l entered
This definition, which shadows the l you passed as an argument, defines l in terms of itself. You meant to write something like...
else do let l' = addPoint l entered
... which defines a new value, l', in terms of the original l.
As Carl points out, turning on -Wall (e.g. by passing it to GHC at the command line, or with :set -Wall in GHCi) would make GHC warn you about the shadowing:
<interactive>:171:33: warning: [-Wname-shadowing]
This binding for ālā shadows the existing binding
bound at <interactive>:167:10
Also, as hightlighted by dfeuer, the whole do-block in the else branch can be replaced by:
readData (addPoint l entered)
As an unrelated suggestion, in this case it is a good idea to replace your uses of length and (!!) with pattern matching. For instance, checkInputData can be written as:
checkInputData :: [String] -> [String]
checkInputData xs = case xs of
[_,_] -> xs ++ ["0"]
[_,_,_] -> xs
_ -> ["0","0","0"]
addPoint, in its turn, might become:
addPoint :: (Num a, Read a) => [Point a] -> String -> [Point a]
addPoint l s = l ++ [Point x y z]
where [x,y,z] = (map read $ checkInputData . splitOn "," $ s) :: (Read a) => [a]
That becomes even neater if you change checkInputData so that it returns a (String, String, String) triple, which would better express the invariant that you are reading exactly three values.
I'm experiencing some problems and can't find their reason. I am currently using the most recent version of GHCi portable - but to face the truth: It is my first time using Haskell, so as usual the problem is probably the user and not so much the system...
Problems that arise include:
I am not completely sure I got the difference between let x = 0 and x <- 0 right. I understand that let is to be used with pure functions, <- with side effects (IO)? Someone please explain that to me once again.
I have mismatchings between types, namely String and (String,[Char]) (and sometimes others...). The compiler tells me that String was expected, although I clearly defined the function as (String,String). What's going on? Did I somewhere make a mistake with the pattern matching?
The recursion does not work as expected (i.e. doesn't work at all apparently). If someone could help me with that, I would be very grateful.
Here's what I want to do:
I am trying to write a little program that implements a finite state machine accepting a word. That means it takes a set of states, one of which is the start state, a list of accepting states, and a number of transition rules. (The alphabets which represent the possible input and states are somewhat implicit.) I don't want to go into too much detail about FSMs here.
However, this is how I figured a way to define such a FSM could look:
"a(b+|c+)"
"start"
["b","c"]
[
("start", [('a',"a"), ('_',"reject")]),
("a", [ ('b',"b"), ('c',"c"), ('_',"reject")]),
("b", [ ('b',"b"), ('_',"reject")]),
("c", [ ('c',"c"), ('_',"reject")]),
("reject", [ ('_',"reject")])
]
In the first line, we have short description of what the FSM is supposed to accept (in form of a regex in this case). It is only used to display it once.
The second line defines the start state, the third line a list of accepting states.
All following lines together are the transition rules. In this example, if we are in state "start" and read an input 'a', the next state is "a", if we read anything else, it is "reject". (I am aware that I have not yet implemented the '_' meaning an else and the program will crash if an input is read for which no transition is defined.)
So here comes the program:
module FSM where
import System.IO
main :: IO ()
main = do
putStr "Enter file name: "
fileName <- getLine
(description, startState, acceptingStates, stateTransitions) <- (readDef fileName)
putStrLn ("FSM description: " ++ description)
putStr "Enter FSM input: "
input <- getLine
let input = reverse input
putStrLn "----------------"
let (finalState, oldStates) = changeState input startState stateTransitions
putStrLn (oldStates ++ finalState)
checkAcception finalState acceptingStates
--reads the specified .fsm file and returns
-- the description of the FSM (first line),
-- the start state (second line),
-- the list of accepting states (third line),
-- and the list of tuples containing all states and transitions (remaining lines)
readDef :: String -> IO (String, String, [String], [(String, [(Char,String)])])
readDef fileName = do
contents <- readFile (fileName ++ ".fsm")
let lineList = lines contents
let description = read (head lineList)
let startState = read (lineList !! 1)
let acceptingStates = read (lineList !! 2)
let stateTransitions = read (filter (/='\t') (concat (drop 3 lineList)))
return (description, startState, acceptingStates, stateTransitions)
--recursive function that takes the input, start state, and state transitions
--and computes the new state by a call to itself with the old state and a part of the input
changeState :: String -> String -> [(String, [(Char,String)])] -> (String, String)
changeState startState [] _ = (startState, "")
changeState startState (x:xs) stateTransitions = do
let (currentState, oldStates) = changeState xs startState stateTransitions
let newState = findKey x (findKey currentState stateTransitions)
let oldStates = (oldStates ++ currentState ++ " -(" ++ [x] ++ ")-> ")
return (newState, oldStates)
--helper function to find a key in a list of tuples and return the corresponding value
--(because we are not using the map notation in the .fsm file)
findKey :: (Eq k) => k -> [(k,v)] -> v
findKey key xs = snd . head . filter (\(k,v) -> key == k) $ xs
--checks for a given state whether or not it is in the list of accepting states
checkAcception :: String -> [String] -> IO ()
checkAcception finalState acceptingStates = do
let accept = any (==finalState) acceptingStates
if accept
then putStrLn "Input accepted!!"
else putStrLn "Input rejected!!"
The idea is to have the user choose a file from which the definition is loaded (readDef, works like a charm). He is then prompted to enter some input the FSM works on.
The recursive changeState then does the actual work (doesn't work as well...).
Finally, the sequence of states and transitions is displayed and it is checked whether the final state is an accepting state (checkAcceptance).
Now, don't try to optimize what I have written. I know, the way the definition is modeled can be improved and many of the lines I wrote can be written far shorter using some high order Haskell foo. But please just help me with the issues listed above (and of course help me make it work).
Thanks a lot in advance.
One last thing: I'm trying some Haskell for a seminar at my university, so if someone from the Software Architecture Group googled my code and reads this: Hi :)
You can get things to compile by just changing the second clause of the changeState function to:
changeState startState (x:xs) stateTransitions =
let (currentState, oldStates) = changeState xs startState stateTransitions
newState = findKey x (findKey currentState stateTransitions)
oldStates2 = (oldStates ++ currentState ++ " -(" ++ [x] ++ ")-> ")
in (newState, oldStates2)
We've 1) removed the do, 2) combined the let clauses and 3) renamed the second occurrence of the oldState variable to oldState2. In Haskell we don't redefine variables - we just create a variable with a new name. Complete code is available here: http://lpaste.net/118404
When you write:
(new, old) = changeState ...
you are saying that changeState is a pure function. If you define changeState with do ... return (...) you are saying it is a monadic computation and when you call it you need to use the arrow <- in a do block:
(new, old) <- changeState ...
Since changeState is a pure function (doesn't need to do IO) you might as well keep it as a pure function, so there is no reason to use do and return.
The problem is that do notation and the return function don't do what you think they do. In Haskell: return doesn't signify that a function should end (even though it's most commonly seen at the end of functions); it just means that the argument should be wrapped in a Monad. Because the type of your function with all arguments applied is (String,String) the compiler thought you were trying to use something like this: (won't actually compile without GHC extensions, and will throw exceptions if used because I used undefined)
instance Monad ((,) String) where
(>>=) = undefined :: (String,a) -> (a -> (String,b)) -> (String,b)
return = undefined :: a -> (String,a)
But the compiler already knew that (String,String) -> (String,String) doesn't match a -> (String,a) so it didn't get as far as checking whether or not the instance exists.
Fixing this problem revealed another one: you define oldStates twice in the same function, which doesn't work in Haskell unless the two definitions are in different scopes.
This is your function modified to compile properly, but I haven't tested it.
changeState :: String -> String -> [(String, [(Char,String)])] -> (String, String)
changeState startState [] _ = (startState, "")
changeState startState (x:xs) stateTransitions = let
(currentState, oldStates) = changeState xs startState stateTransitions
newState = findKey x (findKey currentState stateTransitions)
oldStates' = (oldStates ++ currentState ++ " -(" ++ [x] ++ ")-> ")
in (newState, oldStates')
Basically I would like to find a way so that a user can enter the number of test cases and then input their test cases. The program can then run those test cases and print out the results in the order that the test cases appear.
So basically I have main which reads in the number of test cases and inputs it into a function that will read from IO that many times. It looks like this:
main = getLine >>= \tst -> w (read :: String -> Int) tst [[]]
This is the method signature of w: w :: Int -> [[Int]]-> IO ()
So my plan is to read in the number of test cases and have w run a function which takes in each test case and store the result into the [[]] variable. So each list in the list will be an output. w will just run recursively until it reaches 0 and print out each list on a separate line. I'd like to know if there is a better way of doing this since I have to pass in an empty list into w, which seems extraneous.
As #bheklilr mentioned you can't update a value like [[]]. The standard functional approach is to pass an accumulator through a a set of recursive calls. In the following example the acc parameter to the loop function is this accumulator - it consists of all of the output collected so far. At the end of the loop we return it.
myTest :: Int -> [String]
myTest n = [ "output line " ++ show k ++ " for n = " ++ show n | k <- [1..n] ]
main = do
putStr "Enter number of test cases: "
ntests <- fmap read getLine :: IO Int
let loop k acc | k > ntests = return $ reverse acc
loop k acc = do
-- we're on the kth-iteration
putStr $ "Enter parameter for test case " ++ show k ++ ": "
a <- fmap read getLine :: IO Int
let output = myTest a -- run the test
loop (k+1) (output:acc)
allOutput <- loop 1 []
print allOutput
As you get more comfortable with this kind of pattern you'll recognize it as a fold (indeed a monadic fold since we're doing IO) and you can implement it with foldM.
Update: To help explain how fmap works, here are equivalent expressions written without using fmap:
With fmap: Without fmap:
n <- fmap read getLine :: IO [Int] line <- getLine
let n = read line :: Int
vals <- fmap (map read . words) getLine line <- getLine
:: IO [Int] let vals = (map read . words) line :: [Int]
Using fmap allows us to eliminate the intermediate variable line which we never reference again anyway. We still need to provide a type signature so read knows what to do.
The idiomatic way is to use replicateM:
runAllTests :: [[Int]] -> IO ()
runAllTests = {- ... -}
main = do
numTests <- readLn
tests <- replicateM numTests readLn
runAllTests tests
-- or:
-- main = readLn >>= flip replicateM readLn >>= runAllTests
To study the details of the State monad, I'm trying to create myself a complete desugared version of a simple state monad function, completing the thought started in How does 'get' actually /get/ the initial state in Haskell?, in the answer by J Cooper.
The example state monad function simply swaps the state and the input value, so that (conceptually) if the input is (v, s) then the output is (s, v). I show three translations, first from do notation to desugared >>= and >>, then placing those operators in function position, and finally attempting to replace them and get/put with their definitions.
The 'do' version and the first two translations work, but the final translation does not. Problems:
Upon loading the module, GHCi reports that z1 is not in scope.
I've not figured out exactly how to represent omitting the argument passing in the >> translation.
How should these be fixed?
FWIW, current Haskell platform (GHC 7.4.2).
Thanks!
-- simpleswap
import Control.Monad.State
-- =============================================
-- 'Do' version
simpleswap1 :: String -> State String String
simpleswap1 inp = do
z1 <- get
put inp
return z1
-- =============================================
-- Desugared to >>= and >>
simpleswap2 :: String -> State String String
simpleswap2 inp =
get >>=
\z1 -> put inp >>
return z1
-- =============================================
-- >>= and >> changed to function position
simpleswap3 :: String -> State String String
simpleswap3 inp =
(>>=) get
(\z1 -> (>>) (put inp) (return z1) )
-- =============================================
-- Attempt to translate >>=, >>, get and put
simpleswap4 :: String -> State String String
simpleswap4 inp =
state $ \s1 ->
-- (>>=)
let (a2, s2) = runState ( {- get -} state $ \sg -> (sg,sg) ) s1
in runState (rhs1 a2) s2
where
rhs1 a2 = \z1 ->
-- (>>)
state $ \s3 ->
let (a4, s4) = runState ( {- put inp -} state $ \_ -> (inp, ()) ) s3
in runState (rhs2 a4) s4
where
rhs2 a4 = return z1
-- =============================================
main = do
putStrLn "version 1004"
let v = "vvv"
let s = "sss"
putStrLn ("Before val: " ++ v ++ " state: " ++ s)
let (v2, s2) = runState (simpleswap4 v) s
putStrLn ("After val: " ++ v2 ++ " state: " ++ s2)
-- =============================================
There are a few minor mistakes in simpleswap4. Here is a corrected version:
simpleswap4 :: String -> State String String
simpleswap4 inp =
state $ \s1 ->
-- (>>=)
let (z1, s2) = runState ( {- get -} state $ \sg -> (sg,sg) ) s1
in runState (rhs1 z1) s2
where
rhs1 z1 =
-- (>>)
state $ \s3 ->
let (_, s4) = runState ( {- put inp -} state $ \_ -> ((), inp) ) s3
in runState rhs2 s4
where
rhs2 = return z1
I've renamed a2 to z1 (in lines 5 and 6). This doesn't change the semantics, but emphasized that the first component of the pair returned by the desugared get call is actually the result that gets bound to z1 in the previous versions of simpleswap.
The type of rhs1 should be String -> State String String. In your version, it gets an additional lambda-bound variable. It's unclear what the difference between a2 and z1 should be in your version. Removing the lambda (in line 8) also has the advantage of fixing your scoping problem. You are using z1 in the nested where-clause, but the where can only see variables bound on the left hand side of the declaration it is attached to.
In line 11, I've replaced a4 with _. This is to emphasize that (>>) does discard the result of the first action. As a consequence, rhs2 is not parameterized over this result either.