comp.lang.ada
 help / color / mirror / Atom feed
* Initialization Params for Controlled Types
@ 1994-10-27  9:44 Angel Alvarez
  1994-10-27 14:27 ` Tucker Taft
  1994-10-27 23:06 ` Robert Dewar
  0 siblings, 2 replies; 13+ messages in thread
From: Angel Alvarez @ 1994-10-27  9:44 UTC (permalink / raw)


Given that Initialization for Controlled types only takes a single
parameter, the Object to initialize, how can one actually pass 
at run-time other data to help initialize the object in one way or
another? ... Passing them as record fields of the object itself?

It is unclear to me how this could be done for Controlled objects  
(by means of default field initialization for the record type itself?) 
and at any rate it seems like this solution might introduce "excess baggage" 
for the whole life of the object, just to have on hand the data 
required at initialization time ... Will anybody please like to comment?

Thank you for your help,
Angel Alvarez





^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: Initialization Params for Controlled Types
  1994-10-27  9:44 Initialization Params for Controlled Types Angel Alvarez
@ 1994-10-27 14:27 ` Tucker Taft
  1994-11-04 13:44   ` John English
  1994-10-27 23:06 ` Robert Dewar
  1 sibling, 1 reply; 13+ messages in thread
From: Tucker Taft @ 1994-10-27 14:27 UTC (permalink / raw)


In article <CyBsE5.GFs@dit.upm.es>, Angel Alvarez <aalvarez@maja> wrote:

>Given that Initialization for Controlled types only takes a single
>parameter, the Object to initialize, how can one actually pass 
>at run-time other data to help initialize the object in one way or
>another? ... Passing them as record fields of the object itself?

The normal way to "construct" an object in a parameterized way in Ada
is with a function.  There is no separate notion
of "constructor" -- functions are used to do construction.
Internally, a function would typically use aggregates and/or
allocators to complete the job of construction.

E.g.:
      X : My_Type := Foo(a, b, c);

This means that "constructors" in Ada are just normal functions,
and their name can give you a clue as to what they are doing
(unlike in some other unnamed OO languages ;-).

Initialize is really for *default* initialization only,
where parameterization (beyond discriminants that control
size/shape) is generally unnecessary.

The one place where a function doesn't work for parameterized
initialization is for an object of a limited type (and even that
is not a problem if you generally use access values to refer
to such objects).  In this case, one way to parameterize the
initialization of a limited object is by discriminants.  
In Ada 9X, discriminants are more general, and can be of
an access type.  This allows you to "pass in" essentially anything
to the (default) initialization (using 'Access if necessary).

For a limited object, it is also generally reasonable
to call an explicit initialization procedure with any number
of parameters.  Again, the primary goal of Initialize is to
provide *default* initialization.  Explicit parameterized 
initialization can be performed in other ways (via functions
or procedures).

>It is unclear to me how this could be done for Controlled objects  
>(by means of default field initialization for the record type itself?) 
>and at any rate it seems like this solution might introduce "excess baggage" 
>for the whole life of the object, just to have on hand the data 
>required at initialization time ... Will anybody please like to comment?

See above.  It is true that for limited objects, the data used
for default initialization needs to be accessible via the discriminants.
But again, remember we are talking about default initialization.
Explicit parameterized initialization can be accomplished using 
functions and/or procedures.

>Thank you for your help,
>Angel Alvarez

S. Tucker Taft    stt@inmet.com
Ada 9X Mapping/Revision Team
Intermetrics, Inc.
Cambridge, MA  02138

P.S. Even if the size of the object being constructed is large,
there is no need for a "constructor" concept distinct from functions.
Functions are generally just as efficient in decent implementations.

In most implementations, if a function returns a large object, the 
caller passes in the address of a location where to put the result.  
When used in an initialization context, this location can be the 
target object itself (since the object doesn't exist yet, there is no harm
in using it as the "temp" in which to put the return value).

Furthermore, it has become quite common for compilers to implement
a "return" optimization where if all return statements return
the same local variable, then that "local" variable actually resides
in the return "temp" allocated by the caller.  The net effect
is that the function "constructs" the result directly in its final
resting place, and no extra copying (or "adjusting") is performed.
-T



^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: Initialization Params for Controlled Types
  1994-10-27  9:44 Initialization Params for Controlled Types Angel Alvarez
  1994-10-27 14:27 ` Tucker Taft
