From: "Robert I. Eachus" <rieachus@comcast.net>
Subject: Re: Question about OO programming in Ada
Date: Wed, 26 Nov 2003 12:58:52 -0500
Date: 2003-11-26T12:58:52-05:00 [thread overview]
Message-ID: <GpednY19wYJAdFmiRVn-gg@comcast.com> (raw)
In-Reply-To: <bq0qfs$3ja$1@online.de>
Ekkehard Morgenstern wrote:
> Ok. What I'd like to know is if I can view Ada record types as classes of
> objects.
Yes. In Ada every type is a member of one or more classes of types.
But for dispatching, read on.
> I.e. if I declare a record, like
>
> type My_Type1 is
> record
> Field1 : Integer;
> Field2: Integer;
> end record;
>
> Can I extend the record type like
>
> type My_Type2 is new My_Type with
> record
> Field3 : Integer;
> Field4 : Integer;
> end record;
>
> such that the procedures declared to operate on type My_Type1 will operate
> on the My_Type1 fields of My_Type2.
To extend a record type, the parent type needs to be tagged. Of course,
the parent can be explicitly derived from a third tagged type, and so
on. The rule is that to be tagged, a non-derived type must include the
reserved word tagged in its declaration, and a derived type must include
a extension part, which may be null.
> I gathered from what I read that I need to declare My_Type1 as a tagged
> type.
See above.
> Now, I read that method dispatching (with static dispatching corresponding
> to method overloading in C++, and runtime dispatching corresponding to
> virtual methods in C++), would be possible only by using a class-wide type,
> such as My_Type1'Class in the first parameter of a procedure or in the
> return value of a function.
This may be correct for C++ but it is definitely not correct for Ada.
In Ada, a primitive operation can be dispatched to if it has a parameter
or return type that is a tagged type. If there is more than one such
parameter or result type, they must all resolve to the same specific
type at the point of the call. If you want to write a dispatching
operation that takes more than one tagged operand, and of different
specific types you have to select one such parameter as the one that you
dispatch on. The non-dispatching parameters or result type must be
class wide. Inside the operation you can further dispatch on the
classwide arguments.
That is complex enough to deserve an example. Let's say you want to
write a registration system for your state's DMV. For driver's
licenses, you have an operand of type Person. To allow for special
processing in some cases, you decide to make this type tagged. Since
you don't want multiple copies of the same person in the system, let's
make Person limited:
type Person is tagged limited private;
To deal with the complexities of registering truck, motorcycles, etc.,
you create a vehicle type:
type Vehicle is tagged limited private;
Of course, we need a registration type which will also have several
variants, and you decide to make this type tagged as well:
type Registration is tagged limited private;
Since I made these limited, there will be a need for access types so
that for example a registration can contain a vehicle reference and an
owner reference:
type Person_Ref is access all Person'Class;
type Vehicle_Ref is access all Vehicle'Class;
type Registration_Ref is access all Registration'Class;
Whew! Got all that out of the way. Incidently, even though the _Ref
types are needed in the actual record declarations, there is not
necessarily a need for the user of the abstractions to be aware of the
types. My usual habit is to put them in the private part to start, and
move them out later if necessary.
Now we can start to declare our registration function:
function Vehicle_Registration (Owner: in out Person;
The_Vehicle: in out Vehicle)
return Registration;
The compiler promptly slaps our hand. You can't have a function that
dispatches on three separate classes. But in reality, that is not
really what we wanted anyway. Let me work this both forward and
backward. First, we don't really want to register Vehicles, we want to
register cars, trucks, motorcycles, buses, boats, etc. And there may be
"generic" people, but the base types for Vehicles and Registrations
should be abstract.
type Vehicle is abstract tagged limited private;
type Registration is abstract tagged limited private;
(It is sort of amazing to me that you often use one or two of abstract,
tagged, limited, or private in a declaration, but if you need three, you
usually end up adding the fourth.)
Now in our base package we can see that we want an abstract function for
registration. The type of registration returned will be determined
within the specific function dispatched to, and any special cases
involving people can be handled by dispatching within the actual
function. So what we really wanted was:
abstract function Register(Owner: in out Person'Class;
The_Vehicle: in out Vehicle)
return Registration'Class;
Perhaps when you finish, you will end up returning a Registration_Ref,
but so far it isn't necessary to make that type public. (Notice that
you can always write Register(Someone, Car)'Access, if you really need
to get an access value to assign to a Registration_Ref, or in some cases
Register(Someone, Car)'Unchecked_Access.)
Later you will write (child) packages to deal with cars, trucks, buses,
and so on. In cars you will have:
function Register(Owner: in out Person'Class
The_Car: in out Automobile)
return Registration'Class;
You know that you will always return an Automobile_Registration when
called with an Automobile unless there is a separate
Temporary_Automobile_Registration. (You haven't decided on that issue
yet.) But AT THE POINT OF THE CALL to the abstract Register, it is not
known at compile time what type of Registration will be returned.
There is a lot to learn in the above. Notice that we have used
classwide parameters or return values for two different reasons in the
declaration of Register above. The first occurs in Owner, where the
owner may be any type derived from Person, and the Register function may
not need to use internal dispatching on the parameter. Or, for example,
registering a motorcycle may require that the Owner have a license to
ride motorcycles. This can be determined by nested dispatching, or more
usually by "if Owner in Motorcycle_Driver'Class then..."
But the second instance, the use of a classwide result type is more
interesting. This is not to cause dispatching, but to actually return
an object whose class is not known at compile time. A typical usage is:
procedure Something (...) is
The_Registration: Registration'Class
:= Register(The_Owner, The_Vehicle);
begin
-- further operations on The_Registration, which may involve
-- dispatching calls.
end Something;
> First of all, how do I accomplish returning a reference in Ada? What is the
> default behaviour of parameter passing and return in Ada? Someone said here
> that if I use an "in" parameter in a procedure or function definition, the
> object will be passed by reference. What about the returing of objects? Are
> they returned by copy or returned by reference?
Whether an object is passed by value, value-return, or reference depends
on the object's class, not on whether the parameter is in, in out, or
out. In parameters can be referenced but not changed, in out parameters
can be referenced and changed, and for out parameters the attributes are
defined at the point of the call, but there may not be an initial value.
There are tricks you can play in Ada to modify in parameters, and of
course, if you have an in access value, you may be able to modify the
value designated. (The may is because the view may be limited.) In
general, whether a parameter is in, in out, or out is decided to reflect
the expectations that a user of the subprogram should expect.
> If I use a class-wide type, like My_Type1'Class, what kind of object is
> that? Is it similar to Java's "object.class"?
An object of a class-wide type always has a specific type. It is just
that the type may not be determined until the object is created at
run-time. Of course, if a class-wide object declaration is nested
inside a subprogram, the type of the class-wide object--or any
class-wide parameters--can change each time the subprogram is called.
> What's the difference between passing a class-wide type as an in/out
> parameter of a procedure, and passing an access to it?
>
> i.e. the difference between
>
> procedure Proc( Param : in out Type'Class );
>
> and
>
> type Type_Class_Access is access Type'Class;
> procedure Proc( Param : in Type_Class_Access );
Not much. Or rather, using the second form unnecessarily is a sign that
you are new to Ada. There are cases where the access parameter won't
cause any additional difficulties, and other cases where it will mean
that you need to explicitly free the storage to avoid memory leaks.
If you pass objects instead of pointers, you eliminate the potential
memory leaks. This doesn't mean you shouldn't use pointers (access
types) where appropriate. But in Ada a good rule is that you only use
explicit access types to create data structures. And since you normally
use abstract data types to implement these structures, the notation is
limited to the private parts and bodies of a few packages. (And you
probably chose those packages from a library instead of "rolling your
own.") But sometimes you need to create your own interlinked data
structures, as in the example of Persons, Vehicles, and Registrations.
In those cases, you can use different data structure packages, written
as generics to implement some of the data structures you need.
Incidently, this is my problem with a set of data structure packages as
a part of Ada. There are several ways to implement these structures in
Ada, and which one you want to use depends on intimate details of the
problem you are trying to solve. So a limited set of official solutions
IMHO would be worse than the current situation. (A core set of data
structure packages in the standard, plus an indication that compiler
vendors and others are encouraged to extend the set is a different story.)
> I think I know what run-time dynamic dispatching, class-wide types and
> derived types refer to, but I'm not sure about implicit type conversions
> that I can use.
Don't cheat yourself. It is possible to subset Ada's support for
object-oriented programming to match what you currently know about the
subject from C++ or some other OO languages. But the Ada model is much,
much richer than that. A lot of this richness involves information
hiding. In Ada you constantly find yourself asking two questions:
What is the minimum required information to use this subprogram,
data-type, abstraction, and so on?
and:
Is there a different way to express this which allows more
information hiding?
Why this emphasis on information hiding? The converse of information
hiding is coupling. The more coupling you have, the more bugs you have,
the bugs are harder to find, and fixing bugs is more likely to create
additional bugs. So maximizing information hiding is the easy way to
bug free programming.
> Like, I'd want to do something similar like this:
>
> C := access A;
>
> In C++, I can just write " C = &A" (if C is a pointer to B). How do I do
> that in Ada?
C := A'Access;
> I tried to use the pointer-like semantics of access to class-wide types, but
> I ended up with doing manual type conversions (or casting) all over. Is that
> normal?
Yes. Ada is trying to tell you something. In Ada the intent is that
cleaner means better. If you are converting back and forth all over the
place, it means that your programming model is not well thought out.
Note that if you really are programming in a scope where you need to do
such conversions, you can declare your own conversion functions, or
overload subprograms to take both types of operands:
type Bar_Ref is access Bar;
...
procedure Foo(in out Bar_Ref) is
begin Foo(Bar'Access); end Foo;
pragma Inline(Foo);
But again, if you want or need something like that, it is usually an
indication of a design problem.
> I thought by using this newsgroup, I could be spared from reading in the Ada
> 95 Rationale. Which is very explicit, but also verbose, and I'd like to make
> my learning process quicker. ;-)
Quicker is possible, but tough. We used to say that learning Ada was 5%
learning syntax and 95% learning software engineering. Then along came
Ada 95. It cleaned up a lot of special cases to make learning how to
program in Ada easier, and added support for several powerful new
programming paradigms. My guess is that Ada 95 made learning the
language somewhat easier, but tripled the amount of software engineering
you need to know to use the whole language. You can see that above,
with delegation of dispatching, multiple dispatch, and returning
classwide objects. Those are just from the object-oriented extensions
in Ada 95.
I could probably teach a one semester course on how to use child
packages in Ada, and then follow it with a semester on generic package
parameters in Ada, or a semester on real-time programming in Ada, or a
semester on high integrity programming, oops make that at least two
semesters! I could also probably create a year long course on how to
use access discriminants in Ada, but IMHO, maybe 30 people world-wide
need to know all the tricks of using them. (Maybe a course on when it
looks like you need to use access discriminants, but don't?)
> English is a foreign language to me, and I'd rather not
> read umpteen pages of Ada language theory when I can avoid it! ;-) )
Hmmm. There should be some good Ada reference material auf Deutch, but
I don't know what offhand. (And at this point I'm not going to go back
and rewrite the above. ;-)
--
Robert I. Eachus
100% Ada, no bugs--the only way to create software.
next prev parent reply other threads:[~2003-11-26 17:58 UTC|newest]
Thread overview: 109+ messages / expand[flat|nested] mbox.gz Atom feed top
2003-11-25 19:04 Question about OO programming in Ada Ekkehard Morgenstern
2003-11-25 20:17 ` Randy Brukardt
2003-11-26 0:34 ` Ekkehard Morgenstern
2003-11-26 6:17 ` Vinzent 'Gadget' Hoefler
2003-11-26 9:29 ` Dmitry A. Kazakov
2003-11-26 15:54 ` Stephen Leake
2003-11-26 20:07 ` Randy Brukardt
2003-11-26 21:36 ` Stephen Leake
2003-11-26 8:56 ` Peter Hermann
2003-11-25 20:55 ` Martin Krischik
2003-11-26 0:22 ` Ekkehard Morgenstern
2003-11-26 1:00 ` Jeffrey Carter
2003-11-26 16:36 ` Martin Krischik
2003-11-26 18:09 ` Robert I. Eachus
2003-11-27 13:45 ` Jean-Pierre Rosen
2003-11-25 21:48 ` Stephen Leake
2003-11-26 0:01 ` Ekkehard Morgenstern
2003-11-26 1:16 ` Jeffrey Carter
2003-11-26 15:10 ` Georg Bauhaus
2003-11-26 15:48 ` Stephen Leake
2003-11-26 16:24 ` Hyman Rosen
2003-11-26 17:58 ` Robert I. Eachus [this message]
2003-11-27 2:10 ` Ekkehard Morgenstern
2003-11-27 10:15 ` Ludovic Brenta
2003-11-27 18:35 ` Jeffrey Carter
2003-11-28 4:35 ` Hyman Rosen
2003-11-28 7:28 ` Vinzent 'Gadget' Hoefler
2003-11-28 8:46 ` Dale Stanbrough
2003-11-28 10:16 ` Vinzent 'Gadget' Hoefler
2003-12-01 15:57 ` Martin Krischik
2003-12-01 16:47 ` Hyman Rosen
2003-12-03 18:35 ` Martin Krischik
2003-12-01 21:13 ` Jeffrey Carter
2003-12-02 8:47 ` Dmitry A. Kazakov
2003-12-03 9:29 ` Pascal Obry
2003-12-03 11:26 ` Dmitry A. Kazakov
2003-12-03 12:49 ` Ludovic Brenta
2003-12-03 13:41 ` Dmitry A. Kazakov
2003-12-03 14:11 ` Ludovic Brenta
2003-12-03 14:45 ` Dmitry A. Kazakov
2003-12-03 15:44 ` Hyman Rosen
2003-12-03 16:11 ` Dmitry A. Kazakov
2003-12-03 18:20 ` David C. Hoos
[not found] ` <28eb01c3b9ca$25b18870$b101a8c0@sy.com>
2003-12-03 18:35 ` Hyman Rosen
2003-12-03 20:05 ` Randy Brukardt
2003-12-03 20:57 ` Hyman Rosen
2003-12-03 21:16 ` Hyman Rosen
2003-12-03 22:04 ` Pascal Obry
2003-12-03 22:34 ` Hyman Rosen
2003-12-04 1:23 ` Robert I. Eachus
2003-12-04 7:15 ` Hyman Rosen
2003-12-04 17:43 ` Warren W. Gay VE3WWG
2003-12-04 8:55 ` Dmitry A. Kazakov
2003-12-04 19:13 ` Randy Brukardt
2003-12-04 19:29 ` Hyman Rosen
2003-12-04 21:32 ` Robert I. Eachus
2003-12-05 8:43 ` Dmitry A. Kazakov
2003-11-27 22:12 ` Robert I. Eachus
2003-11-28 6:37 ` Simon Wright
2003-11-30 2:51 ` Robert I. Eachus
2003-12-06 7:48 ` Chad Bremmon
2003-12-06 13:33 ` Jeff C,
2003-12-06 22:44 ` Hyman Rosen
2003-12-07 3:02 ` Chad Bremmon
2003-12-07 7:53 ` Hyman Rosen
2003-12-07 15:34 ` James Rogers
2003-12-07 18:30 ` Martin Krischik
2003-12-07 20:25 ` James Rogers
2003-12-08 3:36 ` Hyman Rosen
2003-12-08 4:42 ` Chad Bremmon
2003-12-08 8:42 ` Hyman Rosen
2003-12-08 9:34 ` Dmitry A. Kazakov
2003-12-08 13:25 ` Hyman Rosen
2003-12-08 15:05 ` Dmitry A. Kazakov
2003-12-09 4:38 ` Hyman Rosen
2003-12-09 8:19 ` Dmitry A. Kazakov
2003-12-09 13:29 ` Hyman Rosen
2003-12-09 14:36 ` Dmitry A. Kazakov
2003-12-09 15:05 ` Hyman Rosen
2003-12-09 15:59 ` Dmitry A. Kazakov
2003-12-09 16:41 ` Hyman Rosen
2003-12-10 11:32 ` Dmitry A. Kazakov
2003-12-10 15:27 ` Hyman Rosen
2003-12-10 17:15 ` Dmitry A. Kazakov
2003-12-08 17:55 ` Chad Bremmon
2003-12-08 23:09 ` Hyman Rosen
2003-12-09 8:26 ` Dmitry A. Kazakov
2003-12-08 19:33 ` Martin Krischik
2003-12-09 4:41 ` Hyman Rosen
2003-12-08 17:27 ` Chad Bremmon
2003-12-08 18:44 ` Georg Bauhaus
2003-12-08 19:27 ` Martin Krischik
2003-12-08 19:36 ` Chad Bremmon
2003-12-09 4:43 ` Hyman Rosen
2003-12-08 23:23 ` Hyman Rosen
2003-12-08 19:25 ` Martin Krischik
2003-12-07 21:29 ` Peter C. Chapin
2003-12-08 3:44 ` Hyman Rosen
2003-12-08 3:46 ` Hyman Rosen
2003-12-08 5:54 ` James Rogers
2003-12-08 8:45 ` Hyman Rosen
2003-12-07 17:39 ` Chad Bremmon
2003-12-08 23:39 ` Hyman Rosen
2003-12-09 2:36 ` Chad Bremmon
2003-12-09 4:52 ` Hyman Rosen
2003-12-09 11:24 ` Georg Bauhaus
2003-12-09 18:42 ` Chad Bremmon
2003-12-09 20:11 ` Hyman Rosen
2003-12-08 23:40 ` Hyman Rosen
replies disabled
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox