1
1
mirror of https://github.com/tweag/nickel.git synced 2024-09-11 11:47:03 +03:00

Explain why Nickel is not an embedded DSL (#1365)

* Explain why Nickel is not an embedded DSL

* Apply suggestions from code review

Co-authored-by: Viktor Kleen <viktor.kleen@tweag.io>

* Update RATIONALE.md

---------

Co-authored-by: Viktor Kleen <viktor.kleen@tweag.io>
This commit is contained in:
Yann Hamdaoui 2023-06-15 14:27:15 +02:00 committed by GitHub
parent 1676d22a2b
commit 5884cd730c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -23,7 +23,13 @@ alternatives.
- [Typing](#typing)
- [Turing completeness](#turing-completeness)
- [Side-effects](#side-effects)
2. [Comparison with alternatives](#comparison-with-alternatives)
2. [Why Nickel is not a DSL embedded in an existing language](#why-nickel-is-not-a-dsl-embedded-in-an-existing-language)
- [Error messages](#error-messages)
- [LSP integration](#lsp-integration)
- [Haskell is heavy](#haskell-is-heavy)
- [Haskell is not familiar](#haskell-is-not-familiar)
- [Is it really less work?](#is-it-really-less-work)
3. [Comparison with alternatives](#comparison-with-alternatives)
- [Starlark](#starlark-the-standard-package)
- [Nix](#nix-json-and-functions)
- [Dhall](#dhall-powerful-type-system)
@ -186,6 +192,85 @@ extensible, meaning that third-party may define new effects and implement
externally the associated effect handlers in order to customize Nickel for
specific use-cases.
## Why Nickel is not a DSL embedded in an existing language
Using an existing language to embed configuration is a common approach. In this
section, we'll try to answer the question why Nickel isn't a DSL embedded in,
say, Haskell.
On the front of Infrastructure-as-Code, Pulumi adopted a similar approach:
instead of creating their own new deployment language, they chose to leverage
known, mature and well-equipped programming languages to write deployments.
While embedding brings obvious advantages, it also comes with its lot of issues.
### Error messages
The end result of Nickel being a useful tool is, all in all, error messages
(not only, but it's the user-facing consequence of the majority of correctness
features such as typechecking and contracts). It doesn't help to have a very
fancy types and contracts system if you're unable to diagnose when something
goes wrong.
Embedding Nickel in Haskell would probably make error messages unusable. They
can already be suboptimal for normal Haskell, but adding for example row
polymorphism encoded in the Haskell type system would be worse. Encoding
Nickel's gradual type system in a usable way in Haskell might prove challenging
as well.
The contract system alone could be easily implemented in any functional language
without much native support (including Nix). Once again, the big difference
Nickel hopes to make is error messages (beside naturality of the syntax, LSP
support and so on). Being built-in, contract error reporting has special support
in the interpreter with access to source positions, the call stack, and so on.
This is much harder to replicate in a host language.
### LSP integration
The same arguments apply to the LSP. The Nickel LSP currently features record
completion based on both type information, contract information, and bare record
structures. This is the kind of feature that makes using Nickel for e.g.
Kubernetes appealing: even without functions and types, getting completion with
documentation in-code is already valuable. This is not possible for a generic
Haskell LSP, with no knowledge of DSLs such as Nickel.
### Haskell is heavy
One of the possible use-case of Nickel would be to embed Nickel, either as a
binary or linked to it as a C library, into another tool to use (think a cloud
orchestrator like Terraform). Pulling all of the GHC toolchain to evaluate a few
hundred lines of configuration sounds overkill. Garbage collection and
Haskell runtime makes it also harder to interface it with other languages and
binaries (hence the choice of Rust to implement Nickel).
### Haskell is not familiar
Another goal of Nickel is to be understandable by DevOps, not only by functional
programmers. In particular, as long as you don't write library code yourself,
the syntax is JSON-like and doesn't use very fancy constructions (custom
contracts are often implemented externally to the configuration itself, and
basic contracts look like JSON schemas). Changing some configuration option
should be trivial to do for non-developers. This is also harder to achieve in a
Haskell DSL.
### Is it really less work
A big part of the work of developing Nickel is language design and
experimentation. I think implementing the core language from scratch, now that
we have a good idea of what it looks like, wouldn't be a daunting task.
Another aspect is developing the tooling, and indeed we could get some for free
if we piggy-backed on Haskell. But the Haskell tooling would be close to useless
for such an advanced DSL anyway, as mentioned in the previous paragraphs.
Finally, embedding Nickel in Haskell would also involve constraints and work
that we don't have for a stand-alone language (such as encoding the type system
of Nickel inside the one of Haskell).
In conclusion, it's hard to tell, but it doesn't seem totally obvious that
embedding Nickel in Haskell from the beginning would have been much less work
than starting a language from scratch.
## Comparison with alternatives
Let's compare Nickel with the languages cited at the beginning: Starlark, Nix