Splitting foreign-error:code Lassi Kortela (27 Jul 2020 06:46 UTC)
Re: Splitting foreign-error:code Lassi Kortela (27 Jul 2020 08:26 UTC)
Re: Splitting foreign-error:code Lassi Kortela (27 Jul 2020 08:51 UTC)
Re: Splitting foreign-error:code John Cowan (28 Jul 2020 19:25 UTC)
Re: Splitting foreign-error:code for SRFI 198, Foreign Errors Lassi Kortela (27 Jul 2020 23:44 UTC)
Flat vs nested alist Lassi Kortela (27 Jul 2020 23:50 UTC)
Re: Flat vs nested alist Lassi Kortela (27 Jul 2020 23:53 UTC)
Re: Flat vs nested alist John Cowan (28 Jul 2020 03:06 UTC)
Pre-SRFI for property list utilities Lassi Kortela (28 Jul 2020 07:35 UTC)
Re: Pre-SRFI for property list utilities hga@xxxxxx (28 Jul 2020 11:00 UTC)
Plist utilities and SRFI 198 Lassi Kortela (28 Jul 2020 11:08 UTC)
Re: Plist utilities and SRFI 198 John Cowan (28 Jul 2020 18:12 UTC)
plist pre-SRFI hga@xxxxxx (12 Aug 2020 15:14 UTC)
Re: plist pre-SRFI John Cowan (12 Aug 2020 15:21 UTC)
Re: plist pre-SRFI John Cowan (12 Aug 2020 15:53 UTC)
Re: plist pre-SRFI hga@xxxxxx (12 Aug 2020 15:58 UTC)
Re: plist pre-SRFI John Cowan (12 Aug 2020 16:59 UTC)
Re: plist pre-SRFI hga@xxxxxx (12 Aug 2020 17:34 UTC)
Re: plist pre-SRFI John Cowan (12 Aug 2020 19:37 UTC)
Use of SRFI 198 in SRFI 170 hga@xxxxxx (12 Aug 2020 20:04 UTC)

Re: Splitting foreign-error:code for SRFI 198, Foreign Errors Lassi Kortela 27 Jul 2020 23:44 UTC

> I have a *very* strong preference for keeping the number of top level
> exposed interfaces small.

Where does your preference come from - is it a particular school of
thought in design, or just from personal experience that it tends to
works better?

Over time I've developed almost the opposite preference. IMHO the key
metric is not the number of functions but the number of
interdependencies. If the functions do not depend on each other, none of
them complicates the others. Rich Hickey discusses this in one of his talks.

On a related note, when you have a data structure, adding more functions
to work on that data structure makes it stronger. Perlis: "It is better
to have 100 functions operate on one data structure than 10 functions on
10 data structures." <http://www.cs.yale.edu/homes/perlis-alan/quotes.html>

> In this case, foreign-error:code allows for
> any error-set to have none, represented by #f, e.g. libsodium and
> plenty of other libraries just have functions that signal errors with
> no further organization.

This is fine.

> errno really only has a number, I've added the C define such as EINVAL
> which all the documentation uses for the convenience of the user.  It's
> trivial to provide, and it can be very obnoxious for a user to grovel
> through a Linux /usr/include to map the number to the name he needs to
> interpret it in the documentation.

Agreed.

Even more importantly, not all errno numbers are the same across
operating systems. The C identifiers like EINVAL are the stable
interface enshrined by POSIX. The numeric values are per OS.

> Whereas databases such a PostgreSQL have an elaborate organization for
> their errors.  Having foreign-error:code contain an alist allows for
> both the simple display of everything, without having to look at the
> whole error object, and programmatic access by key for each value.

Good point. This is worth thinking more about.

> The proposed repository for error-set, as well as library specific foreign
> interfaces, should tell you the keys to expect.

That's a good idea.

Also, similar keys across different error sets should strive to have the
same key symbols to ease people's cognitive load.

> But they can be
> discovered simply by triggering an error in a REPL and noting all the
> keys in foreign-error:code.

True - at least if all keys are filled in for all errors of that type.

> Another advantage is that having foreign-error:code fan out into an
> alist doesn't require us *today* to anticipate every possible way
> an error can be classified.  That I've worked with database like
> Oracle and PostgreSQL is why I want to layer all this underneath
> the single foreign-error:code getter.

I would try to avoid nested alists, and have all keys in one flat
top-level alist. No hard principle for this either, just that nested
things are more complicated than flat things.

So the example from the current draft:

(make-foreign-error
  '((error-set . errno)
    (code . ((number . 2)
             (symbol . errno/ENOENT)))
    (scheme-procedure . open-file)
    (foreign-interface . open)
    (message . "open-file called open: ...")
    (data . ((arguments . ("not-a-valid-filename" 0 428))
             (heritage . "SRFI 170")))))

I would prefer to write as something like this:

(make-foreign-error
  '((error-set . errno)
    (code . 2)
    (symbol . ENOENT)
    (scheme-procedure . open-file)
    (foreign-interface . open)
    (message . "open-file called open: ...")
    (arguments . ("not-a-valid-filename" 0 428))
    (heritage . "SRFI 170")))

Accessor functions will be simpler: (foreign-error:data ferr 'code)
instead of (foreign-error:data ferr 'code 'number) or something like it.

If the value for that alist key is a lambda, extra arguments to
`foreign-error:data` can be passed to that lambda. We don't have to use
them to do nested alist lookups if we have no nested alists.

If we specify keys in the error-set registry, that will also be simpler
if there is only one level of keys.

> Here, in addition to foreign-error:message, which should always be
> populated in systems with enough memory to store them, or procure them
> in some localization schemes, you add 3 new first class, top level
> interfaces to the API in addition to code:
>
> (foreign-error:symbol ferr) -> 'invalid_password
> (foreign-error:class-code ferr) -> 28
> (foreign-error:class-title ferr) -> "Invalid Authorization Specification"
>
> What's wrong with keeping them in an alist, keyed on 'symbol,
> 'class-code, and 'class-title?

Error classes are probably rare enough that storing them in the custom
alist would indeed be better.

> This also bring up the question of what is your limiting principle for
> the first class, top level of the API?

None - I have no principles :)

In all seriousness, there probably can't be any objective principle.

Earlier I was thinking about enumerating error sets, and that it would
be useful to enumerate based on either mnemonic error identifiers
(EINVAL and the like) or error codes (28 and the like). So at least
those would be good to have as top-level fields. But there is really no
fundamental requirement that that be the case - the iterator can just as
well get those keys from the alist.

Top-level procedures are shorter and more natural to type:

(foreign-error:code ferr) vs
(foreign-error:data ferr 'code)

But it's not a huge difference.

Top-level calls are also easier to static-check for typos by compilers.