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.
> 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:
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.
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.
Fair enough, NixOS doesn't completely solve this either (other than the aforementioned possibility to reference other parts of the system config and some integrity checks). I was just wondering if that's an area where more static checks are possible.
That sounds pretty good though, thanks! Maybe I'll try out Guix System some day.
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.
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.