Elm-Style TUI
Build a tiny terminal app with Elm-style state transitions and an impure shell.
Pure Core, Impure Shell
The shell owns terminal input and output. Application code is plain state plus pure message parsing, updates, and rendering.
Example
The shell owns input and output; state transitions, event parsing, and rendering stay pure.
todo-tui.brp
record Model {value: Int, running: Bool}
union Msg:
Increment
Decrement
Reset
Quit
Ignore
func run_app[State, Message](
initial: State,
parse_event: pure (String) -> Message,
step: pure (State, Message) -> State,
render: pure (State) -> String,
active: pure (State) -> Bool,
) -> Void:
-- This shell owns all terminal I/O.
var state: State = initial
var eof: Bool = False
print(render(state))
while active(state) and not eof:
match input("cmd> "):
Some(raw):
-- The application callbacks are pure, so input and output cannot leak into update or view.
state = step(state, parse_event(raw))
print(render(state))
None:
eof = True
pure func init() -> Model:
{value = 0, running = True}
pure func parse_msg(raw: String) -> Msg:
match raw.trim():
"+": Increment
"-": Decrement
"0": Reset
"q": Quit
_: Ignore
pure func update(model: Model, msg: Msg) -> Model:
-- Every message returns the next model instead of mutating global state.
match msg:
Increment:
{ model | value = model.value + 1 }
Decrement:
{ model | value = model.value - 1 }
Reset:
{ model | value = 0 }
Quit:
{ model | running = False }
Ignore:
model
pure func is_running(model: Model) -> Bool:
model.running
pure func view(model: Model) -> String:
status: String = if model.running:
"running"
else:
"done"
lines: List[String] = [
"Counter TUI (" + status + ")",
"value: " + model.value.to_string(),
"+ increment | - decrement | 0 reset | q quit",
]
lines.join("\n")
func main(args: List[String]) -> Void:
run_app(init(), parse_msg, update, view, is_running)
Try it
terminal
printf '+\n+\n-\nq\n' | blorp run todo-tui.brp