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.) --jorge
stt@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...