A developer and language designer recently published a set of principles for building new programming languages and their configuration systems. The post, dated December 2024, reads as a collection of hard-won frustrations about decisions that seem obvious only after they have gone wrong. The underlying argument is simple: many recurring problems in language design were solved long ago, and new projects keep reinventing them.
Multiline Strings and Indentation
The author opens with a complaint that will resonate with anyone who has embedded a multiline string into configuration code. When indentation matters in the surrounding context, raw string literals often take over the left gutter and destroy the visual structure of the file. The author points to Dhall as a language that handled this reasonably well, and notes that Nix and Lix arrived at similar solutions. They also mention having implemented dedentation logic for JavaScript template literals in a tool called Ve.dedent.
The core point is that this is a solved problem. It deserves a few extra hours of attention early in a language's development, before backwards compatibility makes it impossible to fix later.
File Paths and Project Structure
File paths should resolve relative to the file they appear in, or relative to a well-defined project root. This matters more for configuration than for running programs, where a working directory is usually sufficient. But imports, in particular, should behave predictably.
The author calls out Docker Compose and Rust's Cargo as configuration systems that get this wrong. The implication is that fixing paths retroactively is painful, and the cost compounds when the language lacks a robust versioning strategy to manage the fallout.
File Extensions and Ecosystem Compatibility
A new language entering an existing ecosystem should pick a file extension and stick with it. Syntax highlighting in IDEs, online code viewers, and git forges depends heavily on file extensions. Without a recognizable one, users face unnecessary friction. The author points to Bazel's Starlark as a case study: Starlark files use a mix of WORKSPACE, BUILD, .bzl, and .bazel extensions, which makes highlighting inconsistent and grepping harder because all possible file forms must be included in search patterns.
Trailing Commas, Keywords, and Numeric Literals
Trailing commas are a solved problem and should be allowed in every new language, with the author adding that leading commas or even bullet-point syntax are worth considering.
On the topic of keywords, the author expresses a personal but strongly held view: bare keywords are a liability. Keywords should carry a sigil to distinguish them from identifiers. This allows the language to grow its keyword set over time without breaking code that previously used those words as variable or function names. The author challenges readers to name a programming language that got its initial keyword set perfect on the first try.
Similar caution applies to numeric literals and string or regex escapes. Languages that bolt on additional prefixes or suffixes later find themselves blocked by backwards compatibility. The author recommends delimited escape sequences like \u{XXXX} as a more forward-compatible approach.
Versioning and Release Dates
Versioning in programming languages deserves more attention than it typically receives. The author raises an underexplored problem: how to handle code that spans versions. A migration script written for version 2 of a tool will necessarily live in that same version, since version 1 could not have anticipated it. Testing such a script requires having both versions checked out and running in parallel.
The author also argues that every release should include a date, even if it is not part of the version number itself. Without dates, comparing versions across languages, runtimes, packages, and dependencies becomes nearly impossible. As for the mechanics of versioning itself, tools that inspect a library's API, especially in typed languages, to determine whether a change warrants a patch, minor, or major bump are among the more sophisticated approaches available.
Build Systems and File Watching
Build systems should expose their list of tracked files and globs to the developer. This serves multiple purposes at once. It powers find and grep operations without requiring additional tooling. It provides the foundation for file watching. And it enables richer code search that can distinguish between identifiers and strings, or between types and terms.
The author's conclusion is blunt: file watching cannot be retrofitted onto a build system that was not designed with it from the start. Attempting to do so often requires writing an entirely separate program to parse files, reconstruct dependency trees, and rebuild the watcher whenever configuration changes. The upfront investment pays for itself quickly.