Sorry, this will be a bit fuzzy… It’s probably a well-discussed problem area, and I just don’t know the terminology?
Trigger: I just read a random doc saying:
The development server defaults to redirecting to /404.html for any requests to URLs that don’t exist.
…
If you’ve already defined other redirects, you must explicitly add the 404 redirect.
and this is very typical issue, especially in config formats/DSLs but also in APIs:
some default/implicit behavior is good for typical use, you don’t even have to mention, yet when you start customizing you need to become aware of it and know how to express it.
And there the disconnect between “things I can write on this blank page / function call” to “what actually happens” gets annoying, because the defaults, and the space of possible values, are invisible ![]()
There are many common mitigations:
- document it externally.
- document it in-context comments in “example config” which one is recommended to copy & edit.
- extend value space with a special notation to succinctly refer to default behavior. [e.g. in Python, functions with optional
arg=Nonethat test forNoneand behave specially.] - export default behavior as a const/function/class you can reuse. [differs from the above in not treating it specially, just relying on regular composition
import DEFAULT; def f(arg=DEFAULT): ...] - Treating customization as inheritance, with optionally calling
super()aka(call-next-method)aka CSSinheritetc. [this is a form of the last 2, can argue both sides] - building a GUI/wwwui instead of config file. A dedicated UI can show can have “advanced” toggle that opens a whole section with in-context help, can have dedicated checkbox etc. for default.
When done well, this is the best UX
. It’s also too much work to apply everywhere, and sometimes is out-of-sync or supports only subset of underlying functionality
.
the broader question I’m trying to ask:
Short of dedicated UIs, how can we make configs / DSLs that are more transparent, both wrt. to what you can write, and what that will do?
Has anyone written about this “blindness” problem systematically?
- Tiny example of linguistic choice: In Python (and many languages) you don’t usually detect omission of optional args, the easy path is specifying some default value. This helps because it lets callers explicitly pass that value. In particular wrappers can accept same
arg=Noneorarg=foo.DEFAULTand smoothly defer tof(arg=arg)without having to conditionally pass/omit it.- OTOH, this facilitates layering when higher level functions blindly copy support for lower-level’s default behaviors but don’t clearly document it.
- Architecture idea: having separate “pre-process” stage where config user wrote is expanded into “effective config” in same notation — and have option to show it.
-
Programmatically, this means splitting your API from
f(arg=DEFAULT)which does it all intocompute_arg()you could call separately +f(arg). -
This gets tricky with [default] behaviors that try several branches and only get resolved at execution time.
When one config piece is applied many times, it can even take different branches. [e.g. when configuring HTTP redirects, requests for/foo/no-such-nameand/bar/no-such-namemight resolve differently e.g./foo/spa.htmlvs. top-level/404.html]
This touches on need for “materialized execution”? -
But in practical terms, that’s why (1) logging actual decisions + (2) supporting dry run can be easier to implement than pre-computing… It’s certainly the most widespread approach!
-
-
How about DX ideas? You write config/DSL but with interactive support?
- Completion is well explored! Done well in some CLIs (despite being a beast to implement across shells), both for discoverability, in-context brief help, and default values.
In Unix shells it’s installed out-of-band, so all too commonly gets out-of-sync with actual command
. - Language Server Protocol lowered the bar on rich editing support. Some recent languages bake support directly into the interpreter! Sounds like a good direction, but I don’t have experience to say more.
- Language workbenches re-frame the activity from “I’m writing an interpreter” to “I’m designing a language+tooling”

- Completion is well explored! Done well in some CLIs (despite being a beast to implement across shells), both for discoverability, in-context brief help, and default values.
-
Are type annotations / data schemas a form of builtin linguistic support for expressing DSLs? Are they popular because they help discoverability of “what I can write here”?!
I’d say the editor tooling for this is under-developed. In principle, any function accepting some algebraic data type could be automatically translated to almost-GUI for supplying inputs, but most tooling I’ve seen is much less hand-holding, it’s targetted at programmers over users.- Are there type systems that integrate documentation & defaults?
i feel I’m veering off too wide, so I’ll shut up. But yes I’m interested in linguistic choices AND architecture AND tooling…