[e-lang] Remaining semantic issues
Mark Miller
markm at cs.jhu.edu
Mon May 30 16:16:16 EDT 2005
Hi Kevin,
Since your latest email depends on this one, I'll answer it first.
Kevin Reid wrote:
> This is a list of bugs which I believe will require changes to Kernel-E
> semantics or structure.
>
>
> *** SuchThatPattern discards information
>
> Non-primitive patterns such as map and quasi-patterns end up discarding
> information about the failure of subpatterns.
>
> This is already entered as a bug:
>
> https://bugs.sieve.net/bugs/?func=detailbug&bug_id=125617&group_id=16380
Btw, this is now at
<https://sourceforge.net/tracker/index.php?func=detail&aid=1211112&group_id=75274&atid=551529>
> I now think the right solution for fixing/replacing ? is something
> analogous to the Guard protocol - exposing the ejector.
>
> However, I can see no simple way to retrieve failure information from a
> subpattern. Of the two means of 'invoking' patterns:
>
> 1. MatchBindExpr discards the failure exception, except in bindings
> established by the pattern (of which there can be zero).
> 2. DefineExpr discards the distinction between match failure and
> throw()n exceptions.
Does the new experimental trinary-define adequately take care of this issue?
> *** Failing patterns in kernel matchers / expansion of multiple matchers
>
> https://bugs.sieve.net/bugs/?func=detailbug&bug_id=125573&group_id=16380
This is now at
<https://sourceforge.net/tracker/index.php?func=detail&aid=1211088&group_id=75274&atid=551529>
> The Miranda implementations of __respondsTo and __getAllegedType assume
> that if a matcher is present, it is a full delegate object that has the
> corresponding methods itself (so as to correctly report the interface
> as the semi-union of the two objects).
>
> This results in problems when the matcher is intended to handle only
> particular methods:
>
> ? help(require)
> # problem: Prefix doesn't match: run
>
> The straightforward solution is for the Miranda implementations to
> treat a nonmatching matcher equivalently to no matcher. This is as good
> as we can do for __getAllegedType.
>
> For __respondsTo, it could match the matcher's pattern against [verb,
> [null] * arity]. This would typically produce reasonable results, but
> might be surprising in that the pattern is executed without the body.
Indeed. Nevertheless, it's probably the best solution.
> A related problem which becomes more serious once the above is
> resolved, especially if __respondsTo is implemented as I describe:
>
> The current expansion of multiple matchers to a single Kernel-E matcher
> node results in the kernel matcher having a pattern which always
> matches (rather than failing if none of the pre-expansion patterns match).
>
> I see no clean solution to this other than allowing multiple matchers
> in Kernel-E.
Assuming trinary-define becomes an official part of Kernel-E, then, in
combination with __MatchContext, we could solve this for __getAllegedType, but
still not for __respondsTo. Oh well, I was hoping that the combination would
enable us to define a working expansion to the existing Kernel-E.
Therefore, your conclusion remains -- Kernel-E must accept multiple matchers,
and the E parser must stop expanding these to a single matcher.
> *** Exception information leakage
>
> Existing bug: https://bugs.sieve.net/bugs/?func=detailbug&bug_id=125503&group_id=16380
This is now at
<https://sourceforge.net/tracker/index.php?func=detail&aid=1211106&group_id=75274&atid=551529>
> This is my current proposal, partly derived from a discussion with Mark
> Miller on July 19, 2004.
>
> 1. 'Ordinary' exceptions are sealed.
>
> Any exceptions resulting from non-E code, throw(_), throw.eject(null,
> _), or Kernel-E (e.g. 'no such method' or primitive patterns outside of
> match-bind) are sealed by a vat-global sealer. The corresponding
> unsealer is available via the privileged scope.
Yes.
> 2. optEjector is used wherever it matters.
>
> Ejectors follow capability rules, by explicitly specifying the
> exceptional data-and-control-flow path. Therefore, the current pattern
> of using 'optEjector' parameters is kept, and used everywhere that the
> caller might want to distinguish particular exceptions, or get
> information about an exception.
Yes. Do you know of places where we should be doing this that we're not? (No
doubt there are many...)
> 3. Sealers are used to distinguish exceptions across turns.
>
> Since ejectors are useless across vat turns (and therefore for
> inter-vat communication), where a client would otherwise pass an
> ejector, it passes a new kind of object with the same interface:
>
> def makeFailureBrand {
> to run(label) :any {
> def [sealer, unsealer] := <elib:sealing.Brand>(label)
>
> def ejectoid implements PassByCopy {
> to run(problem :Throwable) {
> # SealedBoxThrowable is simply an object which is a Java Throwable
> # and can hold a promise for a SealedBox.
> throw(makeSealedBoxThrowable(mySealer <- seal(problem)))
> }
Is "mySealer" vs "sealer" above a typo?
Assuming it is, I now understand why you felt the need to throw an eventual
ref. More on this when I respond to your recent message.
> to run() {
> failureSealer("unspecified problem")
> }
> }
>
> def guard extends __makeGuard(guard) {
> to coerce(specimen, optEjector) :any {
> def box := SealedBoxThrowable.coerce(specimen, optEjector) \
> .getValue()
I don't understand this. What's the ".getValue()" for?
> # unseal/2 does not actually exist, but would use the ejector
> # for both UnsealingExceptions and the argument not being
> # a proper SealedBox (so its Java argument type must be
> # Object).
Ah yes, unseal is an example answer to my question above -- it should have
always had an optEjector argument (or at least an optional one) for the reason
you explain above.
> return Throwable.coerce(unsealer.unseal(box, optEjector),
> optEjector)
> }
> }
>
> return [ejectoid, guard]
> }
>
> to inline(guard__Resolver) {
> def [ejectoid, bind guard] := makeFailureBrand("inline")
> return ejectoid
> }
> }
>
> Example usage:
>
> def [ejectoid, MyMissingKey] := makeFailureBrand("missing key")
>
> when (farFlexMap <- fetch(key, ejectoid)) -> _(value) {
> # ok
> } catch problem :MyMissingKey {
> # oops
> }
>
> (If it is still desirable that Throwables be DeepPassByCopy (a
> constraint which I think is not particularly useful given these
> proposed restrictions on exception-data flow), then encryption-based
> sealers may be used instead.)
I do think this is still desirable, although it makes ejectoids more different
from ejectors, which I admit is confusing.
> Since the 'ejectoid' (not a name I'm actually proposing) acts almost
> exactly as an Ejector does (run/1 -> never returns), no changes are
> needed to objects which accept an optEjector-style argument.
Except that their argument must be a Throwable. I'm not sure I see why this
is, since it is the containing SealedBoxThrowable that's actually being
thrown. (If Throwables must be DeepPassByCopy, then the argument to an
ejectoid must be DeepPassByCopy, so there'd still be that difference.)
> The inline/1 method (also in need of a better name) allows a shorthand
> for not 'pre-declaring' the pair:
>
> def fb := makeFailureBrand.inline
>
> farFlexMap <- fetch(key, fb(def MyMissingKey))
Clever.
> 4. Stack traces are not public.
>
> From unprivileged code, exceptions do not appear to contain stack
> traces, except perhaps pseudo-traces explicitly added by code
> manipulating exceptions (e.g. a catch-and-rethrow with added information).
Yes.
--
Text by me above is hereby placed in the public domain
Cheers,
--MarkM
More information about the e-lang
mailing list