The Precedence Question
A recent Hacker News thread posed a deceptively simple question: does your domain-specific language (DSL) actually need operator precedence? With just 5 points and one comment, the discussion might seem minor. But it hits at a core tension in language design.
Developers create DSLs all the time. These are small, specialized languages for specific tasks - think configuration files, build scripts, or data transformation rules. They're supposed to be simpler than general-purpose languages. Yet many designers instinctively add operator precedence because "that's what real languages do."
That instinct might be wrong.
What You're Actually Building
Operator precedence determines which operations happen first in expressions. In math, multiplication happens before addition unless parentheses say otherwise. Most programming languages inherit this from mathematics.
But DSLs aren't doing complex mathematics. They're configuring servers, defining data pipelines, or describing UI layouts. The expressions are simpler. The need for sophisticated precedence rules often disappears.
"I've built three DSLs for different projects," says one developer in the thread. "Only one needed precedence, and even then, just two levels. The others worked fine with left-to-right evaluation and explicit parentheses."
That's the pragmatic take. Precedence adds cognitive load for users and implementation complexity for maintainers. Every precedence rule needs documentation, testing, and debugging. For a language used by a small team or for a single project, that overhead might not pay off.
The Skeptic's View
Seasoned developers know the trap. You start with something simple. Then you add "just one more feature" to handle edge cases. Before you know it, your lightweight DSL has become a mini-C++ with all the complexity you were trying to avoid.
"I've seen DSLs grow operator precedence, then type inference, then macros," comments another developer. "Six months later, nobody remembers why we didn't just use Python with a library."
The cynical take? Many language features exist because designers copy what they've seen elsewhere, not because users actually need them. Operator precedence often falls into this category for DSLs.
When Precedence Makes Sense
Not all DSLs should avoid precedence rules. Some domains naturally involve nested expressions that would become unreadable without them.
Query languages often benefit from precedence. SQL's AND/OR precedence prevents parentheses soup in complex conditions. Configuration languages for mathematical or scientific computing might need it too.
The key is asking "will users write expressions complex enough to need this?" If the answer is "maybe once a year," explicit parentheses are probably better. If users will write nested expressions daily, precedence becomes valuable.
Implementation Costs
Adding precedence isn't free. It affects every part of your language implementation.
Parsers get more complex. Error messages become harder to write clearly. Documentation needs more examples and explanations. Tooling like syntax highlighters and auto-complete must understand the rules.
For a public DSL with many users, these costs might be justified. For an internal tool used by five developers? Probably not.
The Middle Ground
Some languages take hybrid approaches. They implement minimal precedence - maybe just one or two levels instead of the dozen found in languages like C++. Others make precedence optional, allowing parentheses for clarity even when not strictly needed.
Lua takes an interesting approach. It has precedence, but the rules are simple and documented in a single table. The language designers consciously avoided the complexity of C-style precedence hierarchies.
For DSL designers, the lesson is clear: start with no precedence. Add it only when users complain about too many parentheses. Even then, add the minimum needed to solve the actual problem.
What Developers Are Actually Saying
The single comment on the Hacker News thread gets straight to the point: "For most DSLs, no. Use parentheses and keep the parser simple."
This reflects a broader shift in developer attitudes. The early days of computing valued cleverness and minimal keystrokes. Today's developers prioritize readability, maintainability, and simplicity. Parentheses might take more typing, but they make intentions explicit.
"I'd rather see (a + b) * c than memorize whether + or * happens first in YetAnotherConfigLanguage," says one developer on a related forum. "My brain has better things to do."
Practical Advice for Language Designers
Before adding operator precedence to your DSL, ask these questions:
- How many users will write expressions complex enough to need it?
- Can they use parentheses instead without too much pain?
- What's the maintenance cost of implementing precedence?
- Will precedence rules match users' existing mental models?
For many DSLs, the answers point toward simplicity. Start with left-to-right evaluation or require explicit grouping. See how users react. You can always add precedence later if it's genuinely needed.
Remember: every feature you add becomes a feature someone has to learn, document, and maintain. Make sure it's earning its keep.
The Bigger Picture
This discussion isn't really about operator precedence. It's about feature creep in software design. DSLs start as simple solutions to specific problems. Then they accumulate features until they're no longer simple or specific.
Operator precedence serves as a canary in the coal mine. If you're adding it to a DSL, ask yourself: are we solving the right problem? Or are we building a general-purpose language in disguise?
Sometimes the answer is "we need a real programming language." That's okay. Recognizing when your DSL has outgrown its purpose is better than forcing it to do things it wasn't designed for.
For now, the consensus seems clear: most little languages don't need big precedence rules. Keep them simple. Your users will thank you.