@ 1994-10-27 23:06 ` Robert Dewar
  1 sibling, 0 replies; 13+ messages in thread
From: Robert Dewar @ 1994-10-27 23:06 UTC (permalink / raw)


to pass parameters to initialization routines, use discriminants, works
nicely, that's how we do storage pools in GNAT.




^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: Initialization Params for Controlled Types
  1994-10-27 14:27 ` Tucker Taft
@ 1994-11-04 13:44   ` John English
  1994-11-04 22:16     ` Norman H. Cohen
  0 siblings, 1 reply; 13+ messages in thread
From: John English @ 1994-11-04 13:44 UTC (permalink / raw)


Tucker Taft (stt@spock.camb.inmet.com) wrote:
: For a limited object, it is also generally reasonable
: to call an explicit initialization procedure with any number
: of parameters.  Again, the primary goal of Initialize is to
: provide *default* initialization.  Explicit parameterized 
: initialization can be performed in other ways (via functions
: or procedures).

There is one subtle danger with using procedures as "constructors".
Imagine the following:
  type BankAccount is tagged private;
  procedure Open (Account : in out BankAccount;
		  Name    : in String);

Here Open opens a bank account in a particular name.  Now if you
derive a new type CurrentAccount from BankAccount to produce a
BankAccount that allows overdrafts, you might have this:
  type CurrentAccount is new BankAccount with private;
  procedure Open (Account   : in out CurrentAccount;
		  Name      : in String;
		  Overdraft : in Money);

Here's where the danger is: the first procedure (Open for BankAccount)
is a primitive of BankAccount and will be inherited by CurrentAccount.
It's therefore possible to initialise a CurrentAccount as if it were
a BankAccount, which might leave the overdraft limit uninitialised:
  C : CurrentAccount;
  ...
  Open (C, "Fred Bloggs");  -- oops!

Okay, you can provide a default overdraft limit of 0.00, so this is
not an entirely convincing example; the point is that there is a
danger here which is non-obvious, since you might forget that there
is a second Open for CurrentAccounts which is provided due to inheritance.
C++ sidesteps this problem by making constructors non-inheritable.
The only way to accomplish this in 9X is to make sure that Open isn't
a primitive (e.g. by shoving it in a child package) which is fairly
painful.

-- 
-------------------------------------------------------------------------------
John English                    | Thoughts for the day:
Dept. of Computing              | - People who live in windowed environments
University of Brighton          |   shouldn't cast pointers
E-mail: je@brighton.ac.uk       | - In C++ only your friends can access your
Fax:    0273 642405             |   private parts
-------------------------------------------------------------------------------



^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: Initialization Params for Controlled Types
  1994-11-04 13:44   ` John English
@ 1994-11-04 22:16     ` Norman H. Cohen
  1994-11-05 15:01       ` Cyrille Comar
                         ` (2 more replies)
  0 siblings, 3 replies; 13+ messages in thread
From: Norman H. Cohen @ 1994-11-04 22:16 UTC (permalink / raw)


In article <1994Nov4.134412.10010@unix.brighton.ac.uk>,
je@unix.brighton.ac.uk (John English) writes: 

