* better error messages through assertions
@ 2022-02-14 22:32 Ricardo Wurmus
2022-02-15 8:48 ` Maxime Devos
` (3 more replies)
0 siblings, 4 replies; 20+ messages in thread
From: Ricardo Wurmus @ 2022-02-14 22:32 UTC (permalink / raw)
To: guix-devel
Hi Guix,
today on IRC someone reported an ugly error message when reconfiguring
their system:
--8<---------------cut here---------------start------------->8---
Backtrace:
18 (primitive-load "/home/me/.config/guix/current/bin/…")
In guix/ui.scm:
2209:7 17 (run-guix . _)
2172:10 16 (run-guix-command _ . _)
In ice-9/boot-9.scm:
1752:10 15 (with-exception-handler _ _ #:unwind? _ # _)
In guix/status.scm:
822:3 14 (_)
802:4 13 (call-with-status-report _ _)
In guix/scripts/system.scm:
1256:4 12 (_)
In ice-9/boot-9.scm:
1752:10 11 (with-exception-handler _ _ #:unwind? _ # _)
In guix/store.scm:
658:37 10 (thunk)
1320:8 9 (call-with-build-handler #<procedure 7fecaf8570c0 at g…> …)
2123:24 8 (run-with-store #<store-connection 256.99 7fecb75c7230> …)
In guix/scripts/system.scm:
827:2 7 (_ _)
703:7 6 (_ #<store-connection 256.99 7fecb75c7230>)
In gnu/system.scm:
1227:19 5 (operating-system-derivation _)
In gnu/services.scm:
1091:6 4 (instantiate-missing-services _)
In srfi/srfi-1.scm:
460:18 3 (fold #<procedure 7fecb73c0960 at gnu/services.scm:109…> …)
In gnu/services.scm:
1092:27 2 (_ (#<<service> type: #<service-type gdm 7fecbd17f6…> …) …)
In ice-9/boot-9.scm:
1685:16 1 (raise-exception _ #:continuable? _)
1685:16 0 (raise-exception _ #:continuable? _)
ice-9/boot-9.scm:1685:16: In procedure raise-exception:
In procedure struct-vtable: Wrong type argument in position 1 (expecting struct):
--8<---------------cut here---------------end--------------->8---
As you can probably tell easily by looking at this message, the
“service” field of the operating system configuration looked something
like this:
(services (append (list a b c %desktop-services) #;oops))
instead of this
(services (append (list a b c) %desktop-services))
This is because INSTANTIATE-MISSING-SERVICES — and FOLD-SERVICES, and
many more — assumes that it is only passed a plain list of services. It
then proceeds to call SERVICE-KIND on what may or may not be a service.
I think we should add simple type checks, something like this:
(define (listof pred)
(lambda (thing)
(and (list? thing) (every pred thing))))
…
(define (assert-type type-check thing message)
(or (false-if-exception (type-check thing))
(report-error (G_ "type error: …\n" message))))
;; Use ASSERT-TYPE in an example procedure.
(define (do-something-with-services services)
(assert-type (listof service?) services
"SERVICES must be a list of <service> values.")
;; Do things…
(map service-kind services))
What do you think? There are many different ways of implementing this
(a new variant of DEFINE that also accepts a type declaration, an assert
like above, a fancier assert that composes a helpful error message by
itself, a separate type declaration that is looked up only when the
corresponding procedure is called in a certain context, etc), but I’d
first like to know if there is consensus that we want something like
this.
--
Ricardo
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: better error messages through assertions
2022-02-14 22:32 better error messages through assertions Ricardo Wurmus
@ 2022-02-15 8:48 ` Maxime Devos
2022-02-15 21:45 ` Philip McGrath
` (2 subsequent siblings)
3 siblings, 0 replies; 20+ messages in thread
From: Maxime Devos @ 2022-02-15 8:48 UTC (permalink / raw)
To: Ricardo Wurmus, guix-devel
[-- Attachment #1: Type: text/plain, Size: 627 bytes --]
Ricardo Wurmus schreef op ma 14-02-2022 om 23:32 [+0100]:
> I think we should add simple type checks, something like this:
> [...]
>
> What do you think? There are many different ways of implementing this
> (a new variant of DEFINE that also accepts a type declaration, an assert
> like above, a fancier assert that composes a helpful error message by
> itself, a separate type declaration that is looked up only when the
> corresponding procedure is called in a certain context, etc), but I’d
> first like to know if there is consensus that we want something like
> this.
Seems nice.
Greetings,
Maxime.
[-- Attachment #2: This is a digitally signed message part --]
[-- Type: application/pgp-signature, Size: 260 bytes --]
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: better error messages through assertions
2022-02-14 22:32 better error messages through assertions Ricardo Wurmus
2022-02-15 8:48 ` Maxime Devos
@ 2022-02-15 21:45 ` Philip McGrath
2022-02-15 22:15 ` Ricardo Wurmus
2022-02-22 4:31 ` Arun Isaac
2022-02-25 18:55 ` Maxim Cournoyer
3 siblings, 1 reply; 20+ messages in thread
From: Philip McGrath @ 2022-02-15 21:45 UTC (permalink / raw)
To: Ricardo Wurmus, guix-devel, Maxime Devos
Hi,
On 2/14/22 17:32, Ricardo Wurmus wrote:
> As you can probably tell easily by looking at this message, the
> “service” field of the operating system configuration looked something
> like this:
>
> (services (append (list a b c %desktop-services) #;oops))
>
> instead of this
>
> (services (append (list a b c) %desktop-services))
>
> This is because INSTANTIATE-MISSING-SERVICES — and FOLD-SERVICES, and
> many more — assumes that it is only passed a plain list of services. It
> then proceeds to call SERVICE-KIND on what may or may not be a service.
>
> I think we should add simple type checks, something like this:
>
> (define (listof pred)
> (lambda (thing)
> (and (list? thing) (every pred thing))))
> …
> (define (assert-type type-check thing message)
> (or (false-if-exception (type-check thing))
> (report-error (G_ "type error: …\n" message))))
>
> ;; Use ASSERT-TYPE in an example procedure.
> (define (do-something-with-services services)
> (assert-type (listof service?) services
> "SERVICES must be a list of <service> values.")
>
> ;; Do things…
> (map service-kind services))
>
> What do you think? There are many different ways of implementing this
> (a new variant of DEFINE that also accepts a type declaration, an assert
> like above, a fancier assert that composes a helpful error message by
> itself, a separate type declaration that is looked up only when the
> corresponding procedure is called in a certain context, etc), but I’d
> first like to know if there is consensus that we want something like
> this.
>
As a Guix user and contributor, I would love better error messages.
As a Racketeer, I think you're half way to reinventing contracts.
In particular, since the operating system services field is thunked,
this example already points to the desirability of higher-order contracts.
Using "contracts" need not initially involve all the bells and whistles
of Racket's contract library. For example, higher-order contracts can be
implemented with `lambda` in the absence of special runtime support for
chaperones and impersonators, as illustrated in [1]. But the Racket
community has accumulated a great deal of both theoretical insight and
implementation experience in many subtle corners of this problem: I hope
Guix can build on that experience.
-Philip
[1]: https://docs.racket-lang.org/guide/Building_New_Contracts.html
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: better error messages through assertions
2022-02-15 21:45 ` Philip McGrath
@ 2022-02-15 22:15 ` Ricardo Wurmus
2022-02-28 12:59 ` Ludovic Courtès
0 siblings, 1 reply; 20+ messages in thread
From: Ricardo Wurmus @ 2022-02-15 22:15 UTC (permalink / raw)
To: Philip McGrath; +Cc: guix-devel
Philip McGrath <philip@philipmcgrath.com> writes:
> As a Racketeer, I think you're half way to reinventing contracts.
Yes, I was in fact thinking of contracts, but shied away from mentioning
them :) The reason is that I think we can cover a lot of distance with
just a few simple assertions to avoid plowing ahead on bad arguments.
--
Ricardo
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: better error messages through assertions
2022-02-14 22:32 better error messages through assertions Ricardo Wurmus
2022-02-15 8:48 ` Maxime Devos
2022-02-15 21:45 ` Philip McGrath
@ 2022-02-22 4:31 ` Arun Isaac
2022-02-25 18:55 ` Maxim Cournoyer
3 siblings, 0 replies; 20+ messages in thread
From: Arun Isaac @ 2022-02-22 4:31 UTC (permalink / raw)
To: Ricardo Wurmus, guix-devel
[-- Attachment #1: Type: text/plain, Size: 635 bytes --]
Hi Ricardo,
I too would like to see much better error checking for our services,
though I don't know what the best way to achieve that is.
Type checking alone may not be sufficient. All types in a configuration
record could be valid, but some combination of field values in the
record may not be valid. For a quick and dumb example, it may not be
allowed to configure a service to listen on a TCP port and a Unix socket
at the same time, and we should error out if it is told to do so. For
these use cases, we need to support more complex predicates that take
the whole configuration record and check if it is valid.
Regards,
Arun
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 524 bytes --]
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: better error messages through assertions
2022-02-14 22:32 better error messages through assertions Ricardo Wurmus
` (2 preceding siblings ...)
2022-02-22 4:31 ` Arun Isaac
@ 2022-02-25 18:55 ` Maxim Cournoyer
2022-02-26 13:33 ` Ricardo Wurmus
3 siblings, 1 reply; 20+ messages in thread
From: Maxim Cournoyer @ 2022-02-25 18:55 UTC (permalink / raw)
To: Ricardo Wurmus; +Cc: guix-devel
Hello Ricardo,
Ricardo Wurmus <rekado@elephly.net> writes:
> Hi Guix,
>
> today on IRC someone reported an ugly error message when reconfiguring
> their system:
>
> Backtrace:
> 18 (primitive-load "/home/me/.config/guix/current/bin/…")
> In guix/ui.scm:
> 2209:7 17 (run-guix . _)
> 2172:10 16 (run-guix-command _ . _)
> In ice-9/boot-9.scm:
> 1752:10 15 (with-exception-handler _ _ #:unwind? _ # _)
> In guix/status.scm:
> 822:3 14 (_)
> 802:4 13 (call-with-status-report _ _)
> In guix/scripts/system.scm:
> 1256:4 12 (_)
> In ice-9/boot-9.scm:
> 1752:10 11 (with-exception-handler _ _ #:unwind? _ # _)
> In guix/store.scm:
> 658:37 10 (thunk)
> 1320:8 9 (call-with-build-handler #<procedure 7fecaf8570c0 at g…> …)
> 2123:24 8 (run-with-store #<store-connection 256.99 7fecb75c7230> …)
> In guix/scripts/system.scm:
> 827:2 7 (_ _)
> 703:7 6 (_ #<store-connection 256.99 7fecb75c7230>)
> In gnu/system.scm:
> 1227:19 5 (operating-system-derivation _)
> In gnu/services.scm:
> 1091:6 4 (instantiate-missing-services _)
> In srfi/srfi-1.scm:
> 460:18 3 (fold #<procedure 7fecb73c0960 at gnu/services.scm:109…> …)
> In gnu/services.scm:
> 1092:27 2 (_ (#<<service> type: #<service-type gdm 7fecbd17f6…> …) …)
> In ice-9/boot-9.scm:
> 1685:16 1 (raise-exception _ #:continuable? _)
> 1685:16 0 (raise-exception _ #:continuable? _)
>
> ice-9/boot-9.scm:1685:16: In procedure raise-exception:
> In procedure struct-vtable: Wrong type argument in position 1 (expecting struct):
>
> As you can probably tell easily by looking at this message, the
> “service” field of the operating system configuration looked something
> like this:
>
> (services (append (list a b c %desktop-services) #;oops))
>
> instead of this
>
> (services (append (list a b c) %desktop-services))
>
> This is because INSTANTIATE-MISSING-SERVICES — and FOLD-SERVICES, and
> many more — assumes that it is only passed a plain list of services. It
> then proceeds to call SERVICE-KIND on what may or may not be a service.
>
> I think we should add simple type checks, something like this:
>
> (define (listof pred)
> (lambda (thing)
> (and (list? thing) (every pred thing))))
> …
> (define (assert-type type-check thing message)
> (or (false-if-exception (type-check thing))
> (report-error (G_ "type error: …\n" message))))
>
> ;; Use ASSERT-TYPE in an example procedure.
> (define (do-something-with-services services)
> (assert-type (listof service?) services
> "SERVICES must be a list of <service> values.")
>
> ;; Do things…
> (map service-kind services))
>
> What do you think? There are many different ways of implementing this
> (a new variant of DEFINE that also accepts a type declaration, an assert
> like above, a fancier assert that composes a helpful error message by
> itself, a separate type declaration that is looked up only when the
> corresponding procedure is called in a certain context, etc), but I’d
> first like to know if there is consensus that we want something like
> this.
I hear we now have "field sanitizers" on Guix records; without having
dug the details, it seems to be we could add a predicate validating the
input there? The nice thing about it is that it'd be a one place
change, instead of asserts to sprinkle around various places.
Thanks,
Maxim
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: better error messages through assertions
2022-02-25 18:55 ` Maxim Cournoyer
@ 2022-02-26 13:33 ` Ricardo Wurmus
2022-02-26 13:51 ` Maxim Cournoyer
2022-02-28 13:02 ` Ludovic Courtès
0 siblings, 2 replies; 20+ messages in thread
From: Ricardo Wurmus @ 2022-02-26 13:33 UTC (permalink / raw)
To: Maxim Cournoyer; +Cc: guix-devel
Maxim Cournoyer <maxim.cournoyer@gmail.com> writes:
> I hear we now have "field sanitizers" on Guix records; without having
> dug the details, it seems to be we could add a predicate validating the
> input there?
I don’t see how that would help here. In my example the service values
themselves are all right. It’s a procedure acting on what it assumes is
a list of service values that fails.
Record field validation would not have prevented that error.
--
Ricardo
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: better error messages through assertions
2022-02-26 13:33 ` Ricardo Wurmus
@ 2022-02-26 13:51 ` Maxim Cournoyer
2022-02-28 13:02 ` Ludovic Courtès
1 sibling, 0 replies; 20+ messages in thread
From: Maxim Cournoyer @ 2022-02-26 13:51 UTC (permalink / raw)
To: Ricardo Wurmus; +Cc: guix-devel
Hi Ricardo,
Ricardo Wurmus <rekado@elephly.net> writes:
> Maxim Cournoyer <maxim.cournoyer@gmail.com> writes:
>
>> I hear we now have "field sanitizers" on Guix records; without having
>> dug the details, it seems to be we could add a predicate validating the
>> input there?
>
> I don’t see how that would help here. In my example the service values
> themselves are all right. It’s a procedure acting on what it assumes is
> a list of service values that fails.
>
> Record field validation would not have prevented that error.
Thanks for explaining. For what it's worth, what you are proposing
seems a sound improvement to me, so feel free to go ahead with patches!
Thanks,
Maxim
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: better error messages through assertions
2022-02-15 22:15 ` Ricardo Wurmus
@ 2022-02-28 12:59 ` Ludovic Courtès
2022-02-28 16:18 ` Philip McGrath
0 siblings, 1 reply; 20+ messages in thread
From: Ludovic Courtès @ 2022-02-28 12:59 UTC (permalink / raw)
To: Ricardo Wurmus; +Cc: guix-devel
Hi!
Ricardo Wurmus <rekado@elephly.net> skribis:
> Philip McGrath <philip@philipmcgrath.com> writes:
>
>> As a Racketeer, I think you're half way to reinventing contracts.
>
> Yes, I was in fact thinking of contracts, but shied away from mentioning
> them :) The reason is that I think we can cover a lot of distance with
> just a few simple assertions to avoid plowing ahead on bad arguments.
I’d very much like to have contracts.
For record types, we have “sanitizers” right now, which we could use to
insert procedural type checks. It wouldn’t be as nice as proper
declarative contracts though, unless we arrange to make them look like
contracts.
Thoughts?
Ludo’.
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: better error messages through assertions
2022-02-26 13:33 ` Ricardo Wurmus
2022-02-26 13:51 ` Maxim Cournoyer
@ 2022-02-28 13:02 ` Ludovic Courtès
2022-02-28 16:00 ` Maxim Cournoyer
1 sibling, 1 reply; 20+ messages in thread
From: Ludovic Courtès @ 2022-02-28 13:02 UTC (permalink / raw)
To: Ricardo Wurmus; +Cc: guix-devel, Maxim Cournoyer
Ricardo Wurmus <rekado@elephly.net> skribis:
> Maxim Cournoyer <maxim.cournoyer@gmail.com> writes:
>
>> I hear we now have "field sanitizers" on Guix records; without having
>> dug the details, it seems to be we could add a predicate validating the
>> input there?
>
> I don’t see how that would help here. In my example the service values
> themselves are all right. It’s a procedure acting on what it assumes is
> a list of service values that fails.
>
> Record field validation would not have prevented that error.
I think we could validate primarily at the user/core boundary. If the
‘services’ field had a sanitizer ensuring it’s a list of services, then
it’d be fine.
Ludo’.
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: better error messages through assertions
2022-02-28 13:02 ` Ludovic Courtès
@ 2022-02-28 16:00 ` Maxim Cournoyer
0 siblings, 0 replies; 20+ messages in thread
From: Maxim Cournoyer @ 2022-02-28 16:00 UTC (permalink / raw)
To: Ludovic Courtès; +Cc: Ricardo Wurmus, guix-devel
Hello,
Ludovic Courtès <ludo@gnu.org> writes:
> Ricardo Wurmus <rekado@elephly.net> skribis:
>
>> Maxim Cournoyer <maxim.cournoyer@gmail.com> writes:
>>
>>> I hear we now have "field sanitizers" on Guix records; without having
>>> dug the details, it seems to be we could add a predicate validating the
>>> input there?
>>
>> I don’t see how that would help here. In my example the service values
>> themselves are all right. It’s a procedure acting on what it assumes is
>> a list of service values that fails.
>>
>> Record field validation would not have prevented that error.
>
> I think we could validate primarily at the user/core boundary. If the
> ‘services’ field had a sanitizer ensuring it’s a list of services, then
> it’d be fine.
That seems a fine approach to me; thank you for your input. Ricardo,
would you like to give idea a shot and see if it suites the bill? :-).
Maxim
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: better error messages through assertions
2022-02-28 12:59 ` Ludovic Courtès
@ 2022-02-28 16:18 ` Philip McGrath
2022-03-07 10:13 ` Ludovic Courtès
0 siblings, 1 reply; 20+ messages in thread
From: Philip McGrath @ 2022-02-28 16:18 UTC (permalink / raw)
To: Ricardo Wurmus, Ludovic Courtès; +Cc: guix-devel
[-- Attachment #1: Type: text/plain, Size: 8341 bytes --]
Hi,
On Monday, February 28, 2022 7:59:02 AM EST Ludovic Courtès wrote:
> Hi!
>
> Ricardo Wurmus <rekado@elephly.net> skribis:
> > Philip McGrath <philip@philipmcgrath.com> writes:
> >> As a Racketeer, I think you're half way to reinventing contracts.
> >
> > Yes, I was in fact thinking of contracts, but shied away from mentioning
> > them :) The reason is that I think we can cover a lot of distance with
> > just a few simple assertions to avoid plowing ahead on bad arguments.
>
> I’d very much like to have contracts.
>
> For record types, we have “sanitizers” right now, which we could use to
> insert procedural type checks. It wouldn’t be as nice as proper
> declarative contracts though, unless we arrange to make them look like
> contracts.
>
I'm going to try to sketch what I think is a minimal API for contracts.
Racket's state-of-the-art contract system has many features and nuances. I *do
not* think anyone should try to implement them all in one fell swoop. I'm
hoping there's a way to implement your simple assertions with only a modest
amount of overhead that will provide the right base on which to grow the rest
of a contract system. In the short term, the advantage over:
> (assert-type (listof service?) services
> "SERVICES must be a list of <service> values.")
is that you don't have to write error messages by hand.
You need two types of values:
1. Contracts, recognized by `contract?`; and
2. Blame objects, recognized by `blame?`.
In simplest terms, a blame object holds context information for error
reporting. Ultimately, it is used to raise an exception with
`raise-blame-error`[1]: this can support e.g. substituting "expected"/"given"
with "promised"/"produced" in error messages based on the context. A contract
combinator will typically give its sub-contract a blame object extended using
`blame-add-context`[2].
The primitive operation on a contract is `get/build-late-neg-projection`[3]:
by skipping the decades experimenting with other APIs that turned out to
perform less well, you have the opportunity to give this function a better
name. In pseudo-code, `get/build-late-neg-projection` has the contract:
(-> contract? (-> blame? (-> val neg-party val*)))
That is, given a contract, it returns a function that, when given a blame
object, returns a function that enforces the contract. The enforcer function
accepts the value being checked and the "negative party" and either raises an
exception or returns the checked value, possibly wrapped to enforce higher-
order checks.
(The "negative party" is simultaneously deeply important and something you
could ignore to start with. Simply put, if a module `(server)` provides a
contracted value that is used by a module `(client foo)`, the "negative party"
represents `(client foo)`, e.g. via a source location. It is the only part
that would be different between `(client foo)` and `(client bar)`, so this API
lets everything else be shared.)
The multiple levels of `lambda` let contract combinators do some of their work
ahead of time, improving performance when the contracted value is used.
As a motivating example, here is a very basic definition of the `listof`
contract combinator. Notice in particular:
* Predicate functions and certain kinds of primitive data are implicitly
promoted to contracts. This wasn't original to Racket's contract system,
but the ergonomics are so compelling you might want it from the beginning.
* Forms like `define/contract`, `struct/contract`, and `contract-out` (which
is preferred) attach contracts to values. Guix might want
`define-record-type*/contract`. There is no public API for constructing
blame objects.
> #lang racket
> (define (listof elem/c)
> (let* (;; error if elem/c isn't a contract
> [elem/c (coerce-contract 'listof elem/c)]
> [elem-late-neg (get/build-late-neg-projection elem/c)])
> (make-contract
> #:name (build-compound-type-name 'listof elem/c)
> #:late-neg-projection
> (λ (blame)
> (define elem-proj+blame
> (elem-late-neg (blame-add-context blame "an element of")))
> (λ (val neg-party)
> (unless (list? val)
> (raise-blame-error
> blame val #:missing-party neg-party
> `(expected: "a list" given: "~e")
> val))
> (for-each (λ (elem)
> (elem-proj+blame elem neg-party))
> val)
> val)))))
>
> (define/contract numbers
> (listof number?)
> '(1 2 2.5 3))
>
> (define/contract fruits
> (listof (or/c 'apple 'banana))
> (list (λ (x) (x x))))
The resulting error message:
> fruits: broke its own contract
> promised: (or/c (quote apple) (quote banana))
> produced: #<procedure:...of-contract-mvp.rkt:27:8>
> in: an element of
> (listof (or/c 'apple 'banana))
> contract from: (definition fruits)
> blaming: (definition fruits)
> (assuming the contract is correct)
> at: /tmp/listof-contract-mvp.rkt:25:17
The most important optimization I left out is providing a specialized first-
order check for use by `or/c`. But even that, in principle, wouldn't have to
be part of an initial implementation.
And, again, you should only need even this much work for a combinator or a
higher-order contract.
Do note that this is all fresh off the top of my head in about an hour, and
I've only incidentally hacked on the internals of the Racket contract system!
If using contracts in Guix is at all appealing, I'd strongly suggest asking
for advice at <https://racket.discourse.group> before starting to write code.
A few other resources:
* "Inside Racket Seminar: Robby Findler on Contracts"
<https://www.youtube.com/watch?v=eUsDKbKtrLs>
A two-hour video walkthrough/Q&A about the implementation of Racket's
contract system. Probably the most comprehensive resource that exists on
how to implement contracts, as opposed to interesting things to do with
contracts once you've got them.
* Robby Findler, "Behavioral Software Contracts" (ICFP 2014)
https://www.youtube.com/watch?v=gXTbMPVFP1M
Wonderful slides and illustrations help to illuminate some of the more
difficult concepts.
In particular, I recommend the section @11:10, "Contracts are not Types",
which explains some very interesting contracts like the one on `dc`[4]
that take advantage of contracts being checked *at runtime* to enforce
very nuanced invariants, somewhat like "dependent types" requiring a
drawing procedure to restore any changes it makes to Cairo's mutable
state. This would support examples like Arun's: say, checking that no two
services are trying to listen on the same TCP port.
The concept of "boundaries" as a design principle is an important lesson
learned.
The source code for the slides (which are a program in `#lang slideshow`,
of course) is at <https://github.com/rfindler/icfp-2014-contracts-talk>.
* "Oh Lord, Please Don’t Let Contracts Be Misunderstood"
Dimoulas, New, Findler, & Felleisen (ICFP 2016)
https://users.cs.northwestern.edu/~robby/pubs/papers/icfp2016-dnff.pdf
Focused on pragmatics (as opposed to fancy math), with discussion both of
how a contract system is put together and of some interesting uses.
I would love to have contracts in Guix, even very rudimentary contracts. If
it's something the community more generally would be interested in, I'd be
glad to help as much as I can.
-Philip
[1]: https://docs.racket-lang.org/reference/Building_New_Contract_Combinators.html#%28def._%28%28lib._racket%2Fcontract%2Fprivate%2Fblame..rkt%29._raise-blame-error%29%29
[2]: https://docs.racket-lang.org/reference/Building_New_Contract_Combinators.html#%28def._%28%28lib._racket%2Fcontract%2Fprivate%2Fblame..rkt%29._blame-add-context%29%29
[2]: https://docs.racket-lang.org/reference/contract-utilities.html#%28def._%28%28lib._racket%2Fcontract%2Fprivate%2Fguts..rkt%29._get%2Fbuild-late-neg-projection%29%29
[4]: https://docs.racket-lang.org/pict/Basic_Pict_Constructors.html#%28def._%28%28lib._pict%2Fmain..rkt%29._dc%29%29
[-- Attachment #2: This is a digitally signed message part. --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: better error messages through assertions
2022-02-28 16:18 ` Philip McGrath
@ 2022-03-07 10:13 ` Ludovic Courtès
2022-03-28 20:25 ` Philip McGrath
0 siblings, 1 reply; 20+ messages in thread
From: Ludovic Courtès @ 2022-03-07 10:13 UTC (permalink / raw)
To: Philip McGrath; +Cc: Ricardo Wurmus, guix-devel
Hi Philip,
Philip McGrath <philip@philipmcgrath.com> skribis:
> Racket's state-of-the-art contract system has many features and nuances. I *do
> not* think anyone should try to implement them all in one fell swoop. I'm
> hoping there's a way to implement your simple assertions with only a modest
> amount of overhead that will provide the right base on which to grow the rest
> of a contract system. In the short term, the advantage over:
>
>> (assert-type (listof service?) services
>> "SERVICES must be a list of <service> values.")
>
> is that you don't have to write error messages by hand.
>
> You need two types of values:
>
> 1. Contracts, recognized by `contract?`; and
> 2. Blame objects, recognized by `blame?`.
[...]
Thanks for the explanation and references! I had briefly looked at
Racket’s contract API in the past but your message gave a clearer view
of how this all fits together.
> I would love to have contracts in Guix, even very rudimentary contracts. If
> it's something the community more generally would be interested in, I'd be
> glad to help as much as I can.
It’d be great to benefit from your expertise here. Like you wrote, I
think we should start with a simple contract system, certainly simpler
than Racket’s, and build from there.
If you’re willing and able to spend time prototyping this, that’s great.
:-)
Thanks,
Ludo’.
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: better error messages through assertions
2022-03-07 10:13 ` Ludovic Courtès
@ 2022-03-28 20:25 ` Philip McGrath
2022-03-30 9:37 ` Ludovic Courtès
0 siblings, 1 reply; 20+ messages in thread
From: Philip McGrath @ 2022-03-28 20:25 UTC (permalink / raw)
To: Ludovic Courtès; +Cc: Ricardo Wurmus, guix-devel
Hi,
On 3/7/22 05:13, Ludovic Courtès wrote:
> Hi Philip,
>
> Philip McGrath <philip@philipmcgrath.com> skribis:
>
>> Racket's state-of-the-art contract system has many features and nuances. I *do
>> not* think anyone should try to implement them all in one fell swoop. I'm
>> hoping there's a way to implement your simple assertions with only a modest
>> amount of overhead that will provide the right base on which to grow the rest
>> of a contract system. In the short term, the advantage over:
>>
>>> (assert-type (listof service?) services
>>> "SERVICES must be a list of <service> values.")
>>
>> is that you don't have to write error messages by hand.
>>
>> You need two types of values:
>>
>> 1. Contracts, recognized by `contract?`; and
>> 2. Blame objects, recognized by `blame?`.
>
> [...]
>
> Thanks for the explanation and references! I had briefly looked at
> Racket’s contract API in the past but your message gave a clearer view
> of how this all fits together.
>
I'm glad this is something Guix people are interested in!
>> I would love to have contracts in Guix, even very rudimentary contracts. If
>> it's something the community more generally would be interested in, I'd be
>> glad to help as much as I can.
>
> It’d be great to benefit from your expertise here. Like you wrote, I
> think we should start with a simple contract system, certainly simpler
> than Racket’s, and build from there.
>
> If you’re willing and able to spend time prototyping this, that’s great.
> :-)
>
I'm interested in putting together a prototype.
I've taken my own suggestion and asked the Racket community for more
advice:
https://racket.discourse.group/t/advice-on-implementing-a-contract-system/832
To quote the end of my last message there,
> The tl;dr of all that is that `(guix records)` seems to ultimately call for "indy-dependent" contracts[1].
>
> On the one hand, the distinction between "indy-dependent" `->i`[2] and "lax-dependent" `->d`[3] is exactly the sort of hard-learned lesson that I hope the Guix community can draw from Racket's decades of experience.
>
> On the other hand, I'm increasingly intrigued by the idea of starting with forms along the lines of `invariant-assertion`[4] and `struct-guard/c`[5] and truly sticking to flat contracts to start with, leaving all the higher-order complexity for another day.
I'm thinking that a reasonable place to start might be to implement a
`contract->sanitizer` form that would allow using contracts to create
sanitizers, ideally with no changes to `(guix records)`.
In addition to the questions about contract system design, I realized I
have a few questions about Guix/Guile that would be relevant when
starting a prototype.
What is the preferred mechanism for exceptions? I know about:
* (rnrs exceptions)
* (ice-9 exceptions)
* (srfi srfi-34)
* (srfi srfi-35)
and IIRC I've seen more than one of them used in the Guix codebase.
Likewise, what record system should I use? I think the answer should
*not* be (guix records): instead, I think (guix records) should
eventually use (guix contracts). But should I use:
* (rnrs records syntactic)
* (rnrs records procedural)
* (srfi srfi-9)
* (oop goops)
Of those, I'm most familiar with R6RS records. I know (guix records) is
implemented on top of (srfi srfi-9), though I vaguely remember some
discussion about potentially changing that.
Also, I don't know much about how the "abi" aspect of (guix records)
works and what types of changes there would trigger rebuilds. (Though,
again, I hope no changes would be needed for the proof-of-concept phase.)
Finally, when I looked again at the example at the top of this thread:
On 2/14/22 17:32, Ricardo Wurmus wrote:
> ice-9/boot-9.scm:1685:16: In procedure raise-exception:
> In procedure struct-vtable: Wrong type argument in position 1 (expecting struct):
> --8<---------------cut here---------------end--------------->8---
>
> As you can probably tell easily by looking at this message, the
> “service” field of the operating system configuration looked something
> like this:
>
> (services (append (list a b c %desktop-services) #;oops))
>
> instead of this
>
> (services (append (list a b c) %desktop-services))
>
> This is because INSTANTIATE-MISSING-SERVICES — and FOLD-SERVICES, and
> many more — assumes that it is only passed a plain list of services. It
> then proceeds to call SERVICE-KIND on what may or may not be a service.
Another problem here seems to be the fault of (srfi srfi-9). For example:
```
$ guile
GNU Guile 3.0.8
Copyright (C) 1995-2021 Free Software Foundation, Inc.
Guile comes with ABSOLUTELY NO WARRANTY; for details type `,show w'.
This program is free software, and you are welcome to redistribute it
under certain conditions; type `,show c' for details.
Enter `,help' for help.
scheme@(guile-user)> ,use (srfi srfi-9)
scheme@(guile-user)> (define-record-type container (make-container
contents) container? (contents container-contents))
scheme@(guile-user)> (container-contents '())
ice-9/boot-9.scm:1685:16: In procedure raise-exception:
In procedure struct-vtable: Wrong type argument in position 1 (expecting
struct): ()
Entering a new prompt. Type `,bt' for a backtrace or `,q' to continue.
scheme@(guile-user) [1]> ,bt
In current input:
3:0 1 (_)
In ice-9/boot-9.scm:
1685:16 0 (raise-exception _ #:continuable? _)
```
It seems like `container-contents` and other field accessors ought to
check their arguments with `container?` (or the applicable predicate)
and not leave error reporting to `struct-vtable`.
Perhaps this could be fixed in the (guix records) layer?
-Philip
[1]: https://www2.ccs.neu.edu/racket/pubs/popl11-dfff.pdf
[2]:
https://docs.racket-lang.org/reference/function-contracts.html#%28form._%28%28lib._racket%2Fcontract%2Fbase..rkt%29._-~3ei%29%29
[3]:
https://docs.racket-lang.org/reference/function-contracts.html#%28form._%28%28lib._racket%2Fcontract%2Fbase..rkt%29._-~3ed%29%29
[4]:
https://docs.racket-lang.org/reference/attaching-contracts-to-values.html#%28form._%28%28lib._racket%2Fcontract%2Fprivate%25in2Fbase..rkt%29._invariant-assertion%29%29
[5]:
https://docs.racket-lang.org/reference/attaching-contracts-to-values.html#%28form._%28%28lib._racket%2Fcontract%2Fbase..rkt%29._struct-guard%2Fc%29%29
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: better error messages through assertions
2022-03-28 20:25 ` Philip McGrath
@ 2022-03-30 9:37 ` Ludovic Courtès
2022-03-30 13:28 ` Andy Wingo
2022-04-01 19:47 ` Philip McGrath
0 siblings, 2 replies; 20+ messages in thread
From: Ludovic Courtès @ 2022-03-30 9:37 UTC (permalink / raw)
To: Philip McGrath; +Cc: Ricardo Wurmus, guix-devel
Hi Philip,
Philip McGrath <philip@philipmcgrath.com> skribis:
> I'm thinking that a reasonable place to start might be to implement a
> `contract->sanitizer` form that would allow using contracts to create
> sanitizers, ideally with no changes to `(guix records)`.
OK. I’d prefer if people who define record types could directly write:
(field getter (contract integer/c))
rather than:
(field getter (sanitizer (contract->sanitizer integer/c)))
But that’s more of a detail.
> What is the preferred mechanism for exceptions?
For Guix code, SRFI-34/35.
> Likewise, what record system should I use?
SRFI-9.
(Perhaps we should put answers to these questions in the “Coding Style”
section of the manual.)
> Also, I don't know much about how the "abi" aspect of (guix records)
> works and what types of changes there would trigger rebuilds. (Though,
> again, I hope no changes would be needed for the proof-of-concept phase.)
I don’t think you need to worry about that.
> Another problem here seems to be the fault of (srfi srfi-9). For example:
[...]
> scheme@(guile-user)> (container-contents '())
> ice-9/boot-9.scm:1685:16: In procedure raise-exception:
> In procedure struct-vtable: Wrong type argument in position 1
> (expecting struct): ()
>
> Entering a new prompt. Type `,bt' for a backtrace or `,q' to continue.
> scheme@(guile-user) [1]> ,bt
> In current input:
> 3:0 1 (_)
> In ice-9/boot-9.scm:
> 1685:16 0 (raise-exception _ #:continuable? _)
> ```
>
> It seems like `container-contents` and other field accessors ought to
> check their arguments with `container?` (or the applicable predicate)
> and not leave error reporting to `struct-vtable`.
SRFI-9 generates the smallest amount of code for the job:
--8<---------------cut here---------------start------------->8---
scheme@(guile-user)> ,use(srfi srfi-9)
scheme@(guile-user)> (define-record-type <foo>
(make-foo x)
foo?
(x foo-x))
scheme@(guile-user)> ,optimize (foo-x '())
$9 = (if (eq? (struct-vtable '()) <foo>)
(struct-ref '() 0)
(throw 'wrong-type-arg
'foo-x
"Wrong type argument: ~S"
(list '())
(list '())))
--8<---------------cut here---------------end--------------->8---
With Guile 3, it might be that adding an extra ‘struct?’ test would have
little effect on performance; we’d need to check.
> Perhaps this could be fixed in the (guix records) layer?
Could be, yes.
Thanks for looking into this!
Ludo’.
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: better error messages through assertions
2022-03-30 9:37 ` Ludovic Courtès
@ 2022-03-30 13:28 ` Andy Wingo
2022-04-01 8:47 ` Ludovic Courtès
2022-04-01 19:28 ` Philip McGrath
2022-04-01 19:47 ` Philip McGrath
1 sibling, 2 replies; 20+ messages in thread
From: Andy Wingo @ 2022-03-30 13:28 UTC (permalink / raw)
To: Ludovic Courtès; +Cc: Ricardo Wurmus, guix-devel
On Wed 30 Mar 2022 11:37, Ludovic Courtès <ludo@gnu.org> writes:
>> scheme@(guile-user)> (container-contents '())
>> ice-9/boot-9.scm:1685:16: In procedure raise-exception:
>> In procedure struct-vtable: Wrong type argument in position 1
> scheme@(guile-user)> ,use(srfi srfi-9)
> scheme@(guile-user)> (define-record-type <foo>
> (make-foo x)
> foo?
> (x foo-x))
> scheme@(guile-user)> ,optimize (foo-x '())
> $9 = (if (eq? (struct-vtable '()) <foo>)
> (struct-ref '() 0)
> (throw 'wrong-type-arg
> 'foo-x
> "Wrong type argument: ~S"
> (list '())
> (list '())))
>
> With Guile 3, it might be that adding an extra ‘struct?’ test would have
> little effect on performance; we’d need to check.
Would have no effect.
Incidentally, you might want to use ,optimize-cps;
scheme@(guile-user)> ,optimize (foo-x '())
$9 = (if (eq? (struct-vtable '()) <foo>)
(struct-ref '() 0)
(throw 'wrong-type-arg
'foo-x
"Wrong type argument: ~S"
(list '())
(list '())))
scheme@(guile-user)> ,optimize-cps (foo-x '())
L0: ; at <unknown>:15:14
v0 := self
L1(...)
L1:
receive()
v1 := const () ; arg at <unknown>:15:21
throw throw/value+data[#(wrong-type-arg "struct-vtable" "Wrong type argument in position 1 (expecting struct): ~S")](v1) ; at <unknown>:15:14
L1(...) means, pass all values to L1. In this case because there are
varargs on the stack from the procedure call. L1 parses them with the
receive(). Anyway, here we see that with respect to the immediate '(),
that all the tests folded. If we instead lift to a procedure:
scheme@(guile-user)> ,optimize-cps (lambda (x) (foo-x x))
L0: ; at <unknown>:16:14
v0 := self
L1(...)
L1:
receive()
v1 := current-module() ; module at <unknown>:16:14
cache-set![0](v1) ; at <unknown>:16:14
v2 := const-fun L7 ; _
return v2 ; at <unknown>:16:14
L7: ; at <unknown>:16:14
v3 := self
L8(...)
L8:
v4 := receive(x) ; x
heap-object?(v4) ? L10() : L38() ; at <unknown>:16:26
L10():
struct?(v4) ? L11() : L38() ; at <unknown>:16:26
L38():
throw throw/value+data[#(wrong-type-arg "struct-vtable" "Wrong type argument in position 1 (expecting struct): ~S")](v4) ; at <unknown>:16:26
L11():
v5 := scm-ref/tag[struct](v4) ; vtable at <unknown>:16:26
v6 := cache-ref[(0 . <foo>)]() ; cached at <unknown>:10:20
heap-object?(v6) ? L19() : L14() ; at <unknown>:10:20
L19():
L20(v6) ; at <unknown>:10:20
L14():
v7 := cache-ref[0]() ; mod at <unknown>:10:20
v8 := const <foo> ; name at <unknown>:10:20
v9 := lookup-bound(v7, v8) ; var at <unknown>:10:20
cache-set![(0 . <foo>)](v9) ; at <unknown>:10:20
L20(v9) ; at <unknown>:10:20
L20(v10): ; box
v11 := scm-ref/immediate[(box . 1)](v10) ; arg at <unknown>:10:20
eq?(v5, v11) ? L22() : L37() ; at <unknown>:16:26
L37():
throw throw/value+data[#(wrong-type-arg foo-x "Wrong type argument: ~S")](v4) ; at <unknown>:16:26
L22():
v12 := word-ref/immediate[(struct . 6)](v5) ; rfields at <unknown>:16:26
v13 := v12 ; nfields at <unknown>:16:26
imm-u64-<[0](v13) ? L25() : L35() ; at <unknown>:16:26
L35():
v21 := const 0 ; _ at <unknown>:16:26
throw throw/value+data[#(out-of-range "struct-ref/immediate" "Argument 2 out of range: ~S")](v21) ; at <unknown>:16:26
L25():
v14 := pointer-ref/immediate[(struct . 7)](v5) ; ptr at <unknown>:16:26
v15 := load-u64[0]() ; word at <unknown>:16:26
v16 := u32-ref[bitmask](v5, v14, v15) ; bits at <unknown>:16:26
v17 := load-u64[1]() ; mask at <unknown>:16:26
v18 := ulogand(v17, v16) ; res at <unknown>:16:26
u64-imm-=[0](v18) ? L31() : L33() ; at <unknown>:16:26
L33():
v20 := const 0 ; _ at <unknown>:16:26
throw throw/value+data[#(wrong-type-arg "struct-ref/immediate" "Wrong type argument in position 2 (expecting boxed field): ~S")](v20) ; at <unknown>:16:26
L31():
v19 := scm-ref/immediate[(struct . 1)](v4) ; val at <unknown>:16:26
return v19 ; at <unknown>:16:26
Here we see the first procedure which is the thunk that wraps the
expression. Then in the beginning of the procedure at L7 you can see
there is a check for struct?, which has to be dominated by a true
heap-object? check. Duplicate checks are elided. So if SRFI-9 added a
`struct?` check it wouldn't be more code; rather it would be less,
actually, because instead of branching to L38, you'd branch to L37.
Too bad about all that other crap about checking whether the index is in
range and the field is boxed or not, though :-/ Probably there is a
better design...
Andy
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: better error messages through assertions
2022-03-30 13:28 ` Andy Wingo
@ 2022-04-01 8:47 ` Ludovic Courtès
2022-04-01 19:28 ` Philip McGrath
1 sibling, 0 replies; 20+ messages in thread
From: Ludovic Courtès @ 2022-04-01 8:47 UTC (permalink / raw)
To: Andy Wingo; +Cc: Ricardo Wurmus, guix-devel
Hi,
Andy Wingo <wingo@igalia.com> skribis:
> Here we see the first procedure which is the thunk that wraps the
> expression. Then in the beginning of the procedure at L7 you can see
> there is a check for struct?, which has to be dominated by a true
> heap-object? check. Duplicate checks are elided. So if SRFI-9 added a
> `struct?` check it wouldn't be more code; rather it would be less,
> actually, because instead of branching to L38, you'd branch to L37.
Oh very nice, thanks for the explanation! That ‘,optimize-cps’ command
proves useful.
So we can have better error reports with no performance penalty,
Philip. Way to go!
Ludo’.
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: better error messages through assertions
2022-03-30 13:28 ` Andy Wingo
2022-04-01 8:47 ` Ludovic Courtès
@ 2022-04-01 19:28 ` Philip McGrath
2022-04-05 12:04 ` Ludovic Courtès
1 sibling, 1 reply; 20+ messages in thread
From: Philip McGrath @ 2022-04-01 19:28 UTC (permalink / raw)
To: Andy Wingo, Ludovic Courtès; +Cc: Ricardo Wurmus, guix-devel
Hi,
On 3/30/22 09:28, Andy Wingo wrote:
>
> Too bad about all that other crap about checking whether the index is in
> range and the field is boxed or not, though :-/ Probably there is a
> better design...
>
> Andy
For the index-out-of-range part, when I saw `record-accessor`, I thought
of it as similar to Racket's `make-struct-field-accessor`[1], which can
check the index just once, when the accessor is created, rather than
each time the accessor is used. That's (part of) what Racket's `struct`
form expands to.
Would it be reasonable to use `record-accessor` in the implementation of
SRFI 9?
-Philip
[1]:
https://docs.racket-lang.org/reference/creatingmorestructs.html#%28def._%28%28quote._~23~25kernel%29._make-struct-field-accessor%29%29
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: better error messages through assertions
2022-03-30 9:37 ` Ludovic Courtès
2022-03-30 13:28 ` Andy Wingo
@ 2022-04-01 19:47 ` Philip McGrath
1 sibling, 0 replies; 20+ messages in thread
From: Philip McGrath @ 2022-04-01 19:47 UTC (permalink / raw)
To: Ludovic Courtès; +Cc: Ricardo Wurmus, guix-devel
Hi,
On 3/30/22 05:37, Ludovic Courtès wrote:
>
>> What is the preferred mechanism for exceptions?
>
> For Guix code, SRFI-34/35.
>
>> Likewise, what record system should I use?
>
> SRFI-9.
>
> (Perhaps we should put answers to these questions in the “Coding Style”
> section of the manual.)
>
As I've looked more closely, I think I'd be porting a fair amount of
code that looks like this:
```
(define-struct (blame-no-swap blame) ()
#:property prop:equal+hash
(list blame=? blame-hash blame-secondary-hash))
```
That code creates a new opaque struct type `blame-no-swap` with parent
type `blame` and customizes the behavior of `equal?` and `equal?`-based
hashing (but the customization only applies when the two
potentially-equal values are both immediate instances of
`blame-no-swap`, not a supertype or subtype).
It looks like SRFI 9 doesn't have a notion of opaque or parent types, so
it seems like it would be easier to use R6RS records as the basis for
porting.
(I think it should still work to use SRFI 9 records for the interface.
But I know the representation of blame, among other details, has gotten
a lot of attention over the years, so it seems like it would be good to
keep the port as close as reasonably possible to the Racket implementation.)
I'm less sure about `equal?` and hashing. I see in [1] that `equal?` can
be controlled via GOOPS, and I have the impression that there's some
kind of bridging between basic types and GOOPS, but I'm not clear on the
details. I don't see anything explicitly about customizing `hash`[2],
but maybe that would work the same way?
-Philip
[1]:
https://www.gnu.org/software/guile/manual/html_node/GOOPS-Object-Miscellany.html
[2]:
https://www.gnu.org/software/guile/manual/html_node/Hash-Table-Reference.html#index-hash
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: better error messages through assertions
2022-04-01 19:28 ` Philip McGrath
@ 2022-04-05 12:04 ` Ludovic Courtès
0 siblings, 0 replies; 20+ messages in thread
From: Ludovic Courtès @ 2022-04-05 12:04 UTC (permalink / raw)
To: Philip McGrath; +Cc: Ricardo Wurmus, guix-devel
Hi,
Philip McGrath <philip@philipmcgrath.com> skribis:
> On 3/30/22 09:28, Andy Wingo wrote:
>> Too bad about all that other crap about checking whether the index
>> is in
>> range and the field is boxed or not, though :-/ Probably there is a
>> better design...
>> Andy
>
> For the index-out-of-range part, when I saw `record-accessor`, I
> thought of it as similar to Racket's `make-struct-field-accessor`[1],
> which can check the index just once, when the accessor is created,
> rather than each time the accessor is used. That's (part of) what
> Racket's `struct` form expands to.
>
> Would it be reasonable to use `record-accessor` in the implementation
> of SRFI 9?
Yes, or in (guix records).
Medium-term, I thought we could rebase (guix records) on Guile records
with an eye on adding support for inheritance. So perhaps we could do
both.
Ludo’.
^ permalink raw reply [flat|nested] 20+ messages in thread
end of thread, other threads:[~2022-04-05 12:04 UTC | newest]
Thread overview: 20+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2022-02-14 22:32 better error messages through assertions Ricardo Wurmus
2022-02-15 8:48 ` Maxime Devos
2022-02-15 21:45 ` Philip McGrath
2022-02-15 22:15 ` Ricardo Wurmus
2022-02-28 12:59 ` Ludovic Courtès
2022-02-28 16:18 ` Philip McGrath
2022-03-07 10:13 ` Ludovic Courtès
2022-03-28 20:25 ` Philip McGrath
2022-03-30 9:37 ` Ludovic Courtès
2022-03-30 13:28 ` Andy Wingo
2022-04-01 8:47 ` Ludovic Courtès
2022-04-01 19:28 ` Philip McGrath
2022-04-05 12:04 ` Ludovic Courtès
2022-04-01 19:47 ` Philip McGrath
2022-02-22 4:31 ` Arun Isaac
2022-02-25 18:55 ` Maxim Cournoyer
2022-02-26 13:33 ` Ricardo Wurmus
2022-02-26 13:51 ` Maxim Cournoyer
2022-02-28 13:02 ` Ludovic Courtès
2022-02-28 16:00 ` Maxim Cournoyer
Code repositories for project(s) associated with this public inbox
https://git.savannah.gnu.org/cgit/guix.git
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).