Minimal foreign error API Lassi Kortela (28 Jul 2020 10:28 UTC)
Re: Minimal foreign error API hga@xxxxxx (28 Jul 2020 11:31 UTC)
Re: Minimal foreign error API Lassi Kortela (28 Jul 2020 12:05 UTC)
Re: Minimal foreign error API Lassi Kortela (28 Jul 2020 12:26 UTC)
Re: Minimal foreign error API Lassi Kortela (28 Jul 2020 12:30 UTC)
Re: Minimal foreign error API Lassi Kortela (28 Jul 2020 13:02 UTC)
Re: Minimal foreign error API hga@xxxxxx (28 Jul 2020 17:56 UTC)
Abstract or concrete data type for foreign error object? Lassi Kortela (31 Jul 2020 16:18 UTC)
Re: Abstract or concrete data type for foreign error object? Lassi Kortela (01 Aug 2020 20:10 UTC)
Re: Minimal foreign error API John Cowan (28 Jul 2020 14:39 UTC)
Re: Minimal foreign error API hga@xxxxxx (28 Jul 2020 15:59 UTC)

Re: Minimal foreign error API Lassi Kortela 28 Jul 2020 12:05 UTC

>> (foreign-error? object) → boolean
>
> If they're nothing more than a property list, and I see no reason to
> make them more complicated, the above would be a bit weak, the best it
> can do is to make sure it's a plist, or at least a list where you can
> use standard procedures to confirm there's an 'error-set key, and its
> value is a symbol.

Ah, I meant merely to specify that the argument list of
`make-foreign-error` and `raise-foreign-error` is structured like a
plist, i.e. alternating keys and values. This looks almost as if they
are keyword arguments.

The `foreign-error` data type would be an abstract data type, so whether
it internally keeps the plist or turns it into an alist, hash-table or
record is an implementation detail. Sorry about the confusion.

In principle, wrapping a define-record-type around the plist would not
add any value besides the fact that that's the only RnRS-standard way to
make user-defined opaque data types in Scheme. So maybe it would make
sense if the bare plist by itself can serve as the error object. Does
RnRS allow us to `raise` a list as an exception, or does that cause
problems of some kind?

> Then again, those *are* the only requirements for a valid error object.

Very good point.

If a bare plist is used, perhaps `make-foreign-error` should at least
prepend some kind of magic value into it so that `foreign-error?` can
check that that value is `eq?` to the right magic.

>> (raise-foreign-error plist...) → object
>
> The above doesn't return per se....  I currently have it as undefined.

You're right.

>> (foreign-error-ref ferr property args...) → object
>>
>> property is a symbol. If the value of the property is a procedure,
>> that procedure is applied to args
>
> How is the user of a Scheme library supposed to know which args he
> needs to feed random procedures in the plist??  Message, see below,
> can be safely special cased, and maybe some others, but the rest??

The point is that the user of an error object shouldn't have to know
which properties in that object have lambdas and which don't. The
library writer who makes an error object is responsible for writing any
lambdas in such a way that they can be given zero arguments and return a
value that is a reasonable value for that property.

One of my formative programming languages was Object Pascal (Delphi).
The language syntax was such that calling a zero-argument procedure
looked identical to reading a record field:

x := someObject.SomeProcedure;
x := someObject.SomeField;