|> There is one subtle danger with using procedures as "constructors".
|> Imagine the following: 
|>   type BankAccount is tagged private;
|>   procedure Open (Account : in out BankAccount;
|>                Name    : in String);
|>
|> Here Open opens a bank account in a particular name.  Now if you
|> derive a new type CurrentAccount from BankAccount to produce a
|> BankAccount that allows overdrafts, you might have this: 
|>   type CurrentAccount is new BankAccount with private;
|>   procedure Open (Account   : in out CurrentAccount;
|>                Name      : in String;
|>                Overdraft : in Money);
|>
|> Here's where the danger is: the first procedure (Open for BankAccount)
|> is a primitive of BankAccount and will be inherited by CurrentAccount.
|> It's therefore possible to initialise a CurrentAccount as if it were
|> a BankAccount, which might leave the overdraft limit uninitialised: 
|>   C : CurrentAccount;
|>   ...
|>   Open (C, "Fred Bloggs");  -- oops!
|>
|> Okay, you can provide a default overdraft limit of 0.00, so this is
|> not an entirely convincing example; the point is that there is a
|> danger here which is non-obvious, since you might forget that there
|> is a second Open for CurrentAccounts which is provided due to inheritance.

There is a good reason the example is not convincing: It violates the
principle that the inheritance hierarchy should reflect an IS-A
relationship.  CurrentAccount should be derived from BankAccount only if
a "current account" is a special kind of "bank account".  If it is, then
it should have all the properties of bank accounts--including the ability
to open it by specifying just a name.  After all, when you add
CurrentAccount to the class rooted at BankAccount, you are asserting that
all the primitive operations of BankAccount can be applied to
BankAccount'Class objects with any tag.

You can look at this as a problem with the operation Open or a problem
with the hierarchy.  If you view it as a problem with the hierarchy, a
solution is to introduce a new abstract class, say Universal_Account,
that declares the operations that truly are meaningful for ALL bank
accounts, and derive BankAccount and CurrentAccount from
Universal_Account.  Open is not one of these operations.  Two distinct
versions of Open would be declared for the two derived types.

|> C++ sidesteps this problem by making constructors non-inheritable.
|> The only way to accomplish this in 9X is to make sure that Open isn't
|> a primitive (e.g. by shoving it in a child package) which is fairly
|> painful.

Note that the danger of an inadvertent error could be avoided in Ada 9X
by making the constructor a function returning a BankAccount value: 

   function New_Bank_Account (Name: String) return BankAccount;

Then the version inherited by CurrentAccount would be abstract.  Calls on
the inherited version would be illegal, thus alerting the programmer to
the fact that something is amiss.

--
Norman H. Cohen    ncohen@watson.ibm.com



