Version 1.5 of ais/ai-00264.txt
!standard 11.01 (02) 01-05-09 AI95-00264/01
!standard 11.03 (02)
!class amendment 01-05-09
!status No Action (10-0-0) 03-10-03
!status work item 01-05-09
!status received 01-05-09
!priority Medium
!difficulty Hard
!subject Exceptions as Types
!summary
Exception types are added to the language, in order to provide the ability to
pass more information along with exceptions, and to provide a hierarchy of
exceptions.
!problem
Ada 83 had a very basic exception mechanism. It was not possible to pass
information when raising an exception, and exceptions were not integrated with
the overall type structure of the language, making it impossible to pass
exception-related information to subprograms, or to store it in data structures,
etc.
Ada 95 added slightly more powerful capabilities in this area, with the
facilities provided by the predefined package Ada.Exceptions. However, the new
mechanism is still only intended for very basic error detection and logging, and
doesn't provide a general-purpose communication mechanism. It has the merit of
clarifying the notions of exception identity and exception occurrence, but
exceptions are still not integrated with the typing model like protected objects
or tasks are.
It is in fact possible to use the Message associated with an exception
occurrence to pass (relatively small) objects when raising an exception, but
this has to be done by marshalling the object into the string. While adequate
for some purposes, this approach is unnecessarily contrived.
This situation is very unfortunate, because other modern programming languages
like C++ or Java, which are in other ways inferior to Ada, have a much more
powerful exception mechanism.
!proposal
Exception Types
A new class of types, called exception types, is added to the language.
Exception types are untagged, composite, limited types (much like task types).
There is no such thing as a formal exception type, so an exception type may only
be passed to a generic formal limited private type or a formal derived type
whose ancestor is an exception type.
A root exception type may be declared by a new form of type declaration:
type User_Defined_Exception is
exception
C1 : T1;
C2 : T2;
end exception;
A derived exception type may then be declared by extending the above type:
type Extended_Exception is
new User_Defined_Exception with
exception
C3 : T3;
C4 : T4;
end exception;
A conventional exception declaration like:
Ada95_Exception : exception;
is then taken, for compatibility, to declare a root exception type with a
single component Message of type String:
type Ada95_Exception (Length : Natural) is
exception
Message : String (1 .. Length);
end exception;
An exception type may be used to complete a limited private type.
Raising An Exception
Raising an exception creates an exception occurrence (it is also possible to
create exception occurrences by declaring objects of exception types, but this
is not too interesting as exception types are limited).
For compatibility, an exception declared by a conventional exception
declaration may be raised by a conventional raise statement:
raise Ada95_Exception;
On the other hand, an exception declared by an exception type declaration must
be raised by a new form of raise statement, with specifies values for all the
components of the exception occurrence:
raise Ada95_Exception'
(Length => 3,
Message => "foo");
This is the only context where an aggregate for an exception type is legal
(aggregates may not normally be of a limited type). Note that extension
aggregates are not usable in this context because exception types are not
tagged.
Note that the Ada 95 routine Ada.Exceptions.Raise_Exception is equivalent to
the above example.
Exception Handling
An exception handler like:
when E : User_Defined_Exception => ...
catches any exception occurrence of type User_Defined_Exception or of a type
derived thereof. In that respect, the name of an exception type in a handler is
somewhat similar to the name of a class-wide type: it covers an entire class of
types.
All the handlers in a given block must have non-overlapping exception types.
For example, the following is illegal:
exception
when User_Defined_Exception =>
when Extended_Exception =>
This rule is different from what Java does, but it seems more consistent with
other rules of Ada (e.g., for case statements or conventional exception
handlers). It ensures that which alternative is selected doesn't depend on the
lexical order of the handlers.
Inside a handler, the choice parameter provides a constant view of the
exception occurrence being handled. Component selection can be used to extract
information from the occurrence:
exception
when E : User_Defined_Exception =>
if Func (E.T1) = Func (E.T2) then ...
(For compatibility, if the exception was declared by a conventional exception
declaration, we will stick to the Ada 95 semantics, i.e., E is of type
Ada.Exception.Exception_Occurrence.)
[Editor's note: What is the type of the exception is different for members of
a list? That is:
when E : User_Defined_Exception | Constraint_Error =>
Probably this will need to be disallowed.]
An others choice may be used to cover all the exception types not covered by
other handlers, but in such a handler the only possible usages of the choice
parameter are those provided by Ada 95.
!discussion
What If We Did It Like Java?
Assume for a moment that we wanted to mirror in Ada the exception mechanism
provided by Java (bear in mind that this is only an analogy, not the real
mechanism proposed by this amendment). One way to do it would be to posit an
anonymous type from which all exceptions are derived:
type exception is abstract tagged limited private;
From this type, exceptions would be declared as type extensions. So any good
old Ada 95 exception like:
Ada95_Exception : exception;
would be declared by:
type Ada95_Exception is new exception with
record
Message : String (…);
end record;
Note that the component Message reflects the fact that every exception
occurrence in Ada 95 may be parameterized by a string.
Following the same model, we would be able to declare exception parameterized
with non-String data:
type User_Defined_Exception is new exception with
record
C1 : T1;
C2 : T2;
end record;
or extend existing exceptions to associate more data with an exception
occurrence:
type Extended_Exception is
new User_Defined_Exception with
record
C3 : T3;
C4 : T4;
end record;
With this analogy, the exception identity corresponds to the tag of the type,
and an exception occurrence is more-or-less an object of type exception'Class.
There are a number of reasons why we don't want to use tagged types to
represent exceptions, though. First, some of the rules related to tagged types
would cause inacceptable limitations; for instance, it would not be possible
to declare an exception in a nested scope, because that would violate the
accessibility rule of RM95 3.9.1(3). Second, existing implementations might
use for exceptions a representation totally different from that of tagged
types; we don't want to force these implementations to change. Finally, many
of the capabilities of tagged types, like dispatching calls or membership
tests, are irrelevant in this context.
Principles
Still, the above analogy has the merit of showing a number of underlying
principles:
- An exception declares a type, which is characterized by its exception
identity.
- From that type, exception occurrences may be created, typically by a raise
statement.
The proposal has uses these principles.
Methodological Note
The reason why an exception type name in a handler covers an entire class of
types is methodological: we want to simplify exception handling by clients. A
service package should be able to use fine-grained exceptions to signal
anomalous conditions using a very precise parameterization. But some clients
might not be interested in the details, so they might want to handle an entire
class of exceptions in one fell swoop. Say that E0 is a root exception type and
E1, E2 and E3 are derived exception types. The service package would contain
code like:
if ... then
raise E1'(...);
else
case ... is
when ... => raise E1'(...);
when ... => raise E2'(...);
when ... => raise E3'(...);
end case;
end if;
then a client that wants to know the details of the failures would have
handlers like:
exception
when E1 =>
when E2 | E3 =>
but a client that is only interested to know that "something went wrong" in
the service package would have a handler like:
exception
when E0 => ...
A further benefit is that this second client would presumably still work
correctly if a new derived exception type E4 is added during maintenance.
Implementation Issues
While this proposal introduces some amount of implementation complexity, it
doesn't seem insuperably complicated:
- Syntax. Assuming that the new syntax doesn't make the grammar hopelessly
ambiguous, this part should not be problematic.
- Static semantics. Exception types are a new concept, and presumably there
would be a number of legality and static semantics rules associated with
them. However, one area where they have very little impact is generics,
because we are not adding new formal types. In general exception types are
very similar to protected or task types, so one would expect that they
could be handled similarly by compilers.
- Dynamic semantics. I can see two potentially problematic areas here:
o Exception types can be unconstrained types, and therefore the
occurrences may have an arbitrarily large size. In Ada 95, the Message
string could be limited to 200 characters, so this might put an
additional burden on the implementations. On the other hand, this is
somewhat mitigated by the fact that exception occurrences are limited,
so they cannot be resized at the drop of a hat.
o Exception types may have controlled subcomponents. From a language
design standpoint, this requires to define precisely when occurrences
are finalized. From an implementation standpoint, this is another
interaction between finalization and the rest of the language.
Undoubtedly, if this proposal is deemed worth pursuing, there is still
a lot of language design to be done to sort out the interactions with
other language features and the compatibility issues, among others.
!example
[Editor's note: We need a good example here.]
!ACATS test
!appendix
From: Pascal Leroy
Sent: Friday, May 4, 2001 4:34 AM
Purpose
Ada 83 had a very basic exception mechanism. It was not possible to pass
information when raising an exception, and exceptions were not integrated with the
overall type structure of the language, making it impossible to pass exception-related
information to subprograms, or to store it in data structures, etc.
Ada 95 added slightly more powerful capabilities in this area, with the facilities
provided by the predefined package Ada.Exceptions. However, the new mechanism
is still only intended for very basic error detection and logging, and doesn't provide a
general-purpose communication mechanism. It has the merit of clarifying the notions
of exception identity and exception occurrence, but exceptions are still not integrated
with the typing model like protected objects or tasks are.
It is in fact possible to use the Message associated with an exception occurrence to
pass (relatively small) objects when raising an exception, but this has to be done by
marshalling the object into the string. While adequate for some purposes, this
approach is unnecessarily contrived.
This situation is very unfortunate, because other modern programming languages
like C++ or Java, which are in other ways inferior to Ada, have a much more
powerful exception mechanism.
This paper presents a proposal for extending Ada with new features related to
exceptions. These features are compatible with Ada 95, yet they provide roughly the
same expressive power as Java.
What If We Did It Like Java?
Assume for a moment that we wanted to mirror in Ada the exception mechanism
provided by Java (bear in mind that this is only an analogy, not the real mechanism
proposed by this paper). One way to do it would be to posit an anonymous type
from which all exceptions are derived:
type exception is abstract tagged limited private;
From this type, exceptions would be declared as type extensions. So any good old
Ada 95 exception like:
Ada95_Exception : exception;
would be declared by:
type Ada95_Exception is new exception with
record
Message : String (…);
end record;
Note that the component Message reflects the fact that every exception occurrence in
Ada 95 may be parameterized by a string.
Following the same model, we would be able to declare exception parameterized
with non-String data:
type User_Defined_Exception is new exception with
record
C1 : T1;
C2 : T2;
end record;
or extend existing exceptions to associate more data with an exception occurrence:
type Extended_Exception is
new User_Defined_Exception with
record
C3 : T3;
C4 : T4;
end record;
With this analogy, the exception identity corresponds to the tag of the type, and an
exception occurrence is more-or-less an object of type exception'Class.
There are a number of reasons why we don't want to use tagged types to represent
exceptions, though. First, some of the rules related to tagged types would cause
inacceptable limitations; for instance, it would not be possible to declare an exception
in a nested scope, because that would violate the accessibility rule of RM95 3.9.1(3).
Second, existing implementations might use for exceptions a representation totally
different from that of tagged types; we don't want to force these implementations to
change. Finally, many of the capabilities of tagged types, like dispatching calls or
membership tests, are irrelevant in this context.
A Proposal
Still, the above analogy has the merit of showing a number of underlying principles:
- An exception declares a type, which is characterized by its exception identity.
- From that type, exception occurrences may be created, typically by a raise
statement.
I will now use these principles to present a proposal for extending the exception
model of Ada 95.
Exception Types
A new class of types, called exception types, is added to the language. Exception types
are untagged, composite, limited types (much like task types). There is no such thing
as a formal exception type, so an exception type may only be passed to a generic
formal limited private type or a formal derived type whose ancestor is an exception
type.
A root exception type may be declared by a new form of type declaration:
type User_Defined_Exception is
exception
C1 : T1;
C2 : T2;
end exception;
A derived exception type may then be declared by extending the above type:
type Extended_Exception is
new User_Defined_Exception with
exception
C3 : T3;
C4 : T4;
end exception;
(I am not religious about the syntax; I just tried to make it similar to tagged types but
still sufficiently distinct that it doesn't become confusing.)
A conventional exception declaration like:
Ada95_Exception : exception;
is then taken, for compatibility, to declare a root exception type with a single
component Message of type String:
type Ada95_Exception (Length : Natural) is
exception
Message : String (1 .. Length);
end exception;
An exception type may be used to complete a limited private type.
Raising An Exception
Raising an exception creates an exception occurrence (it is also possible to create
exception occurrences by declaring objects of exception types, but this is not too
interesting as exception types are limited).
For compatibility, an exception declared by a conventional exception declaration
may be raised by a conventional raise statement:
raise Ada95_Exception;
On the other hand, an exception declared by an exception type declaration must be
raised by a new form of raise statement, with specifies values for all the components
of the exception occurrence:
raise Ada95_Exception'
(Length => 3,
Message => "foo");
This is the only context where an aggregate for an exception type is legal (aggregates
may not normally be of a limited type). Note that extension aggregates are not usable
in this context because exception types are not tagged.
Exception Handling
An exception handler like:
when E : User_Defined_Exception => …
catches any exception occurrence of type User_Defined_Exception or of a type derived
thereof. In that respect, the name of an exception type in a handler is somewhat
similar to the name of a class-wide type: it covers an entire class of types.
All the handlers in a given block must have non-overlapping exception types. For
example, the following is illegal:
exception
when User_Defined_Exception =>
when Extended_Exception =>
This rule is different from what Java does, but it seems more consistent with other
rules of Ada (e.g., for case statements or conventional exception handlers). It ensures
that which alternative is selected doesn't depend on the lexical order of the handlers.
Inside a handler, the choice parameter provides a constant view of the exception
occurrence being handled. Component selection can be used to extract information
from the occurrence:
exception
when E : User_Defined_Exception =>
if Func (E.T1) = Func (E.T2) then …
(For compatibility, if the exception was declared by a conventional exception
declaration, we will stick to the Ada 95 semantics, i.e., E is of type
Ada.Exception.Exception_Occurrence.)
An others choice may be used to cover all the exception types not covered by other
handlers, but in such a handler the only possible usages of the choice parameter are
those provided by Ada 95.
Methodological Note
The reason why an exception type name in a handler covers an entire class of types is
methodological: we want to simplify exception handling by clients. A service
package should be able to use fine-grained exceptions to signal anomalous
conditions using a very precise parameterization. But some clients might not be
interested in the details, so they might want to handle an entire class of exceptions in
one fell swoop. Say that E0 is a root exception type and E1, E2 and E3 are derived
exception types. The service package would contain code like:
if … then
raise E1'(…);
else
case … is
when … => raise E1'(…);
when … => raise E2'(…);
when … => raise E3'(…);
end case;
end if;
then a client that wants to know the details of the failures would have handlers like:
exception
when E1 =>
when E2 | E3 =>
but a client that is only interested to know that "something went wrong" in the
service package would have a handler like:
exception
when E0 => …
A further benefit is that this second client would presumably still work correctly if a
new derived exception type E4 is added during maintenance.
Implementation Issues
While this proposal introduces some amount of implementation complexity, it
doesn't seem insuperably complicated:
- Syntax. Assuming that the new syntax doesn't make the grammar hopelessly
ambiguous, this part should not be problematic.
- Static semantics. Exception types are a new concept, and presumably there
would be a number of legality and static semantics rules associated with them.
However, one area where they have very little impact is generics, because we
are not adding new formal types. In general exception types are very similar to
protected or task types, so one would expect that they could be handled
similarly by compilers.
- Dynamic semantics. I can see two potentially problematic areas here:
o Exception types can be unconstrained types, and therefore the occurrences
may have an arbitrarily large size. In Ada 95, the Message string could be
limited to 200 characters, so this might put an additional burden on the
implementations. On the other hand, this is somewhat mitigated by the
fact that exception occurrences are limited, so they cannot be resized at the
drop of a hat.
o Exception types may have controlled subcomponents. From a language
design standpoint, this requires to define precisely when occurrences are
finalized. From an implementation standpoint, this is another interaction
between finalization and the rest of the language.
Undoubtedly, if this proposal is deemed worth pursuing, there is still a lot of
language design to be done to sort out the interactions with other language features
and the compatibility issues, among others.
****************************************************************
From: Pascal Leroy
Sent: Thursday, May 10, 2001 3:08 AM
Btw, you have a good question about the type of E in:
when E : User_Defined_Exception | Constraint_Error =>
and I didn't think of this case and I don't know what the right answer is.
> I realize this isn't a completely thought out proposal. Why do you not think
> that a generic formal parameter type should be added? It seems like a
> natural requirement (most other types have one), and it doesn't seem to add
> any problems that the proposal doesn't already have.
For the same reason that we don't have formal types for record types and
protected types. Exception types are in fact very similar to protected types:
they are record-ish things, with some special operations (raising and handling).
We don't have formal parameters that expose the internals of records or
protected types, and for good reasons (it would be hard both from a language
design standpoint and for an implementation standpoint, not to mention the
impact on shared generics). I believe there is no compelling reason to invent
an entirely new mechanism for exceptions.
> I think controlled
> components will be a killer. You may want to think about handling those,
> because the proposal has no chance without a good solution to that problem.
I actually gave quite a bit of thought to that issue. First, note that
exception types have to be return-by-reference types, so there is never any
copy. Therefore, the only issue is with initialization and finalization.
Initialization is easy, it happens when you raise the exception (we would need
to define what happens when Initialize raises an exception, but yawn, this is
not terribly exciting).
Finalization is harder. But keep in mind that an exception occurrence is never
propagated across tasks. So the mechanism I have in mind is: (1) when an
occurrence is handled and not reraised, it is finalized prior to exiting the
handler and (2) when an occurrence is not handled, it is finalized as part of
the finalization of the task that raised it. If an exception is raised during a
rendezvous, two occurrences are created, one in each task, and they both live
their lives independently. If a reraise statement is execute, it propagates the
same occurrence.
An exception occurrence could even have task or protected subcomponents, and
their finalization would work similarly (why anyone would want to do that is
beyond me, though).
****************************************************************
From: Randy Brukardt
Sent: Thursday, May 10, 2001 2:26 PM
> and I didn't think of this case and I don't know what the
> right answer is.
The only answer I could think of is that this is illegal. Anything else seems
too confusing. It would only be illegal for exceptions using the new
capabilities, so there wouldn't be a compatibility problem.
> I actually gave quite a bit of thought to that issue. First, note that
> exception types have to be return-by-reference types, so there is never any
> copy. Therefore, the only issue is with initialization and finalization.
So procedure Ada.Exceptions.Save_Occurrence doesn't work on these guys? That
seems like a step backwards. (Assuming it doesn't, then it seems that none of
the other routines in Ada.Exceptions do, either, and that certainly is
unfriendly.)
It really appears that occurrences need to be tagged, because we need them to
be able to be classwide so that the routines in Ada.Exceptions can work. (Note
that does *not* mean that *exceptions* are tagged, only that *occurrences*
are.)
> Initialization is easy, it happens when you raise the exception (we would
> need to define what happens when Initialize raises an exception, but yawn,
> this is not terribly exciting).
We would have to be careful to apply AI-83 to the aggregate here, so that no
copy takes place.
> Finalization is harder. But keep in mind that an exception occurrence is
> never propagated across tasks.
But it can be copied to another task. (Using Save_Occurrence).
> An exception occurrence could even have task or protected subcomponents, and
> their finalization would work similarly (why anyone would want to do that is
> beyond me, though).
But that implies that Save_Occurrence would no longer be usable, and that
certainly seems like a step backwards. Perhaps Save_Occurrence could be defined
as:
procedure Save_Occurrence (Target : out Exception_Occurrence;
Source : in Exception_Occurrence'class);
with it only copying the "parent part" (in this case, the occurrence
information).
There is a problem, however, with Ada.Exceptions.Exception_Message, as the
definition in your proposal says that no message is available in most exception
types. I suppose it too would not be classwide. (Exception_Name and
Exception_Information ought to work on all exception occurrences).
Sigh. More work needed here.
****************************************************************
From: Pascal Leroy
Sent: Friday, May 11, 2001 1:51 AM
> So procedure Ada.Exceptions.Save_Occurrence doesn't work on these guys? That
> seems like a step backwards. (Assuming it doesn't, then it seems that none
> of the other routines in Ada.Exceptions do, either, and that certainly is
> unfriendly.)
Well, evidently Save_Occurrence cannot work if one of the components of the
exception is, say, a task.
My idea was that Save_Occurrence (and most of the stuff in Ada.Exceptions)
would only be retained for Ada 95-like exceptions. For new-style
exceptions, the copy operation would have to be implemented by the user,
either using component-by-component assignment or using streaming. Either
way, we don't have to define what happens when you copy an entire exception
object.
> It really appears that occurrences need to be tagged, because we need them
> to be able to be classwide so that the routines in Ada.Exceptions can work.
> (Note that does *not* mean that *exceptions* are tagged, only that
> *occurrences* are.)
I am not sure how occurrences may be tagged while exceptions aren't. If you
mean that the implementation must record the exception id as some hidden
component of the exception occurrence, then yes, you're right.
At any rate, most of the stuff in Ada.Exceptions is only needed because
Exception_Occurrence is a private type. New-style exceptions are not
private, so you can just access their internals as you do with vanilla
records.
> There is a problem, however, with Ada.Exceptions.Exception_Message, as the
> definition in your proposal says that no message is available in most
> exception types. I suppose it too would not be classwide. (Exception_Name
> and Exception_Information ought to work on all exception occurrences).
I don't see why. If you want to use exceptions for communication purposes
inside your program (as opposed to logging, reporting errors to the user,
etc.) a structured data type is probably much better than a string. If you
actually want a message, put a string component in you exception type
definition.
In essence, I guess I am saying that I see Ada.Exceptions as mostly
obsolescent, and retained for compatibility with Ada 95.
****************************************************************
From: Randy Brukardt
Sent: Friday, May 11, 2001 2:23 PM
> I am not sure how occurrences may be tagged while exceptions aren't. If you
> mean that the implementation must record the exception id as some hidden
> component of the exception occurrence, then yes, you're right.
No, I mean that the type Exception_Occurrence is a tagged type. The "exception
type" is just a component of it.
> At any rate, most of the stuff in Ada.Exceptions is only needed because
> Exception_Occurrence is a private type. New-style exceptions are not
> private, so you can just access their internals as you do with vanilla
> records.
>
I think we agree on Exception_Message. But Exception_Name and
Exception_Information are critical.
> In essence, I guess I am saying that I see Ada.Exceptions as mostly
> obsolescent, and retained for compatibility with Ada 95.
Not a chance. Ada.Exceptions is critical for logging and debugging purposes;
making it unusable with a substantial fraction of exceptions is unacceptable.
Calling it obsolescent is completely ridiculous.
For example, it would be nice if Claw had a root exception that parented all of
the exceptions it raises, so that you could just handle "any Claw error". But
callers of Claw certainly need to be able to log messages and report the
exception information.
All exception occurrences have an Exception_Name. This is useful any time that
there is more than one exception handled in a handler. Certainly, this proposal
*increases* the need for the exception name, because a parent exception can
cover many of them. (For debugging, knowing the exact exception is critical).
Similarly, many compilers put useful information into the occurrence, such as
line numbers (Janus/Ada puts an entire call traceback into it). That information
is very valuable for debugging, and it has nothing whatsoever to do with
anything that the user can create. Loss of access to it is unacceptable.
For instance, in your current proposal, if I added a root exception to Claw,
which then all of the other exceptions were derived from, that would make all of
the Claw exceptions "exception types". If I then wrote the (common) handler:
when Info:Claw.Any_Claw_Error =>
Log_Item ("This routine - " & Ada.Exceptions.Exception_Information(Info));
you're saying that this is illegal?? No way. I will vote against any proposal
which restricts the use of Ada.Exceptions.Exception_Information or
Ada.Exceptions.Exception_Name. Period. They are far more important than any
possible gain from an exception hierarchy or carrying additional information
with exceptions.
----
What I was proposing was that type Exception_Occurrence be tagged, so that
Ada.Exceptions could be rewritten with appropriate classwide routines.
package Ada.Exceptions is
...
function Exception_Identity (X : Exception_Occurrence'Class) return Exception_Id;
function Exception_Name (X : Exception_Occurrence'Class) return String;
function Exception_Information (X : Exception_Occurrence'Class) return String;
function Exception_Message (X : Exception_Occurrence) return String; -- Not classwide; only works on original type.
procedure Save_Occurrence (Target : out Exception_Occurrence; Source : in Exception_Occurrence);
end Ada.Exceptions;
However, that has an inheritance problem. Probably we would need to introduce
"Root_Exception_Occurrence", and make the global operations available on it.
Note that the tag of these types is not really relevant; it doesn't need to have
anything to do with the exception type, only that you can tell
"Exception_Occurrence" from an occurrence created from some exception type.
(I think it is a mistake to blur the distinction between an exception and an
occurrence; they're clearly different things.)
If I had time, I'd work out a complete alternate model, but I don't have time.
(And personally, I don't think this proposal is going any further than
extensible enumeration types.)
****************************************************************
From: Pascal Leroy
Sent: Monday, May 14, 2001 4:30 AM
> No, I mean that the type Exception_Occurrence is a tagged type. The
> "exception type" is just a component of it.
I started along that line, and tried to piggyback on tagged types, but failed.
First, if you make Exception_Occurrence tagged, you have a gross incompatibility
(it isn't at the moment). But more importantly, you don't want to require
implementations to change their representation of exception occurrences to make
them tagged. That could be a major, major change for some implementations, and
I am trying to minimize the cost of the new feature (it's useless to design a
nice-and-fancy mechanism if nobody implements it).
> I think we agree on Exception_Message. But Exception_Name and
> Exception_Information are critical.
Agreed, but these could be replaced by attributes for new-style exceptions.
(Aside: I think a 'Name attribute would in general be a useful capability for
just about any entity of the language. I have often seen code containing
logging messages like "exception foo in subprogram bar" and then Bar is renamed
into Baz and the message is left unchanged. I would like to be able to write
Bar'Name.)
> For instance, in your current proposal, if I added a root exception to Claw,
> which then all of the other exceptions were derived from, that would make
> all of the Claw exceptions "exception types". If I then wrote the (common)
> handler:
>
> when Info:Claw.Any_Claw_Error =>
> Log_Item ("This routine - " &
> Ada.Exceptions.Exception_Information(Info));
>
> you're saying that this is illegal?? No way. I will vote against any
> proposal which restricts the use of Ada.Exceptions.Exception_Information or
> Ada.Exceptions.Exception_Name.
Keep cool, we are not at the voting stage yet :-)
Would Info'Exception_Information be more acceptable to you? A critical
difference between the two is that by using an attribute we do not force the
implementation to use tagged types/class-wide types/dispatching, which might be
very destabilizing. Of course internally it could work that way, but it
wouldn't have to.
> What I was proposing was that type Exception_Occurrence be tagged, so that
> Ada.Exceptions could be rewritten with appropriate classwide routines.
>
> ...
>
> However, that has an inheritance problem. Probably we would need to
> introduce "Root_Exception_Occurrence", and make the global operations
> available on it.
It is full of compatibility problems: you cannot derive from
Exception_Occurrence in a nested scope, all derivations now need an extension,
etc.
> (I think it is a mistake to blur the distinction between an exception and an
> occurrence; they're clearly different things.)
In general I agree with this statement, and I was trying to make the difference
more apparent. Obviously I failed...
> (And personally, I don't think this proposal is going any further than
> extensible enumeration types.)
Maybe, but it was on my action items list. I would really like it if we could
improve the exceptions model of Ada at a reasonable implementation cost, but
this is a hard problem, especially with compatibility issues.
****************************************************************
From: Randy Brukardt
Sent: Tuesday, May 15, 2001 12:21 AM
> Would Info'Exception_Information be more acceptable to you? A critical
> difference between the two is that by using an attribute we do not force the
> implementation to use tagged types/class-wide types/dispatching, which might
> be very destabilizing. Of course internally it could work that way, but it
> wouldn't have to.
That would be a solution, but of course it would require changing code (and
coding habits).
****************************************************************
From: Robert Dewar
Sent: Saturday, October 26, 2002 10:25 AM
Here is a different subject entirely and I am really posting here to ask
whether anyone has registered this.
There is no restriction in Ada on raising exceptions in Pure units, i.e.
it is just fine for a pure subprogram to raise CE.
However, you can't raise an exception with a message because of the
arbitrary restriction imposed by the fact that Ada.Exceptions cannot be
Pure.
(once again, the mess with Pure/Preelaborate etc strikes and no, I am not
suggesting trying to fix this).
But not being able to raise exceptions with a message is really annoying.
I have appended at the bottom a horrible kludge package that GNAT provides
for achieving this in limited circumstances :-)
How about elevating messages to first class status (even when you can
WITH Ada.Exceptions it is kludgy to have to call Raise_Exception with
an identity attribute even the more so because due to bad design this
procedure can return :-(
Perhaps
raise exception with message
e.g.
raise Constraint_Error with "128-bit arithmetic overflow";
Here is the kludge package from GNAT:
------------------------------------------------------------------------------
-- --
-- GNAT RUN-TIME COMPONENTS --
-- --
-- G N A T . E X C E P T I O N S --
-- --
-- S p e c --
-- --
-- $Revision: 1.5 $
-- --
-- Copyright (C) 2000-2002 Ada Core Technologies, Inc. --
-- --
-- GNAT is free software; you can redistribute it and/or modify it under --
-- terms of the GNU General Public License as published by the Free Soft- --
-- ware Foundation; either version 2, or (at your option) any later ver- --
-- sion. GNAT is distributed in the hope that it will be useful, but WITH- --
-- OUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY --
-- or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License --
-- for more details. You should have received a copy of the GNU General --
-- Public License distributed with GNAT; see file COPYING. If not, write --
-- to the Free Software Foundation, 59 Temple Place - Suite 330, Boston, --
-- MA 02111-1307, USA. --
-- --
-- As a special exception, if other files instantiate generics from this --
-- unit, or you link this unit with other files to produce an executable, --
-- this unit does not by itself cause the resulting executable to be --
-- covered by the GNU General Public License. This exception does not --
-- however invalidate any other reasons why the executable file might be --
-- covered by the GNU Public License. --
-- --
-- GNAT was originally developed by the GNAT team at New York University. --
-- It is now maintained by Ada Core Technologies Inc (http://www.gnat.com). --
-- --
------------------------------------------------------------------------------
-- This package provides an interface for raising predefined exceptions
-- with an exception message. It can be used from Pure units.
-- There is no prohibition in Ada that prevents exceptions being raised
-- from within pure units. The raise statement is perfectly acceptable.
-- However, it is not normally possible to raise an exception with a
-- message because the routine Ada.Exceptions.Raise_Exception is not in
-- a Pure unit. This is an annoying and unnecessary restrictiona and this
-- package allows for raising the standard predefined exceptions at least.
package GNAT.Exceptions is
pragma Pure (Exceptions);
type Exception_Type is limited null record;
-- Type used to specify which exception to raise.
-- Really Exception_Type is Exception_Id, but Exception_Id can't be
-- used directly since it is declared in the non-pure unit Ada.Exceptions,
-- Exception_Id is in fact simply a pointer to the type Exception_Data
-- declared in System.Standard_Library (which is also non-pure). So what
-- we do is to define it here as a by reference type (any by reference
-- type would do), and then Import the definitions from Standard_Library.
-- Since this is a by reference type, these will be passed by reference,
-- which has the same effect as passing a pointer.
-- This type is not private because keeping it by reference would require
-- defining it in a way (e.g a tagged type) that would drag other run time
-- files, which is unwanted in the case of e.g ravenscar where we want to
-- minimize the number of run time files needed by default.
CE : constant Exception_Type; -- Constraint_Error
PE : constant Exception_Type; -- Program_Error
SE : constant Exception_Type; -- Storage_Error
TE : constant Exception_Type; -- Tasking_Error
-- One of these constants is used in the call to specify the exception
procedure Raise_Exception (E : Exception_Type; Message : String);
pragma Import (Ada, Raise_Exception, "__gnat_raise_exception");
pragma No_Return (Raise_Exception);
-- Raise specified exception with specified message
private
pragma Import (C, CE, "constraint_error");
pragma Import (C, PE, "program_error");
pragma Import (C, SE, "storage_error");
pragma Import (C, TE, "tasking_error");
-- References to the exception structures in the standard library
end GNAT.Exceptions;
****************************************************************
From: Robert I. Eachus
Sent: Saturday, October 26, 2002 4:14 PM
>However, you can't raise an exception with a message because of the
>arbitrary restriction imposed by the fact that Ada.Exceptions cannot be
>Pure.
>
How difficult would it be to fix this by making Ada.Exceptions pure? As
I see it, this is a question for implementors. As far as I am
concerned, this is one of those packages that should have been pure to
begin with.
****************************************************************
From: Robert Dewar
Sent: Saturday, October 26, 2002 4:33 PM
It is nowhere near a pure package, either conceptually or actually.
It has access types in it so that's decisive in any case.
For fun I tried compiling Ada.Exceptions with pragma Pure. Here is what I
get:
68. type Subprogram_Descriptor_List_Ptr is
|
>>> named access type not allowed in pure unit
71. Subprogram_Descriptors : Subprogram_Descriptor_List_Ptr;
|
>>> declaration of variable not allowed in pure unit
83. Num_Subprogram_Descriptors : Natural;
|
>>> declaration of variable not allowed in pure unit
89. Exception_Tracebacks : Integer;
|
>>> declaration of variable not allowed in pure unit
94. Zero_Cost_Exceptions : Integer;
|
>>> declaration of variable not allowed in pure unit
227. (Subprogram_Descriptor_List, Subprogram_Descriptor_List_Ptr);
|
>>> named access types not allowed in pure unit
864. Counter : Unsigned := 0;
|
>>> declaration of variable not allowed in pure unit
==============Error messages for source file: a-except.ads
54. type Exception_Occurrence_Access is access all Exception_Occurrence;
|
>>> named access type not allowed in pure unit
118. subtype EOA is Exception_Occurrence_Access;
|
>>> named access types not allowed in pure unit
347. Id => Null_Id,
|
>>> non-static constant in preelaborated unit
354. Tracebacks => (others => Null_Loc));
|
>>> non-static constant in preelaborated unit
****************************************************************
From: Robert Dewar
Sent: Saturday, October 26, 2002 4:36 PM
Robert (Eachus), remember that even System itself is not guaranteed is
required to be Pure, there is implementation permission to allow it to
be Pure but this is not required.
(there's a little cleanup that should be considered, require System to
be Pure, I wouldn't be surprised if everyone does that already).
****************************************************************
From: Robert I. Eachus
Sent: Sunday, October 27, 2002 7:17 PM
>It is nowhere near a pure package, either conceptually or actually.
>It has access types in it so that's decisive in any case.
Access type actually, I overlooked Exception_Occurance_Access.
(Parenthetically, I don't know why "access all" has to be verboten in
Pure units, because there is no requirement for an associated collection.)
But that is all detail. The important answer is the one Robert gave as
an implementor, which indicates a lot of buried impurity.
****************************************************************
From: Robert Dewar
Sent: Sunday, October 27, 2002 7:38 PM
And I think in practice this kind of buried impurity will be typical, since
there is a lot of conceptually not very pure stuff here. I would hardly
regard Ada.Eceptions as a collection of mathematically well behaved
functions :-)
****************************************************************
From: Tucker Taft
Sent: Saturday, October 26, 2002 5:33 PM
This seems like a nice, simple addition to the language.
Basing it on "purity" requirements is clever also ;-).
(Robert the Pure...)
Robert Dewar wrote:
> ...
>
> Perhaps
>
> raise exception with message
>
> e.g.
>
> raise Constraint_Error with "128-bit arithmetic overflow";
****************************************************************
From: Robert Dewar
Sent: Saturday, October 26, 2002 6:00 PM
Well it was not some kind of clever idea, but rather a reaction to a real
need :-)
We like to give messages with all exceptions, and for example our pure
64-bit arithmetic-with-overflow package could not do this without a
junk package.
The trouble is that the kludge approach is not easy to extend, because
there is no simple way of passing an exception. You can't use
Exception_Name'Identity because that type is defined in the same
annoying impure package :-)
I suppose we could introduce a new attribute that gave an address value
that could be passed to a pure procedure, or even an instance of some
bogus type in the kludge package (UGH!)
****************************************************************
From: Nick Roberts
Sent: Friday, March 25, 2005 6:27 AM
AI 264 proposed a significant extension to the Ada exception mechanism,
permitting a new exception type to be declared that allowed arbitrary data
to accompany an exception occurrence.
This proposal seems to have been unanimously, presumably because the ARG
felt it was overkill (and I agree).
However, this proposal was made to solve a real problem (the passing of data
with an exception, beyond what is practicable using strings), and the
problem remains. In particular, the OMG IDL (of CORBA) mapping into Ada will
need to be updated for Ada 2005, and this presents an opportunity to improve
the currently awful mapping of exception handling.
The proposal of AI 264 would have solved this problem, but something less
heavy would also help. I propose an extra package:
with Ada.Streams;
package Ada.Exceptions.Parametized is
type Params_Stream_Type(
Initial_Size : Ada.Streams.Stream_Element_Count) is
new Ada.Streams.Root_Stream_Type with private;
procedure Read(
Stream : in out Params_Stream_Type;
Item : out Ada.Streams.Stream_Element_Array;
Last : out Ada.Streams.Stream_Element_Offset);
procedure Write(
Stream : in out Params_Stream_Type;
Item : in Ada.Streams.Stream_Element_Array);
procedure Raise_Exception(
E : in Exception_ID;
Params : not null access Params_Stream_Type;
Message : in String := "");
function Has_Parameters(
X : Exception_Occurrence) return Boolean;
function Exception_Parameters(
X : Exception_Occurrence) return access Params_Stream_Type;
private
...
end Ada.Exceptions.Parametized;
The idea is that this package allows a stream to be associated with an
exception (when it is raised), into which the associated data can be
written, so that when the exception is caught and handled, the data can be
read from the stream. Because the raiser must allocate (space in memory for)
the stream, the implementation does not have to deal with this problem.
Could this be made an optional part of the standard, or a secondary
standard?
Also, at the end of the discussion, RBKD suggested that the 'raise'
statement's syntax be extended to allow the exception message to be
provided. This would be especially useful in pure packages, which cannot
'with' Ada.Exceptions (because it is not pure). Has this idea also been
conscientiously dropped?
I'm really just wanting to spark a discussion on this subject.
****************************************************************
From: Pascal Leroy
Sent: Friday, March 25, 2005 7:50 AM
I have remained frustrated by this. AI 264 led us down a path of
inextricable complexity, and killing it was the right thing to do. On the
other hand the problem it was trying to address is real, and it is
unfortunate that Ada 2005 is not going to bring much progress in this
area. This is one of these very difficult problems (like limited with or
interfaces) that would have required a great deal of time and energy and
many iterations to properly address. Obviously we didn't have the stamina
(or we thought that other topics were more important).
> The proposal of AI 264 would have solved this problem, but
> something less heavy would also help. I propose an extra package:
>
> ...
>
> The idea is that this package allows a stream to be
> associated with an exception (when it is raised), into which
> the associated data can be written, so that when the
> exception is caught and handled, the data can be read from
> the stream. Because the raiser must allocate (space in memory
> for) the stream, the implementation does not have to deal
> with this problem.
I have seen users who were doing basically that, except that they were
encoding the stream as part of the exception message. If the compiler is
friendly enough to support biggish exception messages this works fine, and
it can be written without any support from the language or the compiler.
> Could this be made an optional part of the standard, or a
> secondary standard?
The standard is in the process of being finalized, so certainly not in
2005. Maybe next time. As for a secondary standard, sure, if someone
feels like starting an international workshop agreement to get things
going.
> Also, at the end of the discussion, RBKD suggested that the
> 'raise' statement's syntax be extended to allow the exception
> message to be provided. This would be especially useful in
> pure packages, which cannot 'with' Ada.Exceptions (because it
> is not pure). Has this idea also been conscientiously dropped?
Look at AI 361 (or better, look at section 11.3 in the draft RM at
http://www.adaic.com/standards/ada06.html). I think it does just what you
want.
****************************************************************
From: Marius Amado Alves
Sent: Friday, March 25, 2005 7:02 AM
Why streams? Because of middlewares? Why not a generic parameter?
Because of language profiles that forbid generics?
generic
type Parameter is private (<>);
package Ada.Exceptions.Parametrized is
procedure Raise_Exception(
E : in Exception_ID;
Param : Parameter;
Message : in String := "");
function Exception_Parameter(
X : Exception_Occurrence) return Parameter;
...
****************************************************************
From: Pascal Leroy
Sent: Saturday, March 26, 2005 3:53 AM
At a high-level you can view it that way, I suppose, but this is fraught
with difficulties, because of issues with controlled types, masters, etc.
Say that Parameter is a controlled type. During Raise_Exception, when
does it get finalized? In a handler, do you get a finalized object? If
not, what is its master? These are the questions (among others) that
killed AI 264.
The advantage of a stream is that it's essentially an array of bytes, and
most of those issues disappear.
****************************************************************
Questions? Ask the ACAA Technical Agent