stolfi (Jorge Stolfi) (05/18/91)
I notice that Modula-3 allows exception declarations only at the top level of modules and interfaces. What was the point of this restriction? (Local exceptions seem a pretty natural concept.) --j Jorge Stolfi DEC Systems Research Center stolfi@src.dec.com, ...!decwrl!stolfi
gnelson (Greg Nelson) (05/18/91)
Jorge Stolfi asks,
I notice that Modula-3 allows exception declarations only at the top
level of modules and interfaces.
What was the point of this restriction? (Local exceptions seem a pretty
natural concept.)
You can make sense of local exceptions, but the benefits are not worth
the cost of the effort. I suppose that with local exceptions, you
want the following block to be a checked runtime error:
EXCEPTION E;
BEGIN
TRY
EXCEPTION E; BEGIN RAISE E END
EXCEPT
E: (*Skip*)
END
END
That is, you want the different exception declarations to produce
different exceptions. Now consider:
PROCEDURE P(b: BOOLEAN) =
EXCEPTION E;
BEGIN
TRY
IF b THEN
P(NOT b)
ELSE
RAISE E
END
EXCEPT
E: (* Skip *)
END
END P;
Do you want P(FALSE) to be an unhandled exception? That is, do the
declarations of EXCEPTION E at the two levels of recursion introduce
different exceptions, or the same exception? If they introduce the
same exception, then the procedure call behaves differently than
its body would; semanticists will not like this. If they introduce
different exceptions, programmers might well be surprised. The Modula-3
committee could not find any real use for local exceptions, and
therefore decided that the wisest choice was "none of the above".stolfi (Jorge Stolfi) (05/20/91)
Greg, thanks for your reply.
However, I must say I didn't understand your example:
PROCEDURE P(b: BOOLEAN) =
EXCEPTION E;
BEGIN
TRY
IF b THEN
P(NOT b)
ELSE
RAISE E
END
EXCEPT
E: (* Skip *)
END
END P;
I don't see what is the problem here, since the exception E is always
caught in the same frame where it is raised.
If we substitute the body of P for the call, we get something like
PROCEDURE P(b: BOOLEAN) =
EXCEPTION E;
BEGIN
TRY
IF b THEN
WITH b = NOT b DO
EXCEPTION E;
BEGIN
TRY
IF b THEN
P(NOT b)
ELSE
RAISE E
END
EXCEPT
E: (* Skip *)
END
END P;
END
ELSE
RAISE E
END
EXCEPT
E: (* Skip *)
END
END P;
which doesn't seem to pose any problems of interpretation.
Perhaps you meant to write
PROCEDURE P(b: BOOLEAN) =
EXCEPTION E;
BEGIN
IF b THEN
TRY P(NOT b) EXCEPT E: (* Skip *) END
ELSE
RAISE E
END
END P;
However, in this case the natural interpretation is that the RAISE
E is an error, since (according to the Twelve Changes) P has
an empty RAISES clause, and hence a call to P cannot raise E.
Am I confused?
I am also quite surprised by your assertion that local exceptions have
no real use. Sure, they are not strictly necessary; but the same
can be said of local variables, types, constants, and procedures,
which you agree are Very Good Things.
A typical use of local exceptions would be
PROCEDURE Foo(..) =
EXCEPTION NotFound;
PROCEDURE Lookup(...) RAISES {NotFound} =
BEGIN
...
END Lookup;
BEGIN
...
TRY Lookup(...) EXCEPT NotFound => ... END;
...
END Foo;
--jorge
PS. By the way, the argument that "a procedure call should have the same
effect as its body" is not very convincing. Consider for example
PROCEDURE F(n: INTEGER): REFANY =
TYPE T = BRANDED REF INTEGER;
BEGIN
IF n = 0 THEN
WITH t = NEW(T) DO t^ := 0; RETURN t END
ELSE
RETURN F(n-1)
END
END F;
From this code, I would expect F(0) and F(1) to have the same typecode
(and the SRC compiler seems to agree.) However, if we substitute the
body of F for the call F(n-1), we will get two declarations of type
T, and the brands of F(0) and F(1) will be different.gnelson (Greg Nelson) (05/21/91)
Jorge points out that I flubbed my example. The question is, what
do you want to be the result of P(TRUE) after:
PROCEDURE P(b: BOOLEAN) RAISES ANY =
EXCEPTION E;
BEGIN
IF b THEN
TRY P(NOT b) EXCEPT E: (* Skip *) END
ELSE
RAISE E
END
END P;
stolfi (Jorge Stolfi) (05/21/91)
[Greg Nelson:] The question is, what do you want to be the result
of P(TRUE) after:
PROCEDURE P(b: BOOLEAN) RAISES ANY =
EXCEPTION E;
BEGIN
IF b THEN
TRY P(NOT b) EXCEPT E: (* Skip *) END
ELSE
RAISE E
END
END P;
I suppose the most useful interpretation is that each *static*
occurrence of "EXCEPTION Id" defines a new exception, irrespective
of how many times it is "executed". Then all occurrences of E in the
above example refer to the same exception.
Admittedly this is not consistent with the principle of structural
type equivalence, but is at least consistent with the semantics of
BRANDED T.
By the way, here is another situation where the Report seems
a bit ambiguous about static versus dynamic scopes:
MODULE M2 EXPORT Main;
PROCEDURE MemeChose(n: INTEGER; p: PROCEDURE():INTEGER): BOOLEAN =
PROCEDURE G(): INTEGER =
BEGIN RETURN n END G;
BEGIN
IF n = 0 THEN
RETURN p = G
ELSE
RETURN MemeChose(n-1, G)
END
END F;
PROCEDURE Irrelevant(): INTEGER = BEGIN RETURN 0 END P0;
BEGIN
<* ASSERT NOT MemeChose(1, Irrelevant) *>
END M2.
Should the ASSERT fail or succeed?
The question is whether all instances of G are the same procedural
value, i.e. whether the "environment" component of a procedure value
is a static scope or a dynamic scope. To further confound the issue,
the Report calls G a procedure *constant*...
(The SRC compiler does the right thing, by the way.)
--jorgestt@inmet.inmet.com (05/29/91)
Re: non-top-level exceptions > /* Written 12:41 pm May 21, 1991 by stolfi@src.dec.com */ > > [Greg Nelson:] The question is, what do you want to be the result > of P(TRUE) after: > > PROCEDURE P(b: BOOLEAN) RAISES ANY = > EXCEPTION E; > BEGIN > IF b THEN > TRY P(NOT b) EXCEPT E: (* Skip *) END > ELSE > RAISE E > END > END P; > > I suppose the most useful interpretation is that each *static* > occurrence of "EXCEPTION Id" defines a new exception, irrespective > of how many times it is "executed". Then all occurrences of E in the > above example refer to the same exception. > > Admittedly this is not consistent with the principle of structural > type equivalence, but is at least consistent with the semantics of > BRANDED T. Ada took this path to defining the meaning of exceptions declared in a recursive procedure, namely that each "static" occurrence defines a unique exception. My recommendation: Avoid the problem. Stick with top-level only. It is of marginal use to allow local exceptions, and the butchery done to the normal model of declarations is not worth the trouble. Furthermore, it really gets nasty with "generics" in Ada, since each instantiation is considered a new "static" occurrence, requiring replication of exceptions even if otherwise the code can be shared between two generic instantiations. S. Tucker Taft stt@inmet.inmet.com Intermetics, Inc. Cambridge, MA 02138
stolfi (Jorge Stolfi) (05/30/91)
> [S. Tucker Taft:] Ada took this path to defining the meaning of > exceptions declared in a recursive procedure, namely that each > "static" occurrence defines a unique exception. My recommendation: > Avoid the problem. Stick with top-level only. It is of marginal > use to allow local exceptions, and the butchery done to the normal > model of declarations is not worth the trouble. A strong argument for allowing local exceptions is precisely that they would simplify the model of declarations. If types, procedures, constants, and variables can be declared at any level, why should exceptions be an exception? Besides, BRANDED types already follow the "static" declaration paradigm. > Furthermore, it really gets nasty with "generics" in Ada, > since each instantiation is considered a new "static" occurrence, > requiring replication of exceptions even if otherwise the code > can be shared between two generic instantiations. I suppose this will be a problem also for Modula-3 generics, although the way the latter are implemented (with explicit instantiations) makes the non-equivalence of such exceptions more obvious and understandable. Jorge Stolfi DEC Systems Research Center ---------------------------------------------------------------------- DISCLAIMER: I'm only a user...