Re: Syntactic Diabetes (was Re: A friendlier API for operating-system declarations)

2023-11-26 Thread Edouard Klein
Thank you Liliana and Attila for the swift and actionable feedback :)

Below is a revised proposition.

Here is a minimal working example of an os declaration:
--mwe.scm---
(use-modules
 (beaver system)
 (beaver functional-services)
 (gnu packages version-control)
 (gnu services web)
 (gnu services telephony)
 (gnu services ssh)
 (gnu services base)
 (guix gexp))

(-> (minimal-ovh "osef")
(instantiate nginx)
(instantiate mumble-server
 (welcome-text "coucou")
 (port 64738))
(extend openssh `(("alice" ,(local-file "/home/edouard/.ssh/id_rsa.pub"
(modify openssh
(password-authentication? #f)
(allow-empty-passwords? #t))
(remove guix))
---

To see the value of this syntactic sugar, try to replicate this MWE with
the standard syntax. It's not horrendous, but it *is* off-putting to
many newcomers to git, whereas this sugary piece is more readable for
them (sample size of 1, p=0.0005).

Here is the revised functional-services.scm, not yet commited and
pushed, and only lightly tested in local containers, but not in
production:

Advice and comments welcome :)


functional-services.scm--


(define-module (beaver functional-services)
   #:use-module (gnu system)
   #:use-module (gnu services)
   #:export (instantiate extend modify remove))

(define syntax->string (compose symbol->string syntax->datum))

(define (service-configuration stx service)
  "Return the syntax one can use to refer to xxx-configuration for the given
service"
  (datum->syntax stx (string->symbol
  (string-append
   (syntax->string service)
   "-configuration"

(define (service-type stx service)
  "Return the syntax one can use to refer to xxx-service-type for the given
service"
  (datum->syntax stx (string->symbol
  (string-append
   (syntax->string service)
   "-service-type"

(define-syntax instantiate
  (lambda (stx)
(syntax-case stx ()
  [(_ os service-name)
   (with-syntax
([service-type (service-type stx #'service-name)])
#'(begin
((lambda (x)  ;; It is wrapped in a lamba to make sure os is
   ;; evaluated once only. It it wasn't in a labmda, whatever
   ;; form os is in the calling code would be repeated
   ;; multiple times, and so if the form was e.g. (some-func
   ;; os), then some-func would be called multiple times,
   ;; which may not be desirable.
   (operating-system
 (inherit x)
 (services
  (cons
   (service service-type)
   (operating-system-user-services x)
 os)))]
  [(_ os service-name forms ...)
   (with-syntax
([service-type (service-type stx #'service-name)]
 [service-configuration (service-configuration stx #'service-name)])
#'(begin
((lambda (x)  ;; Wrapping in a lambda for the same reasons as above
   (operating-system
 (inherit x)
 (services
  (cons
   (service service-type
 (service-configuration forms ...))
   (operating-system-user-services x)
 os)))])))

(define-syntax extend
  (lambda (stx)
(syntax-case stx ()
  [(_ os service-name forms ...)
   (with-syntax
([service-type (service-type stx #'service-name)])
#'(begin
((lambda (x)
   (operating-system
 (inherit x)
 (services
  (cons
   (simple-service (format  #f "A ~a extension" (syntax->string 
#'service-name))
   service-type
   forms ...)
   (operating-system-user-services x)
 os)))])))

(define-syntax modify
  (lambda (stx)
(syntax-case stx ()
  [(_ os service-name forms ...)
   (with-syntax
([service-type (service-type stx #'service-name)]
 [service-configuration (service-configuration stx #'service-name)])
#'(begin
((lambda (x)
   (operating-system
 (inherit x)
 (services
  (modify-services (operating-system-user-services x)
(service-type
 config =>
 (service-configuration
  (inherit config)
  forms ...))
 os)))])))

(define-syntax remove
  (lambda (stx)
(syntax-case stx ()
  [(_ os service-name forms ...)
   (with-syntax
([service-type (service-type stx #'service-name)])
#'(begin
((lambda (x)
  

Re: Syntactic Diabetes (was Re: A friendlier API for operating-system declarations)

2023-11-26 Thread Liliana Marie Prikler
Am Sonntag, dem 26.11.2023 um 17:49 +0100 schrieb Edouard Klein:
> Thank you Liliana and Attila for the swift and actionable feedback :)
> 
> Below is a revised proposition.
> 
> Here is a minimal working example of an os declaration:
> --mwe.scm---
> (use-modules
>  (beaver system)
>  (beaver functional-services)
>  (gnu packages version-control)
>  (gnu services web)
>  (gnu services telephony)
>  (gnu services ssh)
>  (gnu services base)
>  (guix gexp))
> 
> (-> (minimal-ovh "osef")
>     (instantiate nginx)
I do wish you spelled out service.  Also, instantiate takes as much
characters to type as add-service.
>     (instantiate mumble-server
>  (welcome-text "coucou")
>  (port 64738))
>     (extend openssh `(("alice" ,(local-file
> "/home/edouard/.ssh/id_rsa.pub"
>     (modify openssh
>     (password-authentication? #f)
>     (allow-empty-passwords? #t))
>     (remove guix))
> ---
> 
> To see the value of this syntactic sugar, try to replicate this MWE
> with the standard syntax. It's not horrendous, but it *is* off-
> putting to many newcomers to git, whereas this sugary piece is more
> readable for them (sample size of 1, p=0.0005).
Well, that'd be 
  (let ((base (minimal-ovh "osef")))
(operating-system
  (inherit base)
  (services
(cons*
 (service nginx-service-type)
 (service mumble-service-type
  (mumble-configuration
   (welcome-text "couocu")
   (port 64738)))
 (service openssh-service-type
  (openssh-configuation
   (password-authentication? #f)
   (allow-empty-passwords? #t)
   (authorized-keys  (minimal-ovh "osef")
  (lambda (base) …))

On that note, we also have extend-openssh-authorized-keys for the use
with modify-services.

> Here is the revised functional-services.scm, not yet commited and
> pushed, and only lightly tested in local containers, but not in
> production:
> 
> Advice and comments welcome :)
> 
> 
> functional-services.scm--
> 
> 
> (define-module (beaver functional-services)
>    #:use-module (gnu system)
>    #:use-module (gnu services)
>    #:export (instantiate extend modify remove))
> 
> (define syntax->string (compose symbol->string syntax->datum))
> 
> (define (service-configuration stx service)
>   "Return the syntax one can use to refer to xxx-configuration for
> the given
> service"
>   (datum->syntax stx (string->symbol
>   (string-append
>    (syntax->string service)
>    "-configuration"
> 
> (define (service-type stx service)
>   "Return the syntax one can use to refer to xxx-service-type for the
> given
> service"
>   (datum->syntax stx (string->symbol
>   (string-append
>    (syntax->string service)
>    "-service-type"
> 
> (define-syntax instantiate
>   (lambda (stx)
>     (syntax-case stx ()
>   [(_ os service-name)
>    (with-syntax
>     ([service-type (service-type stx #'service-name)])
>     #'(begin
>     ((lambda (x)  ;; It is wrapped in a lamba to make sure os
> is
>    ;; evaluated once only. It it wasn't in a labmda,
> whatever
>    ;; form os is in the calling code would be repeated
>    ;; multiple times, and so if the form was e.g. (some-
> func
>    ;; os), then some-func would be called multiple times,
>    ;; which may not be desirable.
Isn't it also wrapped in a lambda, because -> is a threading macro that
takes functions rather than syntax?


Cheers



Re: Syntactic Diabetes (was Re: A friendlier API for operating-system declarations)

2023-11-26 Thread Michal Atlas

Hello guix,

I'll chime in because I've been playing around with this kind of thing 
in my channel [1]

for a bit now and perhaps some of the ideas will be deemed useful.


(service+ OS SERVICE [CONF])
(service- OS SERVICE)
(modify-service OS SERVICE UPDATE)



I found that defining a functional API like this is about as convenient 
as the original version, more examples: [2].


Another small change I tried was to not have -> be a macro but rather a 
simple fold.


I took what the dot-macro form expands to and made it a curried function 
called services

(but remove and modify could be equivalently defined),
which takes a service and then returns a lambda that takes an OS and 
modifies it

just as the beaver-labs macros do with that extra service.


(define ((services . new) os)
  (operating-system
    (inherit os)
    (user-services
  (append new (operating-system-user-services os)

(define (-> system . services)
  (fold (cut <> <>) system services))


(-> os (services (service openssh-service-type)) ...) ; yields os with 
added openssh


((services (service openssh-service-type)) os) ; equivalent


The one macro that is indispensable for terse configurations is still 
some sort of service+ (I use &s as the name),

which appends -service-type, surrounds the body in a -configuration [3],
and in my case calls `services` on it for good measure to get the os 
modifying lambda straight from the service+ clause.



(-> os
  (&s openssh)
  (&s guix-publish
    (advertise? #t))
  ...)


This ends up save for 2 extra characters being syntactically almost 
identical to the original version,

just passes around lambdas instead of getting manipulated by nested macros.

Them being functions they're now easier to work with with in more 
traditional Scheme ways,
for example compose works, and we can wrap them in other functions that 
for example apply them only in some circumstances.
Or combine transformations into new ones. The definitions for which end 
up being remarkably short and generalizable.



(-> os

  (compose (os/hostname "the-dam.org") (&s gpm)) ; freely composable 
with other functions of the same sort


  (%services-with-arguments ...) ; indifferent to being composed on the 
spot even in the middle of threading


  (if-host "hydra" (&s openssh)) ; only adds openssh if the host is 
named hydra, simple function


   ; since the second argument is just a function that may or 
may not be applied by the new lambda


  ((mapped-file-systems  ...)

     ...))) ; not threading by 
macro allows more complex structures without confusion




Then again none of this has had proper field testing, the fact that it 
has less gotchas is just a personal opinion.



Cheers, and all the best


[1]: 
https://git.sr.ht/~michal_atlas/guix-channel/tree/a31b68b46da60002383e2793eba88b99fc5c2382/item/atlas/combinators.scm 
(modified from the original beaver-labs version).


[2]: 
https://git.sr.ht/~michal_atlas/dotfiles/tree/8c78f53139ae176ff0a4cab82ad0fb64bce6989b/item/atlas/config/system/services.scm#L56 



[3]: 
https://git.sr.ht/~michal_atlas/guix-channel/tree/master/item/atlas/utils/services.scm#L53 






Re: Syntactic Diabetes (was Re: A friendlier API for operating-system declarations)

2023-11-26 Thread Edouard Klein


Liliana Marie Prikler  writes:
>>     (instantiate nginx)
> I do wish you spelled out service.  Also, instantiate takes as much
> characters to type as add-service.
>

Done, see below. I was worried about the paronymy between add-service
and add-services which already exists. I defer to you and your
experience on this, naming things is hard.

>> To see the value of this syntactic sugar, try to replicate this MWE
>> with the standard syntax. It's not horrendous, but it *is* off-
>> putting to many newcomers to git, whereas this sugary piece is more
>> readable for them (sample size of 1, p=0.0005).
> Well, that'd be
> ...

I tried:

(let ((base (minimal-ovh "osef")))
  (operating-system
(inherit base)
(services
  (cons*
   (service nginx-service-type)
   (service mumble-server-service-type
(mumble-server-configuration
 (welcome-text "couocu")
 (port 64738)))
   (service openssh-service-type
(openssh-configuration
 (password-authentication? #f)
 (allow-empty-passwords? #t)
 (authorized-keys `(("alice" ,(local-file 
"/home/edouard/.ssh/id_rsa.pub"))
   (operating-system-user-services base)


I admit that this is as readable as the sweet version, but:

- Openssh is already defined in  (minimal-ovh "osef"), so the build
  fails with: 'guix system: error: service 'ssh-daemon' provided more
  than once'
- you forgot  the removal of guix-service, which admitedly is not used much in 
practice
  anyway

The problem with those two is that they break the nice structure of just
adding services, and now one has to first remove an element from
(operating-system-user-services base), then edit one of the element of
the resulting list, then add to elements to it. It is probably possible
to write it in a readable way, maybe less so than the sweet version, but
not so much as to justify adding the new macros to guix.

However the readability is not the main selling point, the writeability
is. Please do not discount how hard it is to write that kind of stuff
when scheme's not your main language. It is possible people here forgot
how hard this is for beginners, especially those like me whose brain is
deformed from years using algol-derived syntaxes.

I think we are losing a lot of mindshare because of the hard step of
learning both guile and guix, and the kind of syntactic sugar I
suggest may help bridge the gap long enough for newcomers to ease into
the syntax, after they get a taste of guix' capabilities.

Another experiment: with the sweet syntax, you can easily extend
services, without having to define a -record-type, etc. Just define a
function. I can write more at length about that if you want.

> On that note, we also have extend-openssh-authorized-keys for the use
> with modify-services.
>
I see it now in the source, but I was unaware of its existence.




Thanks for the swift again review :)

Cheers,

Edouard.


-mwe.scm

(use-modules
 (beaver system)
 (beaver functional-services)
 (gnu packages version-control)
 (gnu services web)
 (gnu services telephony)
 (gnu services ssh)
 (gnu services base)
 (guix gexp))

(-> (minimal-ovh "osef")
(add-service nginx)
(add-service mumble-server
 (welcome-text "coucou")
 (port 64738))
(extend-service openssh `(("alice" ,(local-file 
"/home/edouard/.ssh/id_rsa.pub"
(modify-service openssh
(password-authentication? #f)
(allow-empty-passwords? #t))
(remove-service guix))

--functional-services.scm

(define-module (beaver functional-services)
   #:use-module (gnu system)
   #:use-module (gnu services)
   #:export (add-service extend-service modify-service remove-service))

(define syntax->string (compose symbol->string syntax->datum))

(define (service-configuration stx service)
  "Return the syntax one can use to refer to xxx-configuration for the given
service"
  (datum->syntax stx (string->symbol
  (string-append
   (syntax->string service)
   "-configuration"

(define (service-type stx service)
  "Return the syntax one can use to refer to xxx-service-type for the given
service"
  (datum->syntax stx (string->symbol
  (string-append
   (syntax->string service)
   "-service-type"

(define-syntax add-service
  (lambda (stx)
(syntax-case stx ()
  [(_ os service-name)
   (with-syntax
([service-type (service-type stx #'service-name)])
#'(begin
((lambda (x)  ;; It is wrapped in a lamba to make sure os is
   ;; evaluated once only. It it wasn't in a labmda, whatever
   ;; form os is in the calling code would be repeated
   ;; multiple times, and so if the form was e.g. (some-func
   ;; os), then some-fu

role of core-updates

2023-11-26 Thread Andy Tai
Hi, hope Guix maintainers can clarify the role of the now core-updates
branch; the current documentation does not specify the core-updates
branch as a thing but there are clearly interests and uses of this
branch for package updates not belonging to a feature branch like
gnome and it is useful for, say, updating to the GNU make package
which would have caused world rebuild.  Thanks