Hacker News new | ask | show | jobs
by cmm 1102 days ago
I find Nix to be very close to the perfect DSL for what it does, and I like it quite a lot. But then I never bothered to look at Guix closely -- is its DSL at least lazy? Are there any honest comparisons wrt verbosity and awkwardness of Nix and Guix for stuff both are supposed to be good at?

No idea what "making NixOS compatible with other programming languages" is supposed to mean. On the face of it the phrase is, well, baffling. Would you care to elaborate?

4 comments

> its DSL at least lazy

This keeps getting asked and it's baffling to me. Any programming language can delay evaluation by wrapping values in thunks where that makes sense, so it seems odd to me to give so much importance to whether values are evaluated strictly or delayed by default.

Guix package definitions declare other package values as inputs, and evaluation of these inputs is in fact delayed.

Verbosity: in Guix we don't generally run shell snippets as part of a build; the build phases are compiled into a Guile builder script, so in the absence of helpful abstractions build phases do not generally have the conciseness of shell scripts. On the other hand abstractions are easily fashioned, so some things are more concise and clearer than the shell equivalent.

> > its DSL at least lazy

> This keeps getting asked and it's baffling to me. Any programming language can delay evaluation by wrapping values in thunks where that makes sense, so it seems odd to me to give so much importance to whether values are evaluated strictly or delayed by default.

At least in Haskell laziness increases composability.

I can't think of any examples in Nix, but maybe it's the same reason?

One use case for lazy evaluation in NixOS is that you can reference other parts of the system configuration, possibly even defined in other modules, as long as you don't produce cyclic references. For example, I use stuff like the following often to avoid accidentally specifying a username that does not exist:

  { config, ... }:
  {
   systemd.services.foobar = {
    enable = true;
    serviceConfig.User = config.users.users.foobar.name;
    # etc...
   };
  }
Another use case is being able to keep everything as plain values. For example, I can run `import <nixpkgs> { }` without evaluating all of `nixpkgs`. You can accomplish that in a language with eager evaluation by wrapping values in functions but I prefer the everything-is-a-value way.

Of course, this is just a matter of preference. Like most Guix vs. Nix aspects, as far as I can tell.

The "service" mechanism in Guix System is designed in part as a reaction against those the NixOS module design you're describing: I think the ambient authority, free-style extension mechanism of NixOS modules makes it hard to reason about it (any module can change the config of any other module), and it makes it easy to shoot oneself in the foot (with infinite recursion in particular, as you write).

In Guix System, services extend one another, forming a directed acyclic graph of extensions. When a service extends another service, that connection is explicitly declared, there's no surprise.

To see what it means in practice, check out the Explorer: https://notabug.org/civodul/guix-explorer

Nix bombs out when it runs into a cyclic reference, so at least infinite recursion doesn't hang without any output. Likewise, it also rejects multiple definitions for a value (though to be fair some values like attribute sets are mergeable and it can be hard to wrap one's head around that).

The service model in Guix looks pretty neat! I've long been thinking it would be nice to have a configuration system for NixOS that uses isolated components with a defined (type-safe) interface. The current modules are kind of that, except it all gets squashed down into a single nested attribute set, so it's not really isolated and (understandably) can be confusing. How does Guix System handle multiple services competing for the same global resource (port allocation, file in /etc, ...)?

There's nothing to prevent multiple services from using the same port at this stage. For files in /etc, the "etc" service should detect that the same file appears more than once and error out--that is, it won't instantiate a "broken" system.

There are other things that are detected statically and prevent you from instantiating a broken system: missing modules in the initrd, invalid file system label/UUID, references to non-existent Shepherd services, etc. All this is possible because there are well-defined data types and semantics for the different components of the system.

> Any programming language can delay evaluation by wrapping values in thunks where that makes sense

Indeed. I was simply asking whether the macros used for programming Guix are lazy, how is that baffling?

It's baffling because the question is too vague to produce a meaningful answer. What does it mean for macros in Guix to be lazy? The expansion of some macros may be (and is) lazy. For some things lazy evaluation is sensible (e.g. for declaring dependencies because you don't necessarily want to go evaluate the whole graph whenever you evaluate a single package value), for others it is not.
One thing that's often overlooked with Nix (the language) is that it does not let you define new data types. For example, there's no "package" type in Nix and Nixpkgs; instead there are only functions that are passed "attribute sets" (key/value dictionaries).

