SRFI 198: clear enough for next draft? Lassi Kortela (02 Aug 2020 11:35 UTC)
Re: SRFI 198: clear enough for next draft? hga@xxxxxx (02 Aug 2020 12:22 UTC)
Re: SRFI 198: clear enough for next draft? Lassi Kortela (02 Aug 2020 13:21 UTC)
Re: SRFI 198: clear enough for next draft? hga@xxxxxx (02 Aug 2020 15:04 UTC)
Re: SRFI 198: clear enough for next draft? John Cowan (06 Aug 2020 03:49 UTC)
Re: SRFI 198: clear enough for next draft? hga@xxxxxx (12 Aug 2020 16:56 UTC)
Re: SRFI 198: clear enough for next draft? John Cowan (06 Aug 2020 03:22 UTC)
Re: SRFI 198: clear enough for next draft? hga@xxxxxx (12 Aug 2020 16:33 UTC)
Re: SRFI 198: clear enough for next draft? John Cowan (05 Aug 2020 06:13 UTC)
Re: SRFI 198: clear enough for next draft? hga@xxxxxx (05 Aug 2020 13:11 UTC)
Re: SRFI 198: clear enough for next draft? Lassi Kortela (05 Aug 2020 15:23 UTC)
Re: SRFI 198: clear enough for next draft? hga@xxxxxx (05 Aug 2020 15:59 UTC)
Re: SRFI 198: clear enough for next draft? Lassi Kortela (05 Aug 2020 16:17 UTC)

Re: SRFI 198: clear enough for next draft? hga@xxxxxx 02 Aug 2020 15:04 UTC

> From: Lassi Kortela <xxxxxx@lassi.io>
> Date: Sunday, August 02, 2020 8:21 AM
>
> [ Opaque vs. abstract data types (ADTs), thanks for the lesson. ]
>
>>> (foreign-error? object) => boolean
>>>
>>> (foreign-error-ref ferr symbol) => object
>>
>> Leaves out the object being a lambda, with convenience additional
>> arguments being the lambda's arguments to hand to it.  Not required,
>> but why make more work for the consumer of an error?
>
> True, I'd also vote to support lambdas as discussed.

So using my preferred syntax, the signature would be:

(foreign-error-ref ferr symbol . args) => object

