Subject: bug#56799: (gnu services configuration) usage of *unspecified* is problematic

From: Ludovic Courtès <[email-masked]>
Date: Thu, 04 Aug 2022 14:19:59 +0200

Howdy,

Maxim Cournoyer <[email-masked]> skribis:

>>> Granted, few services outside of Jami probably made use of this, but it
>>> was nevertheless a useful property.
>>
>> I don’t know of any.
>
> I think mostly because few services make use of define-configuration.
> While attempting to write a new VNC service, it quickly became a visible
> annoyance:
>
> (define-configuration/no-serialization xvnc-configuration

[...]

>   (port
>    maybe-port
>    "The port on which to listen for connections from viewers.  When left
> unspecified, it defaults to 5900 plus the display number.")

[...]

> (define (xvnc-shepherd-service config)
>   "Return a <shepherd-service> for Xvnc with CONFIG."
>   ;; XXX: Ensure all the *unspecified* values are handled outside of gexps, as
>   ;; they are not valid gexp input (they are not self-quoting/serializable).
>   ;; This would otherwise cause problem during 'guix deploy'.
>   (let* ((display-number (xvnc-configuration-display-number config))
>          (port (if (unspecified? (xvnc-configuration-port config))
>                    #f
>                    (xvnc-configuration-port config)))

OK, I see.  I guess most of the time, we just call
‘serialize-xyz-configuration’, which automatically handles *unspecified*
values.  In this case, ‘port’ is treated specially and instead passed as
a command-line argument.

Other ways to address that come to mind include: adding ‘port’ to the
config file instead of on the command line (if possible), or doing:

  (serialize-configuration config
                           (find (lambda (field)
                                   (eq? (configuration-field-name field)
                                        'port))
                                 xvnc-configuration-fields))

That’s a mouthful but maybe it could be abstracted.  It does sound less
convenient though.

That said, whether it’s ‘unspecified?’ or something else, you have to
have a check in place, right?  With the new interface it becomes:

 (if (eq? port 'unset) #f port)

Or you can provide an actual default value (an integer in this case),
but that’s possible whether or not *unspecified* is the default value.

WDYT?

>> In addition to these issues around the process, I think we should strive
>> for more stability.  One of the reasons it took time to review
>> <https://issues.guix.gnu.org/54674> is that interface changes are a
>> commitment.  Now commit a2b89a3319dc1d621c546855f578acae5baaf6da
>> introduces a second interface change for reasons that are unclear to me
>> (if the conclusion had been to revert, I’d have favored an actual revert
>> rather than introducing 'unset).
>
> I like to think of *unspecified* or 'unset as an uninteresting
> implementation detail that shouldn't be part of the public API.

It is part of the API for people who write services (but it’s definitely
an implementation detail for someone who just uses services).

> It's an implementation detail of the 'maybe' types/predicates
> generator of the (gnu services configuration) machinery.  Perhaps we
> could introduce a 'maybe-set?' predicate to check them, to avoid
> leaking the implementation detail (the 'unset symbol).  Also, after
> reading more on the topic, it became clear to me that *unspecified* is
> not meant as a value to be actively used by programmers in Scheme; it
> seems it's rather a value meant to be returned when the behavior of a
> procedure is unspecified [0].  Why 'unspecified?' even exist in Guile
> I don't know, I suppose because of some disagreement on the matter, as
> hinted in the previous link.

Right, even cleaner would be to have a specific value for this, like:

  (define &default-value (list 'default)) ;or something w/o read syntax
  (define (default? x) (eq? x &default-value))

But IMO it’s OK.

> The reason I did not simply revert was because the change also
> introduced something useful in the same code change, which is to lift
> the requirement to specify a default value for maybe-* fields.

Right, that makes sense to me.

>> How should we move forward?
>
> Perhaps we can discuss any issues I may have missed that arise from the
> this change, or possible future directions that would improve things.

With the xvnc example, I better understand the kind of situation where
a field value might end up directly in a gexp, though I’m still unsure
whether use of *unspecified* really makes things worse.  At least,
because it lacks a read syntax, the problem is caught early on; whereas
with 'unset, you might end up stuffing 'unset in your config file
without noticing.

Thanks for taking the time to discuss!

Ludo’.