I've added a VAO and reduced the inner loop to only a call to glDrawArrays which renders 8000 triangles (24000 vertices). The gameFrame function now benchmarks at ~86ms on my machine. I would expect it to be ~16ms because of vsync. I've pared down the entire project into this buildable cabal project of three files, most of which is OpenGL boilerplate: https://github.com/MichaelBaker/haskell-opengl. Running it benchmarks the problem area. For what it's worth, I also tried using glBufferSubData to update the vertex data, but that had no effect.

As a side note, vinyl-gl looks like a nice library. I'll definitely look into using it if I can ever get this basic example working.