Subject: Re: bug#52600: [PATCH] doc: Document (gnu services configuration).

From: Attila Lendvai <[email-masked]>
Date: Thu, 23 Dec 2021 15:21:10 +0000

> > i was expecting it to be possible to have a field like:
> >
> > (foo
> > (maybe-integer?))
>
> A ‘maybe-’ type doesn’t necessarily have to have a default value set to
> ‘disabled’. The default value of the ‘foo’ field could just as well be
> ‘3’ or something.

i know. but what i want is a field that is not initialized to any
value, and a way to identify that fact in the service code (e.g. to
derive a default value from another field).

example: my service has a swarm-name field, and the service's unix
user name is derived from it as "swarm-${swarm-name}" -- unless it is
set by the user and overrides it, that is.

in this case i can't set the field using the default value mechanism
of define-configuration (i.e. at construction-time), because the
default value is not a constant. the derivation of the default value
must be done by the service's code, and it must be able to detect
fields that were not set (i'm deliberately staying away from the word
'undefined' here, because the current codebase uses it, together with
"disabled", in a strange way).

> > and its behavior would be to hold an undocumented value by default,
> > that the service implementations need to check for using a public
> > predicate function.
>
> What do you mean by “undocumented value”?

a value that is opaque to the user, and can only be detected by a
predicate (and possibly constructed by another function or through an
exported global variable).

> > some of the config values in my service can conditionally derive its
> > default value based on the value of other fields.
>
> I don’t think this is possible with ‘define-configuration’ yet. But it
> would be a nice feature to have.

i don't think it belongs to the define-configuration macro, because it
would greatly increase the complexity of its DSL/implementation for
little in return; one can always cover the few complex cases from the
scheme code of the service.

> > i need to be able to differentiate between undefined or user provided
> > field values (i.e. completely independent of anything related to
> > serialization).
>
> Maybe we could change ‘undefined’ to instead be an exception, which will
> raised when the user doesn’t provide anything.

that would make the above example/scenario impossible.
although... you're probably using 'undefined' in the strange way that
it is currently used in the code.

i think the nomenclature should be clarified (regardless of what's in
the current codebase). here's my proposal:

1) undefined: no value was provided, neither at construction time, nor
  as a default value in define-configuration.

2) missing: a value must be provided at construction time, but it wasn't.

signalling an error at construction time in case of 'missing' is
probably a good idea. but then 2) is mostly covered by the type
predicates already, no? if i define the type as INTEGER? (i.e. not
MAYBE-INTEGER?), then it'll already signal an error in that case.

> > maybe we should use guile's undefined, and undefined? predicate
> > (which is sadly a macro). or reexport an undefined? function, and
> > shadow guile's macro? it's messy, and guile specific.
>
> I am not familiar with Guile internals, but I think that
> ‘#<unspecified>’ is just a thing that the pretty-printer prints. Maybe

it's also a special type/value (to the point that it has its own heap
object tag in Guile for which Guile's UNDEFINED? macro checks for; see
https://www.gnu.org/software/guile/manual/guile.html#index-undefined_003f).

> we could use “proper” Maybe types, like in SRFI-189[1]?
> [1]: Using https://srfi.schemers.org/srfi-189/srfi-189.html

i don't immediately see its benefits here, but i'll need to
get more familiar with this srfi. thanks for the pointer!

> > or maybe we could use a heap object of an unusual/private type in a
> > global private variable to represent undefined field values, and add a
> > public predicate to test for it. using a cons cell for this is
> > tempting, but it would leak implementation details for fields of type
> > cons?. i'm new to scheme, but the best candidate is maybe a private
> > dummy record instance?
>
> But what if a user wants to set a field to ‘disabled’ (because they
> don’t want anything to get serialized), then that record would have to
> be public.

yes, but preferably through a global variable or a function. my point
is that the object's type/content should be opaque for the users.

> > i'd add a configuration-undefined-value? predicate, and also add a
> > configuration-defined-value? whose semantics is to return the value
> > itself, or #false when undefined. it comes handy in (or
> > (defined-value? foo) 42) patterns for non-boolean fields.
>
> But what if the value itself is #f? You wouldn’t be able to distinguish
> between the cases where the value is undefined and when the value is #f.

it's a user error when it is used on fields that may legitimately hold
the #false value.

> I don’t really see a use of having that functionality; do you have any
> examples when this would be useful to have?

an example: the unix-user field of a service.

1) not specified by the admin => generate a default user name

2) admin sets it to a string => use that as user name

3) admin sets it to undefined (whichever value/API we use to mark the
   undefined value) => don't create a unix user for the service, run
   it as root instead.

but again, i'm not advocating for define-configuration to support this
use-case.

this example can easily modelled by e.g. using 'run-as-root as the
default value in define-configuration, and the service code checking
for it.

> The second paragraph explains this, no? Or do you think it can be
> improved?

true, that should be enough.

i'm also realizing that i'm talking about 1) how to change the code in
(guix service configuration) under a ticket that discusses 2) the
documentation of the current codebase.

i'll cook up a patch that implements what i'm trying to describe
above, and which is flexible enough to cover my use-cases. and then 1)
can be continued under that ticket.

- attila