Re: New draft (#8) of and new "last call" for SRFI 189: Maybe and Either: optional container types Marc Nieper-Wißkirchen (09 Jul 2020 09:30 UTC)

Re: New draft (#8) of and new "last call" for SRFI 189: Maybe and Either: optional container types Marc Nieper-Wißkirchen 09 Jul 2020 09:30 UTC

Am Do., 9. Juli 2020 um 06:32 Uhr schrieb John Cowan <xxxxxx@ccil.org>:

> On Wed, Jul 8, 2020 at 10:28 AM Marc Nieper-Wißkirchen <xxxxxx@nieper-wisskirchen.de> wrote:
>
>> We have also solved the issue of trapping unpredictable exceptions by
>> adding a mandatory 'pred?' argument to 'either-guard'.'
>
> I had actually forgotten that, which somewhat reconciles me to it.  But I still want it to be a (first-class) procedure, not a (second-class) macro, something like this: (exception->either exeception-pred thunk).  This is a little more verbose (an extra lambda) in the case you give, but brings more flexibility in use: it can, for example, be mapped over a list of thunks to be invoked to produce a list of Eithers. Here's an untested implementation sketch:

This can also be done when the form is syntax:

(map (lambda (thunk) (either-guard pred? (thunk))) list-of-thunks)

Your alternative would be:

(map (lambda (thunk) (exception->either pred? thunk)) list-of-thunks)

The advantage of the syntactic form 'either-guard' is that it makes
life for the optimizer much simpler. See Dybvig's critic about
'call-with-values' here:
http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.69.5878&rep=rep1&type=pdf.

Moreover, the special form seems to be much more convenient for most
use cases/users. If Scheme were really a simplistic language, not even
'if' would be a special form but would take two thunks for the
consequent and the alternate. Luckily, this is not the case so using
'if' does not need a lot of boilerplate lambdas and the implementation
has much better chances to optimize 'if'. I think the same arguments
apply to 'either-guard'.

Unrelated to this, I still consider the name 'exception->either' a bad
grammatical abuse of the informal language by which we name
identifiers. The name would be good for a procedure taking exception
object (like R6RS's condition objects or Java's exception objects)
taking their payload and wrapping them in an Either. In our case,
however, we are not even remotely just transforming data. We are
changing the control flow.

> (define (exception->either pred thunk)
>   (with-exception-handler
>     (lambda (e)
>       (if (pred e)
>         (left e)
>         (raise-continuable e)))
>     (lambda ()
>       (right (thunk)))))

Don't use 'with-exception-handler'. Use 'guard', which is fool-proof.
For non-continuable exceptions, the exception handler of
'with-exception-handler' mustn't return (or another exception is
raised). So your code never yields '(left e)' to the enclosing
continuation.

The following version should work (apart from the bad name and apart
from that I still think a syntactic form is superior):

(define (exception->either pred thunk)
  (guard (e ((pred e) (left e))) (right (thunk))))

>> PS: The "an error is signaled" error is still in the current draft.
>
>
> I do not think it is worthwhile to devote a dedicated predicate to catching exceptions of this type, and I think it would set a bad example.  However, "an error satisfying error-object?" works for me, and I have changed the phrase both here and in SRFI 186.

That's slightly better but it doesn't solve the fundamental problem,
namely that you want an error being signaled (in the technical sense)
in a situation (programming error), in which R7RS-small does not
signal errors but encourages implementations to report the error,
which could happen through signaling an error but could also happen
through other means. There's no much point in catching a programmer's
error with 'guard' or 'with-exception-handler' as no one expects such
an error being triggered in a correct program.

We really want something roughly like C's assert semantics here, which
is what happens in the rest of the language. Consider:

(cond ((a => x))

It is an error if 'x' does not evaluate to a procedure, but R7RS-small
does not force the implementation to signal an error, which can
necessarily be caught through 'with-exception-handler'. 'maybe-if' is
not much different to 'cond': It is a special form taking an argument,
which must evaluate to an object of a specific type (procedure or
Maybe).

Another big problem with the current specification of 'maybe-if' is
that it hinders optimizations:

(define (foo bla)
  (let ((x (maybe-if bla a b)))
    ...))

If 'maybe-if' were specified like 'cond' (in that no error has to be
signaled in the technical sense), in an aggressively optimizing mode
an implementation can assume that 'bla' is a Maybe. This allows type
inference in the body of 'foo' but also in contexts, in which 'foo' is
being called. This type inference is important for fast code (allowing
to represent the relevant objects as "unbox" values in machine code,
etc.).

Now with your specification, the implementation's optimizer cannot
assume that 'bla' is a Maybe because there will be an observable side
effect when 'bla' is not a Maybe.

All-in-all, there seem to be zero benefits for forcing the
implementation to signal an error in the technical sense, but a number
of drawbacks.

Marc