That Nix can't tell what's a package and what's not is a hindrance for its user interface (e.g., it's hard to look up packages by name/version or to iterate over them) and for packagers (e.g., easy to end up with package definitions that don't follow the informal "schema").

This presentation I think was a fair attempt to compare, or at least to look at Guix from a Nix perspective

https://youtu.be/bDGzCXr6VYU

Guix uses Guile, which is a general purpose scheme/lisp dialect, allowing for lazy evaluation
I know quite well what Guile and Scheme are; I was asking about the specific DSL/library/whatever combo that Guix is programmed in. The code snippets on the page which this thread is about, for example, mostly use macros that seem to mimic their Nix equivalents very closely but are marginally more verbose. I would very much like to see a compelling example where the fact that Guile has macros makes a practical difference.

(Note that Nix's laziness is not an unalloyed good either, for example it is notoriously difficult to debug. But let's limit ourselves to pure expressiveness for now)

Well I mean look at the docs. To each their own but I find Guile much more palatable than Nix. Moreover, I would rather spend time learning Guile than an obscure language like Nix. But for you Nix is a perfect DSL so i think we would probably be bashing our heads against the wall debating which is better.

As regards macros, Guile allows you to write your own macros in a pretty straight forward way that is just not the case in Nix

I'm not trying to "debate" anything here.

I do note that those Guix people that are feeling competitive towards Nix for whatever reason tend to over-rely on two arguments that just do not work too well when examined closely.

The first is "Nix is obscure the way Guile is not". I would argue both are equally obscure and neither is obscure enough for its quality or UX to suffer for it. And does expertise in Guile (base Scheme is trivial) carry over anywhere interesting?

The second is "Macros!", where the implied idea is that unrestricted syntactic extensibility is Good. Well, I like Lisp (more partial to CL then Scheme, but who cares at this resolution), and I find unrestricted syntactic extensibility to be more of a hazard than a benefit; plus the expressivity of a non-strict FP language really gets you close enough for practical purposes.

> does expertise in Guile (base Scheme is trivial) carry over anywhere interesting?

If you ever want to embed a dynamic language in a native code program for configuring/scripting the latter, Guile is a great choice.

It is, but there already are other choices that are not worse and that are more established
Besider Guix, Guile is also the default extension language for the GNU project. Moreover by learning Guile you learn a lisp which some people find quite enlightening. What do you gain from learning Nix, asside from Nix?

Moreover, "macros are good" in the sense that they are powerful and grant the user freedom to construct software in ways that are just not possible with languages that try to herd their users into a specific mode of behavioir. It is the old freedom and responsibility problem. Again, I believe it is a matter of choice and personal preference

Oh boy.

> Guile is also the default extension language for the GNU project.

AFAIK Guix is the only project that uses Guile and has any actual users (I'm not counting Shepherd because outside GuixSD it is nothing). Guile is like 30 years old, and has been envisioned as "the default extension language for the GNU project" all that time (I was an active contributor for a while, so I should know). Guile is not even used by Emacs; Guile extensibility support in GDB is not even commonly built by distros. Guile is a nice and very competent Scheme implementation and I'd love for it to be useful outside Guix, but that's just not the case, and repeating that slogan won't change it. Seriously, just stop.

> It is the old freedom and responsibility problem

No, it is not. Software development is a social and technical field, not a branch of philosophy.

> What do you gain from learning Nix, asside from Nix?

Nix is a small and approachable lazy pure-functional language. You can learn a fair bit of functional programming by reading nixpkgs, which is frequently recommended even if you never use it in the real world.

> by learning Guile you learn a lisp which some people find quite enlightening.

By learning nix you learn a FP language which some people find quite enlightening.

Personally I can't find any software which I would want to contribute to or extend which uses guile (except GDB which can also be extended with python), so the "you ain't gonna need it" argument holds for guix too.

It's just such an odd argument for a relatively minor difference between the two ecosystems.

> Moreover by learning Guile you learn a lisp which some people find quite enlightening. What do you gain from learning Nix, asside from Nix?

This thread is baffling. cmm is asking a fairly simple question. He's not trying to make a point. If people don't have the answer, they need not respond! There are so many responses to him - none answering his question.