^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: Initialization Params for Controlled Types
  1994-11-04 22:16     ` Norman H. Cohen
@ 1994-11-05 15:01       ` Cyrille Comar
  1994-11-07 10:32         ` John English
  1994-11-07  9:08       ` John English
  1994-11-10 15:23       ` John English
  2 siblings, 1 reply; 13+ messages in thread
From: Cyrille Comar @ 1994-11-05 15:01 UTC (permalink / raw)


ncohen@watson.ibm.com (Norman H. Cohen) writes:
: In article <1994Nov4.134412.10010@unix.brighton.ac.uk>,
: je@unix.brighton.ac.uk (John English) writes: 
: 
: |> There is one subtle danger with using procedures as "constructors".
: |> Imagine the following: 
: |>   type BankAccount is tagged private;
: |>   procedure Open (Account : in out BankAccount;
: |>                Name    : in String);
: |>
: |> Here Open opens a bank account in a particular name.  Now if you
: |> derive a new type CurrentAccount from BankAccount to produce a
: |> BankAccount that allows overdrafts, you might have this: 
: |>   type CurrentAccount is new BankAccount with private;
: |>   procedure Open (Account   : in out CurrentAccount;
: |>                Name      : in String;
: |>                Overdraft : in Money);
: |>
: |> Here's where the danger is: the first procedure (Open for BankAccount)
: |> is a primitive of BankAccount and will be inherited by CurrentAccount.
: |> It's therefore possible to initialise a CurrentAccount as if it were
: |> a BankAccount, which might leave the overdraft limit uninitialised: 
: |>   C : CurrentAccount;
: |>   ...
: |>   Open (C, "Fred Bloggs");  -- oops!
: |>
: |> Okay, you can provide a default overdraft limit of 0.00, so this is
: |> ...
: |> C++ sidesteps this problem by making constructors non-inheritable.
: |> The only way to accomplish this in 9X is to make sure that Open isn't
: |> a primitive (e.g. by shoving it in a child package) which is fairly
: |> painful.

you have an exact equivalent in Ada. If you don't want to make Open a primitive
operation of BankAccount define it as a classwide operation:

  procedure Open (Account : in out BankAccount'Class;
                  Name    : in String);

It will not be inherited! I do not say that it is the perfect solution for your
problem, my point is that it is exactly equivalent to defining Open as a
non-virtual member function in C++.

-- 
------------------------------------------------------------------------
Cyrille Comar,                                  E-mail: comar@cs.nyu.edu
Gnat Project                                    US phone: (212) 998-3489




^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: Initialization Params for Controlled Types
  1994-11-04 22:16     ` Norman H. Cohen
  1994-11-05 15:01       ` Cyrille Comar
@ 1994-11-07  9:08       ` John English
  1994-11-10 15:23       ` John English
  2 siblings, 0 replies; 13+ messages in thread
From: John English @ 1994-11-07  9:08 UTC (permalink / raw)


Norman H. Cohen (ncohen@watson.ibm.com) wrote:
: In article <1994Nov4.134412.10010@unix.brighton.ac.uk>,
: je@unix.brighton.ac.uk (John English) writes: 

: |> C++ sidesteps this problem by making constructors non-inheritable.
: |> The only way to accomplish this in 9X is to make sure that Open isn't
: |> a primitive (e.g. by shoving it in a child package) which is fairly
: |> painful.

: Note that the danger of an inadvertent error could be avoided in Ada 9X
: by making the constructor a function returning a BankAccount value: 

:    function New_Bank_Account (Name: String) return BankAccount;

Yes, I should have declared it "type BankAccount is tagged limited private"
since I assume that copying a bank account shouldn't be allowed -- my mistake.
If this is so, you can't use a function for initialisation.

-- 
-------------------------------------------------------------------------------
John English                    | Thoughts for the day:
Dept. of Computing              | - People who live in windowed environments
University of Brighton          |   shouldn't cast pointers
E-mail: je@brighton.ac.uk       | - In C++ only your friends can access your
Fax:    0273 642405             |   private parts
-------------------------------------------------------------------------------



^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: Initialization Params for Controlled Types
  1994-11-05 15:01       ` Cyrille Comar
@ 1994-11-07 10:32         ` John English
  0 siblings, 0 replies; 13+ messages in thread
From: John English @ 1994-11-07 10:32 UTC (permalink / raw)


Cyrille Comar (comar@cs.nyu.edu) wrote:

:   procedure Open (Account : in out BankAccount'Class;
:                   Name    : in String);

: It will not be inherited! I do not say that it is the perfect solution for your
: problem, my point is that it is exactly equivalent to defining Open as a
: non-virtual member function in C++.

But surely Open can still be applied to a CurrentAccount (which is in
BankAccount'Class) so this doesn't actually help much.

-- 
-------------------------------------------------------------------------------
John English                    | Thoughts for the day:
Dept. of Computing              | - People who live in windowed environments
University of Brighton          |   shouldn't cast pointers
E-mail: je@brighton.ac.uk       | - In C++ only your friends can access your
Fax:    0273 642405             |   private parts
-------------------------------------------------------------------------------



^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: Initialization Params for Controlled Types
  1994-11-04 22:16     ` Norman H. Cohen
  1994-11-05 15:01       ` Cyrille Comar
  1994-11-07  9:08       ` John English
