[PATCH] doc: Document (gnu services configuration).

  • Done
  • quality assurance status badge
Details
4 participants
  • Attila Lendvai
  • Ludovic Courtès
  • Maxim Cournoyer
  • Xinglu Chen
Owner
unassigned
Submitted by
Xinglu Chen
Severity
normal
X
X
Xinglu Chen wrote on 18 Dec 2021 16:12
(address . guix-patches@gnu.org)
665c4d2070de80af1d3594a268f0f6d3fb596d15.1639839498.git.public@yoctocell.xyz
* guix.texi (Complex Configurations): New node.
---
Hi!

This patch documents the complex beast that is the (gnu services
configuration) module. I only documented the things that existed before
the Guix Home merge, though. There were a lot of things to document, and
I hope that my explanations aren’t too confusing (It took me a while to
wrap my head around all of this). :-)

What is still missing is some kind of style guide for writing Guix
services: When should one use (gnu services configuration) vs plain
(guix records)? Should we try to create bindings for all config options
or should we provide an “escape hatch” for users?

I would personally prefer if most (if not all) services were written
using (gnu services configuration), but I don’t really think refactoring
existing services would really be worth it. But that’s another discussion.

doc/guix.texi | 372 ++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 372 insertions(+)

Toggle diff (401 lines)
diff --git a/doc/guix.texi b/doc/guix.texi
index aca88cdada..79b87d2eac 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -383,6 +383,7 @@
* Service Types and Services:: Types and services.
* Service Reference:: API reference.
* Shepherd Services:: A particular type of service.
+* Complex Configurations:: Defining bindgs for complex configurations.
Installing Debugging Files
@@ -35656,6 +35657,7 @@
* Service Types and Services:: Types and services.
* Service Reference:: API reference.
* Shepherd Services:: A particular type of service.
+* Complex Configurations:: Defining bindgs for complex configurations.
@end menu
@node Service Composition
@@ -36389,6 +36391,376 @@
This service represents PID@tie{}1.
@end defvr
+@node Complex Configurations
+@subsection Complex Configurations
+@cindex complex configurations
+Some programs might have rather complex configuration files or formats,
+and to make it easier to create Scheme bindings for these configuration
+files, you can use the utilities defined in the @code{(gnu services
+configuration)} module.
+
+The main utility is the @code{define-configuration} macro, which you
+will use to define a Scheme record type (@pxref{Record Overview,,,
+guile, GNU Guile Reference Manual}). The Scheme record will be
+serialized to a configuration file by using @dfn{serializers}, which are
+procedures that take some kind of Scheme value and returns a
+G-expression (@pxref{G-Expressions}), which should, once serialized to
+the disk, return a string. More details are listed below.
+
+@deffn {Scheme Syntax} define-configuration @var{name} @var{clause1} @
+@var{clause2} ...
+Create a record type named @code{@var{name}} that contains the
+fields found in the clauses.
+
+A clause can have one the following forms
+
+@example
+(@var{field-name}
+ (@var{type} @var{default-value})
+ @var{documentation})
+
+(@var{field-name}
+ (@var{type} @var{default-value})
+ @var{documentation}
+ @var{serializer})
+
+(@var{field-name}
+ (@var{type})
+ @var{documentation})
+
+(@var{field-name}
+ (@var{type})
+ @var{documentation}
+ @var{serializer})
+@end example
+
+@var{field-name} is an identifier that denotes the name of the field in
+the generated record.
+
+@var{type} is the type of the value corresponding to @var{field-name};
+since Guile is untyped, a predicate
+procedure---@code{@var{type}?}---will be called on the value
+corresponding to the field to ensure that the value is of the correct
+type. This means that if say, @var{type} is @code{package}, then a
+procedure named @code{package?} will be applied on the value to make
+sure that it is indeed a @code{<package>} object.
+
+@var{default-value} is the default value corresponding to the field; if
+none is specified, the user is forced to provide a value when creating
+an object of the record type.
+
+@c XXX: Should these be full sentences or are they allow to be very
+@c short like package synopses?
+@var{documentation} is a string formatted with Texinfo syntax which
+should provide a description of what setting this field does.
+
+@var{serializer} is the name of a procedure which takes two arguments,
+the first is the name of the field, and the second is the value
+corresponding to the field. The procedure should return a string or
+G-expression (@pxref{G-Expressions}) that represents the content that
+will be serialized to the configuration file. If none is specified, a
+procedure of the name @code{serialize-@var{type}} will be used.
+
+A simple serializer procedure could look like this.
+
+@lisp
+(define (serialize-boolean field-name value)
+ (let ((value (if value "true" "false")))
+ #~(string-append #$field-name #$value)))
+@end lisp
+
+In some cases multiple different configuration records might be defined
+in the same file, but their serializers for the same type might have to
+be different, because they have different configuration formats. For
+example, the @code{serialize-boolean} procedure for the Getmail service
+would have to be different for the one for the Transmission service. To
+make it easier to deal with this situation, one can specify a serializer
+prefix by using the @code{prefix} literal in the
+@code{define-configuration} form. This means that one doesn't have to
+manually specify a custom @var{serializer} for every field.
+
+@lisp
+(define (foo-serialize-string field-name value)
+ @dots{})
+
+(define (bar-serialize-string field-name value)
+ @dots{})
+
+(define-configuration foo-configuration
+ (label
+ (string)
+ "The name of label.")
+ (prefix foo-))
+
+(define-configuration bar-configuration
+ (ip-address
+ (string)
+ "The IPv4 address for this device.")
+ (prefix bar-))
+@end lisp
+
+However, in some cases you might not want to serialize any of the values
+of the record, to do this, you can use the @code{no-serialization}
+literal. There is also the @code{define-configuration/no-serialization}
+macro which is a shorthand of this.
+
+@lisp
+;; Nothing will be serialized to disk.
+(define-configuration foo-configuration
+ (field
+ (string "test")
+ "Some documentation.")
+ (no-serialization))
+
+;; The same thing as above.
+(define-configuration/no-serialization bar-configuration
+ (field
+ (string "test")
+ "Some documentation."))
+@end lisp
+@end deffn
+
+@deffn {Scheme Syntax} define-maybe @var{type}
+Sometimes a field should not be serialized if the user doesn’t specify a
+value. To achieve this, you can use the @code{define-maybe} macro to
+define a ``maybe type''; if the value of a maybe type is set to the
+@code{disabled}, it will not be serialized.
+
+When defining a ``maybe type'', the corresponding serializer for the
+regular type will be used by default. For example, a field of type
+@code{maybe-string} will be serialized using the @code{serialize-string}
+procedure by default, you can of course change this by specifying a
+custom serializer procedure. Likewise, the type of the value would have
+to be a string, unless it is set to the @code{disabled} symbol.
+
+@lisp
+(define-maybe string)
+
+(define (serialize-string field-name value)
+ @dots{})
+
+(define-configuration baz-configuration
+ (name
+ ;; Nothing will be serialized by default. If set to a string, the
+ ;; `serialize-string' procedure will be used to serialize the string.
+ (maybe-string 'disabled)
+ "The name of this module."))
+@end lisp
+
+Like with @code{define-configuration}, one can set a prefix for the
+serializer name by using the @code{prefix} literal.
+
+@lisp
+(define-maybe integer
+ (prefix baz-))
+
+(define (baz-serialize-interger field-name value)
+ @dots{})
+@end lisp
+
+There is also the @code{no-serialization} literal, which when set means
+that no serializer will be defined for the ``maybe type'', regardless of
+its value is @code{disabled} or not.
+@code{define-maybe/no-serialization} is a shorthand for specifying the
+@code{no-serialization} literal.
+
+@lisp
+(define-maybe/no-serialization symbol)
+
+(define-configuration/no-serialization test-configuration
+ (mode
+ (maybe-symbol 'disabled)
+ "Docstring."))
+@end lisp
+@end deffn
+
+@deffn {Scheme Procedure} serialize-configuration @var{configuration} @
+@var{fields}
+Return a G-expression that contains the values corresponding to the
+@var{fields} of @var{configuration}, a record that has been generated by
+@code{define-configuration}. The G-expression can then be serialized to
+disk by using something like @code{mixed-text-file}.
+@end deffn
+
+@deffn {Scheme Procedure} validate-configuration @var{configuration}
+@var{fields}
+Type-check @var{fields}, a list of field names of @var{configuration}, a
+configuration record created by @code{define-configuration}.
+@end deffn
+
+@deffn {Scheme Procedure} empty-serializer @var{field-name} @var{value}
+A serializer that just returns an empty string. The
+@code{serialize-package} procedure is an alias for this.
+@end deffn
+
+Once you have defined a configuration record, you will most likely also
+want to document it so that other people know to use it. To help with
+that, there are two procedures, both of which are documented below.
+
+@deffn {Scheme Procedure} generate-documentation @var{documentation} @
+@var{documentation-name}
+Generate a Texinfo fragment from the docstrings in @var{documentation},
+a list of @code{(@var{label} @var{fields} @var{sub-documentation} ...)}.
+@var{label} should be a symbol and should be the name of the
+configuration record. @var{fields} should be a list of all the fields
+available for the configuration record.
+
+@var{sub-documentation} is a @code{(@var{field-name}
+@var{configuration-name})} tuple. @var{field-name} is the name of the
+field which takes another configuration record as its value, and
+@var{configuration-name} is the name of that configuration record.
+
+@var{sub-documentation} is only needed if there are nested configuration
+records. For example, the @code{getmail-configuration} record
+(@pxref{Mail Services}) accepts a @code{getmail-configuration-file}
+record in one of its @code{rcfile} field, therefore documentation for
+@code{getmail-configuration-file} is nested in
+@code{getmail-configuration}.
+
+@lisp
+(generate-documentation
+ `((getmail-configuration ,getmail-configuration-fields
+ (rcfile getmail-configuration-file))
+ @dots{})
+ 'getmail-configuration)
+@end lisp
+
+@var{documentation-name} should be a symbol and should be the name of
+the configuration record.
+
+@end deffn
+
+@deffn {Scheme Procedure} configuration->documentation
+@var{configuration-symbol}
+Take @var{configuration-symbol}, the symbol corresponding to the name
+used when defining a configuration record with
+@code{define-configuration}, and print the Texinfo documentation of its
+fields. This is useful if there aren’t any nested configuration records
+since it only prints the documentation for the top-level fields.
+@end deffn
+
+As of right now, there is no automated way to generate documentation for
+and configuration records and put them in the manual. Instead, every
+time you make a change to the docstrings of a configuration record, you
+have to manually call @code{generate-documentation} or
+@code{configuration->documentation}, and paste the output into the
+@file{doc/guix.texi} file.
+
+@c TODO: Actually test this
+Below is an example of a record type created using
+@code{define-configuration} and friends.
+
+@lisp
+(use-modules (gnu services)
+ (guix gexp)
+ (gnu services configuration)
+ (srfi srfi-26)
+ (srfi srfi-1))
+
+;; Turn field names, which are Scheme symbols into strings
+(define (uglify-field-name field-name)
+ (let ((str (symbol->string field-name)))
+ ;; field? -> is-field
+ (if (string-suffix? "?" str)
+ (string-append "is-" (string-drop-right str 1))
+ str)))
+
+(define (serialize-string field-name value)
+ #~(string-append #$(uglify-field-name field-name) " = " #$value "\n"))
+
+(define (serialize-integer field-name value)
+ (serialize-string field-name (number->string value)))
+
+(define (serialize-boolean field-name value)
+ (serialize-string field-name (if value "true" "false")))
+
+(define (serialize-contact-name field-name value)
+ #~(string-append "\n[" #$value "]\n"))
+
+(define (list-of-contact-configurations? lst)
+ (every contact-configuration? lst))
+
+(define (serialize-list-of-contact-configurations field-name value)
+ #~(string-append #$@@(map (cut serialize-configuration <>
+ contact-configuration-fields)
+ value)))
+
+(define (serialize-contacts-list-configuration configuration)
+ (mixed-text-file
+ "contactrc"
+ #~(string-append "[Owner]\n"
+ #$(serialize-configuration
+ configuration contacts-list-configuration-fields))))
+
+(define-maybe integer)
+(define-maybe string)
+
+(define-configuration contact-configuration
+ (name
+ (string)
+ "The name of the contact."
+ serialize-contact-name)
+ (phone-number
+ (maybe-integer 'disabled)
+ "The person's phone number.")
+ (email
+ (maybe-string 'disabled)
+ "The person's email address.")
+ (married?
+ (boolean)
+ "Whether the person is married."))
+
+(define-configuration contacts-list-configuration
+ (name
+ (string)
+ "The name of the owner of this contact list.")
+ (email
+ (string)
+ "The owner's email address.")
+ (contacts
+ (list-of-contact-configurations '())
+ "A list of @@code@{contact-configuation@} records which contain
+information about all your contacts."))
+@end lisp
+
+A contacts list configuration could then be created like this:
+
+@lisp
+(define my-contacts
+ (contacts-list-configuration
+ (name "Alice")
+ (email "alice@@example.org")
+ (contacts
+ (list (contact-configuration
+ (name "Bob")
+ (phone-number 1234)
+ (email "bob@@gnu.org")
+ (married? #f))
+ (contact-configuration
+ (name "Charlie")
+ (phone-number 0000)
+ (married? #t))))))
+@end lisp
+
+After serializing the configuration to disk, the resulting file would
+look like this:
+
+@example
+[owner]
+name = Alice
+email = alice@@example.org
+
+[Bob]
+phone-number = 1234
+email = bob@@gnu.org
+is-married = false
+
+[Charlie]
+phone-number = 0
+is-married = true
+@end example
+
+
@node Home Configuration
@chapter Home Configuration
@cindex home configuration

base-commit: 6061540e30269934dae3395ab9fc1b905a414247
--
2.33.1
A
A
Attila Lendvai wrote on 22 Dec 2021 11:50
(No Subject)
(name . 52600@debbugs.gnu.org)(address . 52600@debbugs.gnu.org)
chn5E62auK-PgGtdieMZIH_87-dxBTQ0I6GXNTEwP-hrHJJP_TH6guQIYhIdFx8yrQSeUotgjMV9-BZi17W-xqAcGVO_RbwLhoe7ATq3Iq0=@lendvai.name
typos:

Toggle quote (2 lines)
> +A clause can have one the following forms

'of' missing.

Toggle quote (2 lines)
> to generate documentation for and configuration records

extra 'and'.

---------------

as for some higher level feedback:

i have just finished my first Guix service, a rather complex one. based on the examples, and on the config codebase itself, i had used define-configuration, and i had encountered a surprise WRT undefined values and maybe- types (only after that have i found this documentation).

Toggle quote (4 lines)
> default-value is the default value corresponding to the field; if
> none is specified, the user is forced to provide a value when creating
> an object of the record type.

i was expecting it to be possible to have a field like:

(foo
(maybe-integer?))

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. (well, short of reimplementing a full-fledged object system with field accessor abstractions, i.e. something like BOUNDP in common lisp).

some of the config values in my service can conditionally derive its default value based on the value of other fields. i need to be able to differentiate between undefined or user provided field values (i.e. completely independent of anything related to serialization).

the reason i don't recommend the use of 'undefined is fields like this that would wrongly be considered valid when no value is provided:

(foo
(symbol?))

Toggle quote (5 lines)
> Sometimes a field should not be serialized if the user doesn’t specify a
> value. To achieve this, you can use the @code{define-maybe} macro to
> define a ``maybe type''; if the value of a maybe type is set to the
> @code{disabled}, it will not be serialized.

the use of 'disabled here was very confusing because configuration objects are typically full of boolean fields... is 'disabled a valid app value, or part of the guix API? confusing to the point that i have confidently reported it as a "bug" on #guix in the maybe- implementation to use 'disabled instead of 'undefined.

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.

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?

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.

in fact, i had these two in my service impl, before reading/writing any of this:

(define (defined-value? x)
(if (eq? x 'undefined) #false x))

(define (undefined-value? x)
(eq? x 'undefined))

then the question arises: do we want to differentiate between the cases when the field value comes from a default form, and when it is set by the user (e.g. at object construction time)? if so, then one well-known value as a marker is not enough, but i don't think it's worth the additional complexity. people with rare, complex use-cases can always resort to define-record*.

------------------

another thing that has initially misled me was the word 'serialize': i don't have a better suggestion, but i have associated it to a more formal serialize/deserialize protocol, as opposed to turning scheme objects into various different configuration file formats that are native for the target binary.

maybe it's worth hinting at in the documentation where serialization is first mentioned.

------------------

if the API of validate-configuration is to raise errors, then maybe it could return the config object if everything is fine. that can simplify the code at the call site.

HTH,

- attila
PGP: 5D5F 45C7 DFCD 0A39
Attachment: file
L
L
Ludovic Courtès wrote on 22 Dec 2021 23:14
Re: bug#52600: [PATCH] doc: Document (gnu services configuration).
(name . Xinglu Chen)(address . public@yoctocell.xyz)
877dbwz3r7.fsf@gnu.org
Hi,

Xinglu Chen <public@yoctocell.xyz> skribis:

Toggle quote (2 lines)
> * guix.texi (Complex Configurations): New node.

Great work! I applied it and fixed typos Attila reported plus “bindgs”
(instead of “bindings”).

Toggle quote (6 lines)
> This patch documents the complex beast that is the (gnu services
> configuration) module. I only documented the things that existed before
> the Guix Home merge, though. There were a lot of things to document, and
> I hope that my explanations aren’t too confusing (It took me a while to
> wrap my head around all of this). :-)

It looks very clear to me.

Toggle quote (9 lines)
> What is still missing is some kind of style guide for writing Guix
> services: When should one use (gnu services configuration) vs plain
> (guix records)? Should we try to create bindings for all config options
> or should we provide an “escape hatch” for users?
>
> I would personally prefer if most (if not all) services were written
> using (gnu services configuration), but I don’t really think refactoring
> existing services would really be worth it. But that’s another discussion.

Yeah. So far the (unwritten) guideline has always been:

• Have record types that provide bindings for all or most of the
available options;

• Always provide an “escape hatch” so users can insert raw
configuration snippets, either because the bindings don’t cover
everything, or because they have an existing config file they’d like
to reuse.

We should probably write it down somewhere. Maybe we need a new section
next to “Packaging Guidelines” to discuss system services?

As for ‘define-configuration’ vs. (guix records) vs. SRFI-9… I don’t
think we really discussed the issue or agreed on something.

For the rather simple services I wrote, I was happy to use plain records
and home-made serializers rather than ‘define-configuration’. But
overall it seems to make more sense to recommend ‘define-configuration’
unconditionally. I guess it already has serializers for the most common
formats, which are all alike, so we should be able to avoid boilerplate.

Thoughts?

Thanks for substantially improving the manual!

Ludo’.
L
L
Ludovic Courtès wrote on 22 Dec 2021 23:14
control message for bug #52600
(address . control@debbugs.gnu.org)
875yrgz3qy.fsf@gnu.org
close 52600
quit
X
X
Xinglu Chen wrote on 23 Dec 2021 11:42
Re: bug#52600: [PATCH] doc: Document (gnu services configuration).
(name . Ludovic Courtès)(address . ludo@gnu.org)
87ilvfd2lx.fsf@yoctocell.xyz
Hi,

Ludovic schrieb am Mittwoch der 22. Dezember 2021 um 23:14 +01:

Toggle quote (9 lines)
> Hi,
>
> Xinglu Chen <public@yoctocell.xyz> skribis:
>
>> * guix.texi (Complex Configurations): New node.
>
> Great work! I applied it and fixed typos Attila reported plus “bindgs”
> (instead of “bindings”).

Great, thanks for taking a look. I didn’t receive any message from
Attila though, and there doesn’t seem to be anything on the ML either.
I guess he sent it when all the GNU infra was down, but unless he didn’t
Cc me, I don’t see why I wouldn’t receive it.

Toggle quote (8 lines)
>> This patch documents the complex beast that is the (gnu services
>> configuration) module. I only documented the things that existed before
>> the Guix Home merge, though. There were a lot of things to document, and
>> I hope that my explanations aren’t too confusing (It took me a while to
>> wrap my head around all of this). :-)
>
> It looks very clear to me.

Good to know. :-)

Toggle quote (22 lines)
>> What is still missing is some kind of style guide for writing Guix
>> services: When should one use (gnu services configuration) vs plain
>> (guix records)? Should we try to create bindings for all config options
>> or should we provide an “escape hatch” for users?
>>
>> I would personally prefer if most (if not all) services were written
>> using (gnu services configuration), but I don’t really think refactoring
>> existing services would really be worth it. But that’s another discussion.
>
> Yeah. So far the (unwritten) guideline has always been:
>
> • Have record types that provide bindings for all or most of the
> available options;
>
> • Always provide an “escape hatch” so users can insert raw
> configuration snippets, either because the bindings don’t cover
> everything, or because they have an existing config file they’d like
> to reuse.
>
> We should probably write it down somewhere. Maybe we need a new section
> next to “Packaging Guidelines” to discuss system services?

That sounds like a good idea; Andrew started to work on something like
that[1].

Toggle quote (12 lines)
> As for ‘define-configuration’ vs. (guix records) vs. SRFI-9… I don’t
> think we really discussed the issue or agreed on something.
>
> For the rather simple services I wrote, I was happy to use plain records
> and home-made serializers rather than ‘define-configuration’. But
> overall it seems to make more sense to recommend ‘define-configuration’
> unconditionally. I guess it already has serializers for the most common
> formats, which are all alike, so we should be able to avoid
> boilerplate.
>
> Thoughts?

Agreed, since ‘define-configuration’ & friends are now documented, it
makes even more sense to use them.

Toggle quote (2 lines)
> Thanks for substantially improving the manual!

-----BEGIN PGP SIGNATURE-----

iQJJBAEBCAAzFiEEAVhh4yyK5+SEykIzrPUJmaL7XHkFAmHEUosVHHB1YmxpY0B5
b2N0b2NlbGwueHl6AAoJEKz1CZmi+1x5Rn8P/3cHPPncCAhpBaiOKkg2IYB3YMNz
X5OdEEiVMMqA5oxcY2vNX8mxDc/daVsixUFc1cq+H5KKo9SU5Jl1Lh0a8XFEikmP
zKZjZbdPA0ohyM4ZI3pKGkFY2s6ZV2YiB5VlMOUuOQmiasvX4z+EHpuZVP+7qhjb
No0I3cPOxTvdQP8LUraxAzO6bR5eYFOVLsyqWXTAgWa15l4C7gKOiMZiahaMNLXC
h+acznf0O9uBu7O6dlerNQfaV4/MhuvQ6oBLEpYYyWgeT42KUUMnleGfmtalKUFI
1ZTKKjNrX+5GD+pWEra5XcZGhu7GP815ZNwruAOc+SWqIKAv8Af4xMI1xvmVBzWm
rztAhNmqnNDY0x0RtcychkQTQzRznR4qTJnpn05uSJFJXUmWTFJldZyOJ4fTAiG7
v+YiesYSCHkTstyv9WsL6l4OV/hmx5Efe/6UuOSM5f42nTEwI5nxhlAi7N4UA1vE
LGHPvvy0wRH/NEG3/bsMM16kauiScIUPZj0ukaMxBhwjWKh7hKl/I7h+9seJpylP
utR/hME+CfPmU76gu0AQqbvbpCSP7V7XlBRPcJuENlTF9Jso6wocJdpixdpoQFFX
yb+7Ld6B1WvMZmuirnKq/S12Xuxza2dV2KenVf+GBaxxwPtVayKkcEd7gIL2FWwH
yV0xmGMD4HIkzJQI
=So/f
-----END PGP SIGNATURE-----

X
X
Xinglu Chen wrote on 23 Dec 2021 13:54
(name . Attila Lendvai)(address . attila@lendvai.name)
87a6grcwh7.fsf@yoctocell.xyz
Attila schrieb am Donnerstag der 23. Dezember 2021 um 11:18 GMT:

Toggle quote (10 lines)
>> Great, thanks for taking a look. I didn’t receive any message from
>> Attila though, and there doesn’t seem to be anything on the ML either.
>>
>> I guess he sent it when all the GNU infra was down, but unless he didn’t
>> Cc me, I don’t see why I wouldn’t receive it.
>
> yep. since then i have resent it, available at:
>
> https://issues.guix.gnu.org/52600#1

Oh, I already had it in my archive, but it was missing a subject, and it
wasn’t part of any thread, so I didn’t see it.

Now to the reply:

Toggle quote (17 lines)
> as for some higher level feedback:
>
> i have just finished my first Guix service, a rather complex
> one. based on the examples, and on the config codebase itself, i had
> used define-configuration, and i had encountered a surprise WRT
> undefined values and maybe- types (only after that have i found this
> documentation).
>
> > default-value is the default value corresponding to the field; if
> > none is specified, the user is forced to provide a value when creating
> > an object of the record type.
>
> 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.

Toggle quote (4 lines)
> 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”?

Toggle quote (3 lines)
> 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.

Toggle quote (4 lines)
> 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.

Toggle quote (9 lines)
> > Sometimes a field should not be serialized if the user doesn’t specify a
> > value. To achieve this, you can use the @code{define-maybe} macro to
> > define a ``maybe type''; if the value of a maybe type is set to the
> > @code{disabled}, it will not be serialized.
>
> the use of 'disabled here was very confusing because configuration
> objects are typically full of boolean fields... is 'disabled a valid
> app value, or part of the guix API?

Boolean fields would be specified using Guile booleans, which would then
get serialized to whatever syntax the configuration language expects.
But you are right that it could be ambigous sometimes.

Toggle quote (4 lines)
> 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
we could use “proper” Maybe types, like in SRFI-189[1]?


Toggle quote (7 lines)
> 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.

Toggle quote (5 lines)
> 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.

Toggle quote (16 lines)
> in fact, i had these two in my service impl, before reading/writing
> any of this:
>
> (define (defined-value? x)
> (if (eq? x 'undefined) #false x))
>
> (define (undefined-value? x)
> (eq? x 'undefined))
>
> then the question arises: do we want to differentiate between the
> cases when the field value comes from a default form, and when it is
> set by the user (e.g. at object construction time)? if so, then one
> well-known value as a marker is not enough, but i don't think it's
> worth the additional complexity. people with rare, complex use-cases
> can always resort to define-record*.

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

Toggle quote (9 lines)
> another thing that has initially misled me was the word 'serialize': i
> don't have a better suggestion, but i have associated it to a more
> formal serialize/deserialize protocol, as opposed to turning scheme
> objects into various different configuration file formats that are
> native for the target binary.
>
> maybe it's worth hinting at in the documentation where serialization
> is first mentioned.

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

Toggle snippet (9 lines)
The main utility is the @code{define-configuration} macro, which you
will use to define a Scheme record type (@pxref{Record Overview,,,
guile, GNU Guile Reference Manual}). The Scheme record will be
serialized to a configuration file by using @dfn{serializers}, which are
procedures that take some kind of Scheme value and returns a
G-expression (@pxref{G-Expressions}), which should, once serialized to
the disk, return a string. More details are listed below.

Toggle quote (4 lines)
> if the API of validate-configuration is to raise errors, then maybe it
> could return the config object if everything is fine. that can
> simplify the code at the call site.

That’s probably a good idea.
-----BEGIN PGP SIGNATURE-----

iQJJBAEBCAAzFiEEAVhh4yyK5+SEykIzrPUJmaL7XHkFAmHEcZQVHHB1YmxpY0B5
b2N0b2NlbGwueHl6AAoJEKz1CZmi+1x5a+EP/3OILpTCVj22h22NP1P5xlhZZP1R
/aXn+kKeIV+S6Nu0pmLYNwWhV14o6gzi4X5iJUSvDyULe0AT2BsviUSSlEsnQONL
/GQOwBlfJRE4rkfaOD9uxIzalDGYLTCvjnAnJz4sIXKIpxF8rJjESjTzowrNpE2h
Yatza/YWDJ/XvyY/r3FdycYOhAbeIGBOI/gfITKoK+bScot4cS84v0GU71VmHsXg
NVjMmiUyT5INC/rnNXHHKaN9n53SP86U/jndiubXDn65RLG4Ue/GTUAik4FxK9i+
uTqksbV0H7HBFsNtBi58A6XpasdnbEUt62kUZ++5gJgL74mMIXedejJ1MaQ3iOEj
4b6Xzg9fBNUGZXyaVVdQSWz4KK6UHS1KLxbUBE30R7iR37dgUiwDvZi1/rt+CZCb
9ulTod7YqxfKb5KTCNFZfjrPKHLh8o6Od629TP5BIuqXlJTY1nmjSWGRm17NcKXm
gSTqu6URtXtvi1u1BrREumzUWoSyCcp6kl4AFKsAiCpEERMdVNlObp6FbhzkYgsO
8itLxEd3oeKSsbcNwTH9zubpjyrT326HqHfK8ZBqqKJpEI0yURuymw5KLgwAUu/Q
2A0bFwbsVjhDmErXHf/cXffylEdx7hnM6Iv0nFbD2cmHYDNzGzcC52logs2FNk4o
uE/3uBtSyCz6v0nZ
=iZAt
-----END PGP SIGNATURE-----

A
A
Attila Lendvai wrote on 23 Dec 2021 16:21
(name . Xinglu Chen)(address . public@yoctocell.xyz)
hOdMN_9g5xix81tudVqVNNwnm78sINdTsV2WiUWnrmWPB6iksv0buLJP8dB6GV7c8I2WxXMk1kWGzLLTOb5bwlTTOV8QVHPFQV3rEUV9T1s=@lendvai.name
Toggle quote (10 lines)
> > 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).


Toggle quote (7 lines)
> > 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).


Toggle quote (7 lines)
> > 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.


Toggle quote (8 lines)
> > 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.


Toggle quote (8 lines)
> > 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


Toggle quote (4 lines)
> 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!


Toggle quote (12 lines)
> > 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.


Toggle quote (9 lines)
> > 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.


Toggle quote (4 lines)
> 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.


Toggle quote (4 lines)
> 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
M
M
Maxim Cournoyer wrote on 6 Jan 2022 15:20
(name . Xinglu Chen)(address . public@yoctocell.xyz)
87y23tq72h.fsf@gmail.com
Hi Xinglu,

Sorry for jumping in late.

Xinglu Chen <public@yoctocell.xyz> writes:

Toggle quote (3 lines)
>> As for ‘define-configuration’ vs. (guix records) vs. SRFI-9… I don’t
>> think we really discussed the issue or agreed on something.

[...]

Toggle quote (5 lines)
> Agreed, since ‘define-configuration’ & friends are now documented, it
> makes even more sense to use them.
>
>> Thanks for substantially improving the manual!

I haven't reviewed the feasibility yet, but since Guix records now have
"sanitizers" that can be used to validate the field values, I have been
wondering if these could be used in define-configuration.

Anyway, thanks a lot for improving and documenting
'define-configuration' :-).

Maxim
A
A
Attila Lendvai wrote on 18 Jan 2022 10:24
(name . Xinglu Chen)(address . public@yoctocell.xyz)
WKogm98_O511PtLC8RGROQidOqEQRgUmpLt8NXJwHMVldiQdI5Yqr5tK0uGVvQj1y_tHzEcL7xdDl3IGnNdFGpaFXao8zY9fTUXtxYSSuiM=@lendvai.name
Toggle quote (6 lines)
> I am not familiar with Guile internals, but I think that
> ‘#<unspecified>’ is just a thing that the pretty-printer prints. Maybe
> we could use “proper” Maybe types, like in SRFI-189[1]?
>
> [1]: Using https://srfi.schemers.org/srfi-189/srfi-189.html

this is indeed a good idea here, thanks for drawing my attention to
it! FTR, i have added guile-srfi-189 in this (not yet applied)
patch:


i think Maybe and Nothing covers exactly what i need here. my plan is
to get guile-srfi-189 into master, then make sure it can be used in
the Guix codebase, and then prepare a patch for the configuration code
that uses Nothing to represent unspecified values.

--
• attila lendvai
• PGP: 963F 5D5F 45C7 DFCD 0A39
--
Government means never having to say you're sorry…
?
Your comment

This issue is archived.

To comment on this conversation send an email to 52600@debbugs.gnu.org

To respond to this issue using the mumi CLI, first switch to it
mumi current 52600
Then, you may apply the latest patchset in this issue (with sign off)
mumi am -- -s
Or, compose a reply to this issue
mumi compose
Or, send patches to this issue
mumi send-email *.patch