Use Control.Lens for code this stateful. (Control.Lens.TH must be used to define Machine.) May as well leave out type signatures this homogenous. Control.Monad.Loops often helps against explicit monadic recursion.
opReadAt target = uses mState $ (`index` target)
opReadNext = mPos <<+= 1 >>= opReadAt
opWrite target what = mState %= update target what
opBin op = do
a <- opReadNext >>= opReadAt
b <- opReadNext >>= opReadAt
target <- opReadNext
opWrite target $ op a b
opcode 1 = opBin (+)
opcode 2 = opBin (*)
opcode 99 = isDone .= True
runCode = (opReadNext >>= opCode) `untilM_` getsuse isDone
evalWith :: Int -> Int -> Machine -> Int
evalWith noun verb = evalState $ do
opWrite 1 noun
opWrite 2 verb
runCode
opReadAt 0