This was great. It meant that you could start out having simple public
variables in your objects, and once you needed to add some more complex
behavior to the getter, you could swap out the variable for a property
(for which the language's object system had native support) that
transparently called a getter method of your choice to return the value
of that property. Callers would not have to be modified since zero-arg
method call / property-getting had identical syntax to variable reads.

That's essentially what we would also be doing in this SRFI by
transparently calling lambdas in `foreign-error-ref`.

>> and the resulting values are returned. If the value of the property
>> is not a procedure, the value is returned as is and it is an error
>> to supply any args.

> We could avoid depending on a property list SRFI by dropping the
> args argument altogether.  If not, shouldn't the signature be:
>
> (foreign-error-ref ferr property . args) → object

`args...` and ` . args` mean the same thing, so you signature is
equivalent to mine unless I'm misreading them. That is,
`foreign-error-ref` always gets a ferr argument and a property argument
(which is a symbol), followed by zero or more arguments passed to the
lambda.

We could drop support for the `args` argument(s), but that would make
things like the localization support much more tricky to implement.

If the property value is not a lambda, perhaps we should silently ignore
any `args` given by the user. This would make something like
`(foreign-error-ref ferr 'message #f)` work even when the message inside
the error object is a simple string instead of a lambda.

>> (foreign-error->string ferr) → string
>>
>> Return the error message. Perhaps we can have property for a custom
>> to-string procedure, and if such a procedure is given in the plist,
>> that is used instead of the message.
>
> Given that it is, and always is going to be a message, wouldn't it
> be a lot more clear to make the name foreign-error->message?  Does
> it make sense to use "->", when this form is always? going to return
> a default string??

`foreign-error->string` is nice for uniformity with other data types,
e.g. `number->string`.

I'm not sure whether the best thing to return would be the error message
with nothing added. That's just the most obvious thing. The idea would
be to return something suitable for `display` and the like. Suggestions
are welcome.

If the return value is always going to be just the message, a simple
`(foreign-error-ref ferr 'message)` would be enough IMHO.

> However, I'll still repeat my point that we can't make alists/plists
> non-reflective, except for opaque values like procedures, and that
> however they're constructed, they should have a fixed order, error-set
> first, the rest....  Instead of my previous idea of complicating the
> whole system by having the symbol that's the value of the error-set
> have as its value a list with the order to enforce, any reason to not
> have make-foreign-error (always called by the raise- procedures, which
> we should explicitly state) put the plist in alphabetical order by key?

Sure, it's fine for `make-foreign-error` sort the plist (either
alphabetically, or by some other criteria - e.g. what is the nicest
order for display purposes). Since foreign-error is an abstract data
type, it can also do any other processing that is useful to the
implementation, e.g. convert the plist to an alist or hash-table.

> Even if there's issues with sort orders not being entirely consistent,
> a particular Scheme implementation instantiation should always do it
> the same, and that's good enough for my purposes.

Makes sense. Per the plist definition you found in the Common Lisp
HyperSpec, in case of duplicate plist keys the first key wins.
`make-foreign-error` should interpret its argument list that way as well.

>> Localizations can be done by giving a procedure instead of a string for
>> the 'message property. Then (foreign-error-ref e 'message 'en 'gb) would
>> call the message procedure with the arguments ('en 'gb) and that
>> procedure should return a string with the right message.
>
> We could define by convention arguments for 'message, but what to do
> if it's not a procedure?  Well, we could always just bundle a single
> default string into a procedure....  What about being able to discover
> what locales it knows or can find out it supports?
Here's an example of different kinds of message properties:

(import (scheme base) (scheme case-lambda) (scheme write))

(define (plist-get/default plist key default)
   (let loop ((plist plist))
     (cond ((not (and (pair? plist) (pair? (cdr plist))))
            default)
           ((eqv? key (car plist))
            (cadr plist))
           (else (loop (cddr plist))))))

(define-record-type foreign-error
   (%make-foreign-error plist)
   foreign-error?
   (plist %foreign-error-plist))

(define (make-foreign-error . plist)
   (%make-foreign-error plist))

(define (foreign-error-ref ferr property . args)
   (let* ((plist (%foreign-error-plist ferr))
          (value (plist-get/default plist property #f)))
     (if (procedure? value) (apply value args) value)))

;;

(define (disp . xs) (for-each display xs) (newline))

(let ((e (make-foreign-error 'message "Hello world")))
   (disp (foreign-error-ref e 'message)))

(let ((e (make-foreign-error 'message (lambda () "Hello world"))))
   (disp (foreign-error-ref e 'message)))

(let ((e (make-foreign-error
           'message (case-lambda
                      (() "Hello world")
                      ((language)
                       (case language
                         ((en #f) "Hello world")
                         ((de) "Hallo Welt")
                         ((fi) "Hei maailma")
                         (else #f)))))))
   (disp (foreign-error-ref e 'message))
   (disp (foreign-error-ref e 'message #f))
   (disp (foreign-error-ref e 'message 'en))
   (disp (foreign-error-ref e 'message 'de))
   (disp (foreign-error-ref e 'message 'fi))
   (disp (foreign-error-ref e 'message 'no-such-language)))

;; Output:

Hello world
Hello world
Hello world
Hello world
Hello world
Hallo Welt
Hei maailma
#f