@ 1994-11-10 15:23       ` John English
  1994-11-11 10:44         ` Robb Nebbe
  1994-11-14 21:19         ` Norman H. Cohen
  2 siblings, 2 replies; 13+ messages in thread
From: John English @ 1994-11-10 15:23 UTC (permalink / raw)


Norman H. Cohen (ncohen@watson.ibm.com) wrote:
: There is a good reason the example is not convincing: It violates the
: principle that the inheritance hierarchy should reflect an IS-A
: relationship.  CurrentAccount should be derived from BankAccount only if
: a "current account" is a special kind of "bank account".  If it is, then
: it should have all the properties of bank accounts--including the ability
: to open it by specifying just a name.  After all, when you add
: CurrentAccount to the class rooted at BankAccount, you are asserting that
: all the primitive operations of BankAccount can be applied to
: BankAccount'Class objects with any tag.

I've had to think about this one for a while -- I didn't find it convincing
but couldn't figure out why.  The idea is that yes, a current account is a
kind of bank account.  BankAccount is an "idealised" bank account -- no
transaction will be refused, no charges will be made, no interest is payable.
Classes like CurrentAccount can then extend this into something more
realistic and so can SavingsAccount etc.  All these account types will
have the common properties inherited from BankAccount (deposit, withdraw,
check balance, print statement...)

However, extending a class involves adding extra members to it and these
must be initialised somehow.  If it isn't possible to do this with a simple
default in the type declaration, you have to call an initialisation routine.
The question is really whether Open should be a primitive.  Each different
derivation of BankAccount will have been extended in a different way and
so will require different initialisation, using different parameters.  It's
quite hard to prevent Open being a primitive (it needs declaring elewhere,
so you have to provide an elsewhere for it) but it's incorrect to consider
it as an operation which can be applied to any BankAccount'Class since it
is responsible for class-specific initialisation.

: You can look at this as a problem with the operation Open or a problem
: with the hierarchy.  If you view it as a problem with the hierarchy, a
: solution is to introduce a new abstract class, say Universal_Account,
: that declares the operations that truly are meaningful for ALL bank
: accounts, and derive BankAccount and CurrentAccount from
: Universal_Account.  Open is not one of these operations.  Two distinct
: versions of Open would be declared for the two derived types.

Changing the hierarchy only postpones the problem: Open will end up as
a primitive of CurrentAccount, and any derivation from CurrentAccount
will therefore inherit it.  Open is indeed not one of those universally
sensible operations, so it needs to be provided as a non-primitive, and
it needs to be provided anew for every derivation.  And each new version
will have to call its parent's version of Open to do the parent-part
specific initialisation.  This is where it gets messy and error-prone.

And of course, there is no way in 9X to force Open to be called and you
have to take extra care (e.g. an Initialised flag) to prevent it being
called more than once.  C++ constructors are a neat solution to these
problems -- they are always called exactly once per object, they are
not inherited, they automatically call the base class constructor, and
they can have parameters.  Ada 9X requires a lot more effort to avoid
subtle errors IMHO.

-- 
-------------------------------------------------------------------------------
John English                    | Thoughts for the day:
Dept. of Computing              | - People who live in windowed environments
University of Brighton          |   shouldn't cast pointers
E-mail: je@brighton.ac.uk       | - In C++ only your friends can access your
Fax:    0273 642405             |   private parts
-------------------------------------------------------------------------------



^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: Initialization Params for Controlled Types
  1994-11-10 15:23       ` John English
@ 1994-11-11 10:44         ` Robb Nebbe
  1994-11-14 21:19         ` Norman H. Cohen
  1 sibling, 0 replies; 13+ messages in thread
From: Robb Nebbe @ 1994-11-11 10:44 UTC (permalink / raw)