> [ We must "use a standard localization approach approved by an
>   expert like John." ]

Which needs to be defined, or merely referenced to in SRFI 198. Going
back to a point I made, allowing lambdas and arbitrary arguments as
values should allow us to punt the details for now.  Now we're gating
SRFI 170 on SRFI 198, and including details of SRFI 198's API in SRFI
170.  Which are now obsolete, except for required key/value pairs.

I believe we could safely decouple SRFI 198 by leaving in the
reference to SRFI 198, and just listing the required key/value pairs.

That part of the API is going to stay, the details of how you make and
raise a SRFI 170 error and decipher it can be omitted.  Or, this being
late summer and the start of the academic year fast approaching, be in
less of a hurry to finalize SRFI 170.  Is anything depending on things
in it that might change?  (I'm already using it for my backup program.)

> [ One way to make human registry browsing easier. ]
>
> [ ... ]
>
> Somebody recommended in one of the other threads that
> raise-continuable-foreign-error is too niche and should be left out. I
> agree that this kind of discontinuity can be a bit odd to users (why
> have one and not the other) but I'd advocate solving it by omitting
> `raise-foreign-error` as well :)

One question that could provide a definitive answer: are or might
there be systems that will use SRFI 198 that don't provide continuable
raises?  Or we could future proof this by adding optional args at the
end of the signature, as you note below it's library etc. authors that
need to get this right:

> Foreign errors are mostly going to be raised by fairly low-level FFI
> bindings and network protocol implementations. That code tends to be
> grotty enough anyway that typing (raise (make-foreign-error ...))
> instead of (raise-foreign-error ...) will not make it harder to
> understand than it already is.

Perhaps, and for example in my SRFI 170 Chibi Scheme sample
implementation this detail is hidden behind two general error raising
functions (pure errnos, and sanity checks).  But see the end where I
think you agree that raise-foreign-error should stay to encourage this
paradigm as much as possible.

Note also make-foreign-error only exists because I can't justify
requiring raising an error as the only way to report them. Allowing
for returning them when that makes sense, such as a server returning
a foreign error object to a client instead of locally blowing up
without a response to the client.

> [ You remind me of the correct language I wasn't remembering, "it is
>   an error to pass non-symbols as plist keys". ]
>
> [ Keywords arguments, where I'll defer to others. ]

>>> - Specify what to do in case of duplicate plist keys (after normalizing
>>> them all to symbols). First one wins?
>>
>> That's the general rule?
>
> I'm not sure if there is any general rule.
>
> `assoc` picks the first match from an alist. But the list was
> constructed in reverse order from some source, then the last match from
> the source list will win. The general expectation is that order of alist
> keys doesn't matter, and there are no duplicates, so depending on things
> like that is surprising.

Still, we need *a* rule.  First, last, and/or cons up something to go
under a key named something like 'malformed-arguments, except we need
a more gentle word than "malformed".

>> But why not stash duplicates somewhere in the error object?  In
>> general we want to preserve information, especially when calls to
>> it are malformed, the user should still be able to make some sense
>> out of a mess (and of the least likely to be caught in testing).
>> Thus a call to make-foreign-error without an 'error-set key will
>> still return an error object, with all the data handed to it in it.
>
> Missing keys is a different problem from duplicate keys.
>
> From an algorithm standpoint, it's easy to specify that all keys are
> optional, and missing ones return #f. No matter which data structure one
> uses, there's one obvious way to implement that.

Thus part of the plan from the beginning.

> It's not obvious how to handle duplicates.
>
> If we required a plist for the internals, then from:
>
> (make-foreign-error
>   'set 'errno
>   'number 12
>   'oops
>   'message "Cannot allocate memory")
>
> we could salvage the 'set and 'errno properties using
> `foreign-error-ref`. But we could not salvage 'message since 'oops
> throws the key-value pairing out of whack. And (foreign-error-ref e
> 'oops) would return 'message.

We salvage it by packaging up everything starting with where the plist
is broken, 'oops in this case, and putting it under a
'malformed-arguments key.  Most certainly do no throw it away, or:

> Additionally, if `make-foreign-error` refuses unbalanced plists, then
> more errors in the usage of `make-foreign-error` are caught early
> instead of propagating into the caller of the library who then has an
> improper error object on his hands.

Problem is, make-foreign-error only gets called when the error is
signaled, not at load/compile time, unless we want to turn it into a
fancy macro.  Which might be OK, but barring that, I maintain a
strong position to throw no information away from malformed calls to
make-error-object.  Even if the argument handed to it is a number
instead of a list.

> Dynamic languages (and dynamic systems, more generally) are filled with
> problems like this....

Indeed.  I'm calling this one case out because error code is
relatively the least exercised code, and because of the disconnect
between the author who calls make-foreign-error in his library or
whatever, and the consumer of the error.

> [...]
>
> This is a matter of taste, but taste is also influenced by experience.
> In my experience, an interface can't be simple enough. GC is the best
> abstraction of all time, and its interface is empty! There is nothing to
> call, it simply works automatically. The closer we can get to the empty
> interface, the better. In that respect your idea of dropping the
> foreign-error:message and other special fields is already better than mine.

The minimum error is '(error-set error).  Allowing only one or two
key/value pairs with numbers for values will be useful in extreme
memory limited embedded applications.  Etc.

> [...]
>
>> I need to make that explicit in the SRFI specification text, what all
>> malformed calls to make-foreign-error will do.  Probably two varieties,
>> if a value for the key 'error-set can be extracted, and if not.
>
> It depends on whether we allow malformed plists to make-foreign-error or
> not. I agree that we should specify something in any case.

See above.

>>> Do we need a `raise-foreign-error` in the SRFI? (raise
>>> (make-foreign-error ...)) is not substantially more difficult.
>>
>> We ***really*** want to encourage this paradigm.  Quoting text from
>> SRFI 170, the end of SRFI 198's Rationale is:
>>
>>> Sometimes it will make sense to simply return an error object, but an
>>> often preferred method is to raise an exception, as in SRFI 170, in
>>> which procedures never return error codes nor use an analogue of the
>>> POSIX errno variable to indicate system-call-level errors.
>>>
>>> Thus procedures can return useful values, and the programmer can
>>> assume that if a foreign interface procedure returns, it succeeded.
>>> This greatly simplifies the flow of the code from the programmer's
>>> point of view.
>>
>> And we might as well hide the details of whichever system's raising
>> from the user of SRFI 198.
>
> Great, we are in agreement :) John, do you concur?

So we include raise-foreign-error in the API?

- Harold