Re: Proper program structure



Maciej Sobczak wrote:
Consider the following example. It is imaginary, but shows the actual
problem, even though the analogy is not always exact.

package Cars is
type Car (<>) is limited private;
...
private
type Car is limited record
Body : ...
Engine : ...
Gear_Box : ...
Wheels : ...
end record;

Note: in the actual project some of the components need to know about
the others, but the "links" are always acyclic.

To keep the sizes of all packages at a reasonable volume I decided to
define each type in a separate package.
The following shows the history of failures:

1. Some of the components of the car make sense in isolation and can
be reused in other contexts, but some might be tied to the project
with no chance for any reuse. Intuitively I thought they could be
defined in private children of the Cars package (private means that
users of Car will not use them by mistake and children to physically
reflect the hierarchy). This is not possible, because the Cars package
would need to "with" these private children packages and that
introduces cyclic dependencies. Too bad.

See my example below. I have each component as a private child of Cars.
I get around the issue you describe by having Cars.Vehicle as a sibling of the other components, and by using private with. Cars is just a root package that only contains some minimal type declarations that are shared between the various packages.


2. Defining them in non-child packages means that they cannot see the
complete structure of the Car and therefore they cannot be "wired" by
discriminants like here:

-- wrong:
type Car is limited record
Body : Body_Type (Car'Access);
Engine : Engire_Type (Car'Access);
-- and so on

In my example, I use limited withs and private withs to achieve this.
I have a Gear_Box with a discriminant for Car.Vehicle_Type'Access.


3. So they have to be wired individually, but discriminants will still
not work, as sibling components of the same record cannot be used to
constrain each other:

-- wrong:
type Car is limited record
Gear_Box : aliased Gear_Box_Type;
Engine : Engire_Type (Gear_Box'Access);
-- and so on

Not needed, since I have a solution for point 2 above.



4. So they have to be wired individually by some initialization
subprogram, presumably the Car's constructor function (because Car has
a constructor function).
There are two ways of doing it:

4a. By defining some Init procedure for each component type, which
will establish the links. This means that all components are aliased
and the Car's constructor function needs to call Init for each
component and provide proper links to dependent components. I don't
like the idea of having two-phase initialization and 'Unchecked_Access
all over the shop, so this solution just sucks.

4b. By allocating all components dynamically and keeping them by
access values. They can be allocated in the proper order with proper
links to other components provided at the allocation time (there are
no cycles, so there is always some proper order to do this), but then
it does not look like a composition anymore and it is essentially
"Java in Ada".

In other words, all the solutions I can think of are either illegal or
crappy.
Am I over-sensitive or did I really hit a design problem?


In the Construct I have used an extended return to return the vehicle without any heap allocations.

Does the following approach satisfy your needs?

Note: If I had used tagged types, I could also have used the new object prefix notation, which might enhance readability as well.

Brad

-----------------------------------------------------

with Cars.Vehicle;
procedure Main is
My_Car : Cars.Vehicle.Vehicle_Type := Cars.Vehicle.Construct;
begin
null;
end Main;

-----------------------------------------------
package Cars is
pragma Pure;

type Gear_Type is (Park, Neutral, Forward, Backward);
end Cars;

--------------------------------------------

private with Cars.Wheels, Cars.Chassis, Cars.Engine, Cars.Gear_Box;
package Cars.Vehicle is
type Vehicle_Type (<>) is limited private;
procedure Shift_Gears (Car : in out Vehicle_Type; Gear : Gear_Type);

function Construct return Vehicle_Type;

private
use Cars.Wheels, Cars.Chassis, Cars.Engine, Cars.Gear_Box;
type Wheel_Array_Type is array (1 .. 4) of Wheel_Type;
type Vehicle_Type is limited
record
Wheels : Wheel_Array_Type;
Chassis : Chassis_Type;
Engine : Engine_Type;
Gear_Box : Gear_Box_Type (Vehicle_Type'Access);
end record;
end Cars.Vehicle;

-----------------------------------------
package body Cars.Vehicle is

function Construct return Vehicle_Type is
begin
return New_Vehicle : Vehicle_Type do
Gear_Box.Select_Gear (New_Vehicle.Gear_Box, Park);
return;
end return;

end Construct;

procedure Shift_Gears (Car : in out Vehicle_Type; Gear : Gear_Type) is
begin
Cars.Engine.Shift_Gears (Motor => Car.Engine, Gear => Gear);
end Shift_Gears;

end Cars.Vehicle;

-------------------------------------------
private package Cars.Wheels is
type Wheel_Type is private;
private
type Length_In_Inches is new Natural;
subtype Wheel_Diameter_Type is Length_In_Inches range 30 .. 80;
type Wheel_Type is
record
Diameter : Wheel_Diameter_Type := 50;
end record;
end Cars.Wheels;
--------------------------
limited with Cars.Vehicle;
private package Cars.Gear_Box is
type Gear_Box_Type
(Containing_Vehicle : access Cars.Vehicle.Vehicle_Type) is private;

procedure Select_Gear (Gear_Shift : in out Gear_Box_Type; Gear : Gear_Type);
-- Somehow this gets called, presumably by user action.
private
type Gear_Box_Type
(Containing_Vehicle : access Cars.Vehicle.Vehicle_Type) is null record;
end Cars.Gear_Box;
--------------------------
with Cars.Vehicle;
package body Cars.Gear_Box is
procedure Select_Gear (Gear_Shift : in out Gear_Box_Type; Gear : Gear_Type) is
begin
Cars.Vehicle.Shift_Gears
(Car => Gear_Shift.Containing_Vehicle.all, Gear => Gear);
end Select_Gear;
end Cars.Gear_Box;
---------------------------
private package Cars.Engine is
type Engine_Type is private;

procedure Shift_Gears (Motor : in out Engine_Type; Gear : Gear_Type);
private
type Engine_Type is
record
Current_Gear : Gear_Type;
end record;
end Cars.Engine;
---------------------
package body Cars.Engine is

procedure Shift_Gears
(Motor : in out Engine_Type;
Gear : Gear_Type) is
begin
Motor.Current_Gear := Gear;
end Shift_Gears;

end Cars.Engine;
--------------------
private package Cars.Chassis is
type Chassis_Type is private;
private
type Chassis_Type is null record;
end Cars.Chassis;
.



Relevant Pages

  • Re: Proper program structure
    ... the visible spec of the containing car (in your example, ... interest only to its private components. ... package to be Cars.Vehicle_Internal, ... function Construct return Vehicle_Type is ...
    (comp.lang.ada)
  • oo problem help please
    ... type car is tagged private; ... package body car is ... type bus is new transport.transport_rec with private; ...
    (comp.lang.ada)
  • Re: Indirect visibility of private part in child packages
    ... an indirect visibility of a private part. ... This can be reduced to three package ... conversion) should work, and it's a compiler bug if that is rejected. ... even if the inheritance comes ...
    (comp.lang.ada)
  • OO problem: Performing actions on messages (very long, sorry)
    ... I'm new to Ada. ... action shall override a field in a message. ... package M1_Float_Override_Action is new Action.Override ... type Instance is abstract tagged private; ...
    (comp.lang.ada)
  • generic unit : compilation error ! please help me
    ... package Parent is ... type T_Parent is tagged private; ... with package Par is new Parent; ... package body Parent.Test_Unit is ...
    (comp.lang.ada)