In article <1994Nov10.152352.27015@unix.brighton.ac.uk>, je@unix.brighton.ac.uk (John English) writes:
|> Norman H. Cohen (ncohen@watson.ibm.com) wrote:
|> : There is a good reason the example is not convincing: It violates the
|> : principle that the inheritance hierarchy should reflect an IS-A
|> : relationship.  CurrentAccount should be derived from BankAccount only if
|> : a "current account" is a special kind of "bank account".  If it is, then
|> : it should have all the properties of bank accounts--including the ability
|> : to open it by specifying just a name.  After all, when you add
|> : CurrentAccount to the class rooted at BankAccount, you are asserting that
|> : all the primitive operations of BankAccount can be applied to
|> : BankAccount'Class objects with any tag.
|> 
|> I've had to think about this one for a while -- I didn't find it convincing
|> but couldn't figure out why.  The idea is that yes, a current account is a
|> kind of bank account.  BankAccount is an "idealised" bank account -- no
|> transaction will be refused, no charges will be made, no interest is payable.
|> Classes like CurrentAccount can then extend this into something more
|> realistic and so can SavingsAccount etc.  All these account types will
|> have the common properties inherited from BankAccount (deposit, withdraw,
|> check balance, print statement...)

If it is an "idealized" bank account, i.e. it describes what all bank
accounts have in common then it should be abstract and operations like
open should also be abstract. A similar example is mamals. A mamal is
an idealization of the commonality between a certain number of animals
but there is no one animal that is "just" a mamal; they are always
something more.

You need the right tool for the job. Just because you can come up with
a solution in C++ using constructors does not mean it is a good solution.
In fact I would program the solution in C++ much the same way I would
in Ada. Pounding a nail in with a screwdriver is not a good solution;
just because C++ happens to include a really big screwdriver thus making
it feasible to pound a nail in with it doesn't change the fact that it
would still be easier to use the hammer (which C++ also provides).

|> And of course, there is no way in 9X to force Open to be called and you
|> have to take extra care (e.g. an Initialised flag) to prevent it being
|> called more than once.  C++ constructors are a neat solution to these
|> problems -- they are always called exactly once per object, they are
|> not inherited, they automatically call the base class constructor, and
|> they can have parameters.  Ada 9X requires a lot more effort to avoid
|> subtle errors IMHO.

Actually all Ada 9X requires is a better design. The actual account type
probably shouldn't even be visible. What should be visible is a type
Account_Number which is not limited private that refers to a real account.
This more closely models the real world and resolves any of the problems
I have thought of.

Maybe you see some that I haven't?

- Robb Nebbe



