I've tried to solve AoC day 2 challenge in Haskell (https://adventofcode.com/2019/day/2/ - don't worry, it's not a competition so sharing a solution here is OK).
The goal is to implement a very simple VM with opcodes 1 (add), 2 (mult) and 99 (exit).
I feel like my solution is incredibly verbose. That maybe be because I rely heavily on the state monad (my background is imperative programming, so there's that). Is there anything I could improve without rewriting the whole solution?
Here's my code, thanks for all suggestions:
import Data.Sequence
import Control.Monad.State
import Data.List.Split
data Machine = Machine {
mState :: Seq Int,
mPos :: Int,
isDone :: Bool
}
opReadHead :: State Machine Int
opReadHead = do
machine <- get
return $ index (mState machine) (mPos machine)
opReadAt :: Int -> State Machine Int
opReadAt target = do
machine <- get
return $ index (mState machine) target
opForward :: State Machine ()
opForward = do
machine <- get
put $ machine { mPos = mPos machine + 1 }
opWrite :: Int -> Int -> State Machine ()
opWrite target what = do
machine <- get
put $ machine { mState = update target what (mState machine) }
opReadNext :: State Machine Int
opReadNext = do
a <- opReadHead
opForward
return a
opAdd :: State Machine ()
opAdd = do
aPtr <- opReadNext
a <- opReadAt aPtr
bPtr <- opReadNext
b <- opReadAt bPtr
target <- opReadNext
opWrite target (a + b)
opMult :: State Machine ()
opMult = do
aPtr <- opReadNext
a <- opReadAt aPtr
bPtr <- opReadNext
b <- opReadAt bPtr
target <- opReadNext
opWrite target (a * b)
opExit :: State Machine ()
opExit = do
current <- get
put $ current { isDone = True }
isMachineDone :: State Machine Bool
isMachineDone = do
get >>= (return . isDone)
opcode :: Int -> State Machine ()
opcode 1 = opAdd
opcode 2 = opMult
opcode 99 = opExit
opExecuteNext :: State Machine ()
opExecuteNext = do
opValue <- opReadNext
opcode opValue
runCode :: State Machine ()
runCode = do
done <- isMachineDone
if done
then return ()
else opExecuteNext >> runCode
evalWith :: Machine -> Int -> Int -> Int
evalWith machine noun verb = do
fst $ runState (do
opWrite 1 noun
opWrite 2 verb
runCode
opReadAt 0
) machine
main :: IO()
main = do
fileData <- readFile "input"
let memory = map read $ splitOn "," fileData
let machine = Machine {
mState = fromList memory,
mPos = 0,
isDone = False
}
let outputs = [(evalWith machine x y, (x, y)) | x <- [0..99], y <- [0..99]]
print $ snd $ head $ Prelude.filter ((== 19690720) . fst) outputs
```