Adding a new type is easy in OOP, hard in FP. Adding a new operation is easy in FP, hard in OOP. Philip Wadler named this the Expression Problem in 1998. We show how it manifests in Rust and Scala, and tease the resolution.
Your match expression is operational semantics. Your pure function is denotational semantics. Rust’s borrow checker is axiomatic semantics. Three formal frameworks, three ways to assign meaning to code, and you have been using all of them without knowing it.
Every enum you write is a formal grammar. Every sealed trait is a set of production rules. You have been doing formal methods all along; you just did not know the name. We trace the connection from Chomsky’s hierarchy to your domain types in Rust and Scala.
While revisiting the TaskRepository trait from the Todo CLI series, I realized I was doing more than drawing an architectural boundary. I was also defining what could be said across that boundary, which is much closer to grammar than I first admitted.
We close the series by exploring what it means to migrate from CLI to TUI with ratatui: how the interaction model changes, what frictions Rust introduces with ownership and &mut in a persistent event loop, and why hexagonal architecture absorbs the change without surgery.
We design the CLI layer with clap derive, typed argument parsing with ValueEnum and FromStr for UUIDs, subcommands as enums, global –output flag for dual table/json output, and errors propagated through layers down to stderr.
We analyze the repository’s testing strategy: behavior-driven tests for each adapter, isolation with tempdir, why there are no shared tests, and the technical debt we decided to document instead of hide.
Third part of the series: we define the persistence contract with a generic trait, implement two adapters (in-memory and JSON to disk), and delve into the difference between interface and implementation as the axis of hexagonal architecture.