^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: Initialization Params for Controlled Types
  1994-11-14 21:19         ` Norman H. Cohen
@ 1994-11-14 18:35           ` Robert I. Eachus
  1994-11-16 21:45           ` Matt Kennel
  1 sibling, 0 replies; 13+ messages in thread
From: Robert I. Eachus @ 1994-11-14 18:35 UTC (permalink / raw)



   Norm said something which is true, but accidently misleading: 

  > As others have pointed out, a function with a classwide result subtype,

  >    function Open_Current_Account (...) return Account'Class;

  > will not be inherited.  However, calls on functions are not intrinsically
  > associated with objects.  Your remark about exactly one call on open per
  > object sounds like an initialization PROCEDURE.  This can be provided by
  > controlled types...

    The more natural way to write this:

      function Open_Current_Account (...) return Account;

    ...will be inherited as an abstract operation which must be
explicitly overridden.  However, this is also almost (but not quite in
Ada 9X) useless for most types of objects, including accounts, where
objects should not be assignable.  Better would be:

      type Account is abstract tagged limited private;

      type Account_Kind is String(1..8); -- allow extension.

      type Account_Ref is access Account'CLASS;
      ...
      function Open_Account (Kind: Account_Kind;...) return Account_Ref;

      -- and probably in a child package:

      type Checking_Account is new Account with private;
      ...


     Now the operation Open_Account will be derived if you
derive a type from Account_Ref for some reason, but it will need a
to explicitly dispatch for each type of account:

    ...
    if Kind = "CHECKING" then return Open_Checking_Account(...);

    If you are clever and lazy ;-), you will define other operations
on accounts--which may or may not be an abstract type, as procedures
or functions with an operand of type Account.  If, these are intended
as operations on all Account types, the templates (abstract or
otherwise) should be defined in the same package as Account, so they
are inherited, potentially dispatching, operations.

    Now in the parent package add, for instance:

    function Balance(AR: Account_Ref) return Money;
    pragma Inline(Balance);

    with the full definition:

    function Balance(AR: Account_Ref) return Money is
    begin return Balance(AR.all); end Balance;
    -- This function raises Constraint_Error not Program_Error if the
    -- pointer is null...

    And with a good optimizing compiler all the magic dispatching glue
will disappear during inlining.

    Where does this leave us with respect to the original "problem"?
The code naturally works the way it is supposed to.  Account type is
either a parameter to the open function or is implicit in the name,
you can chose the one that is appropriate.  Non-constructor functions
and procedures are derived and dispatching unless overridden, and the
distinction between objects and their names only matters where it
matters, possibly only when creating an account.

--

					Robert I. Eachus

with Standard_Disclaimer;
use  Standard_Disclaimer;
function Message (Text: in Clever_Ideas) return Better_Ideas is...



^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: Initialization Params for Controlled Types
  1994-11-10 15:23       ` John English
  1994-11-11 10:44         ` Robb Nebbe
@ 1994-11-14 21:19         ` Norman H. Cohen
  1994-11-14 18:35           ` Robert I. Eachus
  1994-11-16 21:45           ` Matt Kennel
  1 sibling, 2 replies; 13+ messages in thread
From: Norman H. Cohen @ 1994-11-14 21:19 UTC (permalink / raw)


In article <1994Nov10.152352.27015@unix.brighton.ac.uk>,
je@unix.brighton.ac.uk (John English) writes: 

|> Norman H. Cohen (ncohen@watson.ibm.com) wrote: 
...
|> : You can look at this as a problem with the operation Open or a problem
|> : with the hierarchy.  If you view it as a problem with the hierarchy, a
|> : solution is to introduce a new abstract class, say Universal_Account,
|> : that declares the operations that truly are meaningful for ALL bank
|> : accounts, and derive BankAccount and CurrentAccount from
|> : Universal_Account.  Open is not one of these operations.  Two distinct
|> : versions of Open would be declared for the two derived types.
|>
|> Changing the hierarchy only postpones the problem: Open will end up as
|> a primitive of CurrentAccount, and any derivation from CurrentAccount
|> will therefore inherit it.  Open is indeed not one of those universally
|> sensible operations, so it needs to be provided as a non-primitive, and
|> it needs to be provided anew for every derivation.  And each new version
|> will have to call its parent's version of Open to do the parent-part
|> specific initialisation.  This is where it gets messy and error-prone.

This is a more general problem that arises with OOP in any language: 
Often the need arises to define a new class B that has MOST, BUT NOT ALL
of the features of some other class A.  The problem is that the author of
A, not having been omnisicient, failed to recognize it as a special case
of a more general problem for which other special cases would arise in
the future.  If you don't have the authority to modify the definition of
class A, you end up duplicating a lot of code.  If you do have such
authority, you can FACTOR the common features of A and B into a new class
C, redefine A as a specialization of C, and define B as another
specialization of C.

