An impure stateful interpreter evaluates a script while carrying persistent state that can change during execution. Instead of returning only a value, it reads and updates resources such as devices, ingredients, or test inputs. section 5.3.2 section 6.3.5
The interpreter receives an IORef or similar mutable reference, interprets one instruction, updates the referenced state, and recursively continues with the next instruction. This avoids reconstructing the entire state at every step, while making mutation and failure behavior explicit. section 6.3.5 section 5.3.2
Common design responsibilities include:
- Compose nested interpreters by passing a generalized external interpreter into interpreters for embedded languages. section 5.3.2
- Keep runtime resources in mutable references when repeated state threading would make signatures cumbersome. section 5.3.2
- Validate and consume state during interpretation, such as withdrawing an ingredient or rejecting an unavailable resource. section 6.3.5
- Configure stateful mocks with a mutable sequence so successive calls follow a test-defined scenario. section 15.2.2
A stateful mock interpreter
A random-activity script requests a base activity and, for a game branch, a second random value. Supplying the mutable mock list [3, 2] makes the test select the computer-game branch and then Dwarf Fortress. Each interpretation consumes the next value. section 15.2.2
Listing 15.10 Stateful mocking interpreter
interpretRndMocking :: IORef [Int] -> RandomF a -> IO a interpretRndMocking mocksRef (GetRandomInt _ next) = do mockVal <- popNextRndMock mocksRef pure (next mockVal) popNextRndMock :: IORef [Int] -> IO Int popNextRndMock mocksRef = do mocks <- readIORef mocksRef case mocksRef of [] -> error "No mocks available" (v:vs) -> do writeIORef mocksRef vs pure v
The listing shows the central mutation pattern: read the next mock, remove it from the IORef list, and return it to the continuation. section 15.2.2
