Email list hosting service & mailing list manager

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

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

Am Fr., 10. Juli 2020 um 16:05 Uhr schrieb John Cowan <xxxxxx@ccil.org>:

>> 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)
>
>
> "Condition object" is the standard terminology in all Lisp environments; your argument would be strong if that were not the case.  The term "exception" I use as a shorthand for "exceptional situation", where "situation" is an informally defined
term.

It is still grammatical abuse of "->" by the usual conventions.
Semantically, even if "exception" is a shorthand for "exceptional
situation", it is quite a stretch to make sense of it. If we had no
alternative, I could possibly agree with you, but we have a good
alternative.

>> 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.
>
>
> You're right, of course.  It needs a one-shot call/cc to break out of the handler.
>
>>
>> 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))))
>
>
> I think you need an `else` there, but otherwise this looks good.

I don't think you need an 'else' clause. This is the implementation
(the much more comfortable sanely named macro version :)) I posted
earlier and passes all tests (within Wolfgang's test framework):

(define-syntax either-guard
  (syntax-rules ()
    ((_ cond? . body)
     (let ((%cond? cond?))
       (guard (exc
              ((%cond? exc) (left exc)))
         (call-with-values (lambda () . body) right))))))

The following tests pass:

  (check (either= eqv? (either-guard number? (raise 42))
                  (left 42))
     => #t)
  (check (either= eqv? (either-guard number? 42)
                  (right 42))
     => #t)
  (check (guard (exc ((string? exc) exc))
           (either-guard number? (raise "42")))
     => "42")
  (check (either= eqv? (with-exception-handler
                           (lambda (x)
                             (+ 1 x))
                        (lambda ()
                           (either-guard string? (+ 2 (raise-continuable 39)))))
                 (right 42))
     => #t))

>> 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.
>
>
> As I've pointed out, there is very often a need to continue operation even of a buggy program.

This is nothing R7RS ever guarantees to work. As soon you have a
programming error somewhere, chances are that you call procedures with
objects of the wrong times, and then you are in the undefined
territory. For R6RS, the situation is different.

>> We really want something roughly like C's assert semantics here, which
>> is what happens in the rest of the language.
>
>
> Same point: assert() conditionally calls abort(), and although it is possible to recover from abort() in Posix C, this is not true of ISO C.

You can always link in your own version of abort! :)

But, honestly, if want to continue after a programming error due to a
type mismatch, you really have to separate your program into two
processes so that at least the supervising process can still be
assumed to be bug-free.

>> '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).
>
>
> That sounds compelling, but I don't think it is.

Then there must be a reason for. Which?

>> 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.
>
>
> That's true too.  But under the "is an error" semantics, the simplest implementation of (maybe-if m j n) is (if (just? m) j n), just as is given in the SRFI.  In this case, since it is not an error to apply `just?` to a random object, n will be evaluated and its value returned.  I submit that it is unacceptable behavior to silently swallow the difference between Nothing and a non-Maybe.  Signaling an error that satisfies `error-object?` prevents this from happening unless an exception handler to that effect is explicitly installed.

The "simplest" implementation violates the "SHOULD" of R7RS-small that
an implementation is encouraged to detect or report the error. So
while the simplest implementation is still an implementation in the
technical sense, it is a bad implementation.

A better implementation would be (maybe-if m j n) => (if (maybe? m)
(if (just? m) j n) (error "...")).

Note that this better implementation even conforms to the current
wording in SRFI 189.

An even better implementation (assuming a sane "assume", which follows
the above "SHOULD" rule of R7RS-small unless someone compiles the
program in "-O3 mode") is

(maybe-if m j n) => (begin (assume (maybe? m) "...") (if (just? m) j n))

If you want, we can add a post-finalization note to SRFI 145 (or amend
SRFI 145 in some other way), which specifies that "assume" must signal
an error unless a feature identifier "unsafe" (or "ndebug" or
whatever) is set. Or (following SRFI 145's sample implementation) when
the "debug" feature identifier is set. Implementations shipping SRFI
145 are then encouraged to set "debug" by default unless some highly
optimized build is asked for.

The good thing about passing this on to SRFI 145 (or a successor) is
that we have a central place to handle this issue, which will come up
in subsequent SRFIs as well. And implementers of SRFI 189 will know
that they just have to use the last implementation.

Marc