RCL's Gradual Type System: Bringing Safety to Configuration Without Boilerplate
Share this article
When JSON Isn’t Enough: The Case for Typed Configuration
Configuration files—whether for CI/CD pipelines, Kubernetes, or infrastructure-as-code—are notorious for repetitive, brittle YAML or JSON. Ruud van Asseldonk’s RCL (a superset of JSON) tackles this by adding functional programming features: functions, sets, list comprehensions, and non-string dictionary keys. But as RCL evolved, a critical question emerged: How do you add type safety to a language whose primary output is unstructured JSON?
Why Types in a Configuration Language?
In a detailed introduction, van Asseldonk argues two core motivations:
- Bug Prevention: Moving invariants from comments into enforceable checks. For example, ensuring field renames propagate or validating allowed string values.
- Self-Documenting Code: Avoiding “dynamic dict archaeology” (common in Python/JS) where tracing data shapes becomes guesswork. Optional types let developers clarify complexity without cluttering simple cases.
"To keep a large codebase maintainable, you need types. It’s no surprise TypeScript displaced JavaScript and Mypy took Python by storm."
— Ruud van Asseldonk
The Typing Tightrope: Flexibility vs. Safety
RCL’s type system walks a deliberate tightrope:
- Gradual Typing: Annotations are optional. Unannotated code leverages type inference, and the system defers to runtime checks when static guarantees are impossible.
AnyIs Essential: Since RCL must output any valid JSON (including heterogeneous lists like[42, true, "yes"]), the type lattice includesAny—a pragmatic escape hatch.- No Runtime/Compile-Time Split: Unlike long-running apps, RCL configurations execute once. Thus, a “runtime” type error (e.g.,
not 42) is treated as a static failure—collapsing the feedback loop for users.
// Static error, even though 'ints' is empty and runtime would succeed!
let ints = [for x in [1, 2, 3]: if x > 10: x];
[for i in ints: not i] // Error: `not` requires Bool, found Int
Why Not Just Allow All Runtime-Valid Code?
Van Asseldonk draws a hard line: The type system rejects logically unsound operations (like applying not to an integer), even if a specific execution path avoids the error. This prevents latent bugs, prioritizing correctness over lax compatibility.
What’s Next in the Series
This post sets the philosophical foundation. Upcoming entries promise deeper dives:
- Part II: The concrete type system design (unions, functions, the
Anytype). - Part III: Related work in languages like Nix, Dhall, or TypeScript.
- Part IV: Implementing the type checker’s internals.
For developers drowning in YAML templating, RCL’s blend of JSON compatibility, functional abstraction, and gradual typing offers a compelling alternative—and its evolving type system could redefine safety for modern configuration workflows.
Source: Ruud van Asseldonk's blog