My interepreter supports the following operations:
up
(increment the y
coordinate),down
(decrement the y
cooridinate),left
(decrement the x
cooridinate),right
(increment the x
coordinate),printX
(print the x
coordinate),printY
(print the y
coordinate).My current implementation looks like this
interpreterHelper :: [String] -> Int -> Int -> Int -> [String] -> [String]
interpreterHelper cmds index x y output
| index == length (cmds) = reverse output
| (cmds !! index) == "up" = interpreterHelper cmds (index + 1) x (y + 1) output
| (cmds !! index) == "down" = interpreterHelper cmds (index + 1) x (y - 1) output
| (cmds !! index) == "left" = interpreterHelper cmds (index + 1) (x - 1) y output
| (cmds !! index) == "right" = interpreterHelper cmds (index + 1) (x + 1) y output
| (cmds !! index) == "printX" = interpreterHelper cmds (index + 1) x y (show x : output)
| (cmds !! index) == "printY" = interpreterHelper cmds (index + 1) x y (show y : output)
interpreter :: [String] -> [String]
interpreter cmds = interpreterHelper cmds 0 0 0 []
However, my exercise requires that I can make it interactive with the following statement:
interact (unlines . interpreter . lines)
which does not seem to happen.
How can I make it interactive, so that when I, say, type in printY
, I immediately receive the output of the current value of the y
- coordinate?
The obvious problem is that you collect all output into an accumulator first and only produce anything at the very end, so the code doesn't produce any output until the end.
You can instead produce the output as you go:
interpreterHelper :: [String] -> Int -> Int -> Int -> [String]
interpreterHelper cmds index x y
| (cmds !! index) == "up" = interpreterHelper cmds (index + 1) x (y + 1)
| (cmds !! index) == "down" = interpreterHelper cmds (index + 1) x (y - 1)
| (cmds !! index) == "left" = interpreterHelper cmds (index + 1) (x - 1) y
| (cmds !! index) == "right" = interpreterHelper cmds (index + 1) (x + 1) y
| (cmds !! index) == "printX" = show x : interpreterHelper cmds (index + 1) x y
| (cmds !! index) == "printY" = show y : interpreterHelper cmds (index + 1) x y
interpreter :: [String] -> [String]
interpreter cmds = interpreterHelper cmds 0 0 0
That does solve the immediate problem of interactiveness.
But it eventually produces an error when we reach the end of the input (we can't use length
because that would require all elements to be there again). And it's still fairly inefficient. Indexing into a list is often a habit of programmers coming from a non functional background, but for linked lists like Haskell's lists it's complexity is O(index)
so your function is currently O(n²)
. You can improve that and in the process get rid of index
by just matching on the cmds
list:
interpreterHelper :: [String] -> Int -> Int -> [String]
interpreterHelper [] _ _ = []
interpreterHelper ("up" : cmds) x y = interpreterHelper cmds x (y + 1)
interpreterHelper ("printY" : cmds) x y = show y : interpreterHelper cmds x y
-- other commands accordingly
interpreter :: [String] -> [String]
interpreter cmds = interpreterHelper cmds 0 0