Advent of Code 2019, Day 2 in Haskell
slice combines some lookups. The index arithmetic can be frontloaded.

eval :: Intcode -&gt; Intcode
eval = go [0,4..] where
  go (index:es) intcode =
    let
      [opcode, v1Pos, v2Pos, savePos] = toList $ slice index 4 intcode
      v1 = intcode ! v1Pos
      v2 = intcode ! v2Pos
    in
      case intToOp opcode of
        Add  -&gt; go es $ intcode // [(savePos, v1 + v2)]
        Mult -&gt; go es $ intcode // [(savePos, v1 * v2)]
        Noop -&gt; intcode


// takes linear time on immutable vectors. Noop should be Halt. This displays the unreliability of adding data types for their suggestive names. Therefore, inline intToOp.

eval :: Intcode -&gt; Intcode
eval = modify $ \intcode -&gt;
  void $ runMaybeT $ for_ [0,4..] $ \index -&gt; do
    [opcode, v1Pos, v2Pos, savePos] &lt;- traverse (read intcode) [index..index+3]
    v1 &lt;- read intcode v1Pos
    v2 &lt;- read intcode v2Pos
    case opcode of
      1 -&gt; lift $ write intcode savePos $ v1 + v2
      2 -&gt; lift $ write intcode savePos $ v1 * v2
      99 -&gt; empty
      _ -&gt; error $ "invalid opCode, should not happen" ++ show opcode


Of course, all of this imperativeness doesn't feel Haskell. However, interpreting an imperative language is pretty much the one time you need not feel bad about that.