In your example, CurrentAccount is A and the unanticipated derivation
from CurrentAccount that you hypothesize is B.  (Had the derivation been
anticipated, an abstract class playing the role of C--having all the
features of CurrentAccount meaningful even for specializations of current
accounts, but not features such as Open--would have been written in the
first place, and a concrete class would have been defined by deriving
from this abstract class and adding an appropriate Open operation.

|> And of course, there is no way in 9X to force Open to be called and you
|> have to take extra care (e.g. an Initialised flag) to prevent it being
|> called more than once.  C++ constructors are a neat solution to these
|> problems -- they are always called exactly once per object, they are
|> not inherited, they automatically call the base class constructor, and
|> they can have parameters.  Ada 9X requires a lot more effort to avoid
|> subtle errors IMHO.

I'm confused now about whether you're envisioning Open as a function or a
procedure.  (It's been a while and I don't have the full example at
hand.)  As others have pointed out, a function with a classwide result
subtype,

   function Open_Current_Account (...) return Account'Class;

will not be inherited.  However, calls on functions are not intrinsically
associated with objects.  Your remark about exactly one call on open per
object sounds like an initialization PROCEDURE.  This can be provided by
controlled types.  Like C++ constructors, the Initialize procedure of a
controlled type is called once per object, and can be parameterized using
discriminants.  By default, a C++ constructor is not inherited, but a
default constructor that implicitly calls the base-class constructor is
implicitly created, which amounts to pretty much the same thing.  In Ada
9X the parent's Initialize procedure is not implicitly invoked by the
derived type's Initialize procedure, but it is straightforward to make an
explicit call.

--
Norman H. Cohen    ncohen@watson.ibm.com



^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: Initialization Params for Controlled Types
  1994-11-14 21:19         ` Norman H. Cohen
  1994-11-14 18:35           ` Robert I. Eachus
@ 1994-11-16 21:45           ` Matt Kennel
  1 sibling, 0 replies; 13+ messages in thread
From: Matt Kennel @ 1994-11-16 21:45 UTC (permalink / raw)


Norman H. Cohen (ncohen@watson.ibm.com) wrote:

: This is a more general problem that arises with OOP in any language: 
: Often the need arises to define a new class B that has MOST, BUT NOT ALL
: of the features of some other class A.  The problem is that the author of
: A, not having been omnisicient, failed to recognize it as a special case
: of a more general problem for which other special cases would arise in
: the future.  If you don't have the authority to modify the definition of
: class A, you end up duplicating a lot of code.  If you do have such
: authority, you can FACTOR the common features of A and B into a new class
: C, redefine A as a specialization of C, and define B as another
: specialization of C.

If you separate subtyping from implementation inheritance there
is no problem doing what you want.

In B, you inherit just some of the implementation of A.  This is not a problem
because by doing so you do not promise to uphold B as a subtype of A.

You then write an abstract class "C" that fits over A and B (with
no implementation) that abstracts the commonality.  A and B are
both subtypes of C, but neither is a subtype of each other.

: --
: Norman H. Cohen    ncohen@watson.ibm.com

--
-Matt Kennel  		mbk@inls1.ucsd.edu
-Institute for Nonlinear Science, University of California, San Diego
-*** AD: Archive for nonlinear dynamics papers & programs: FTP to
-***     lyapunov.ucsd.edu, username "anonymous".



^ permalink raw reply	[flat|nested] 13+ messages in thread

end of thread, other threads:[~1994-11-16 21:45 UTC | newest]

Thread overview: 13+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
1994-10-27  9:44 Initialization Params for Controlled Types Angel Alvarez
1994-10-27 14:27 ` Tucker Taft
1994-11-04 13:44   ` John English
1994-11-04 22:16     ` Norman H. Cohen
1994-11-05 15:01       ` Cyrille Comar
1994-11-07 10:32         ` John English
1994-11-07  9:08       ` John English
1994-11-10 15:23       ` John English
1994-11-11 10:44         ` Robb Nebbe
1994-11-14 21:19         ` Norman H. Cohen
1994-11-14 18:35           ` Robert I. Eachus
1994-11-16 21:45           ` Matt Kennel
1994-10-27 23:06 ` Robert Dewar

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox