comp.lang.ada
 help / color / mirror / Atom feed
* Proper program structure
@ 2009-09-30 21:25 Maciej Sobczak
  2009-09-30 22:16 ` Robert A Duff
                   ` (3 more replies)
  0 siblings, 4 replies; 13+ messages in thread
From: Maciej Sobczak @ 2009-09-30 21:25 UTC (permalink / raw)


First, thank you all for the replies to my previous questions.
But I have more. :-)

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.

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

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

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?

--
Maciej Sobczak * www.msobczak.com * www.inspirel.com

Database Access Library for Ada: www.inspirel.com/soci-ada



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

* Re: Proper program structure
  2009-09-30 21:25 Proper program structure Maciej Sobczak
@ 2009-09-30 22:16 ` Robert A Duff
  2009-10-01  7:13   ` Maciej Sobczak
  2009-09-30 23:43 ` Adam Beneschan
                   ` (2 subsequent siblings)
  3 siblings, 1 reply; 13+ messages in thread
From: Robert A Duff @ 2009-09-30 22:16 UTC (permalink / raw)


Maciej Sobczak <see.my.homepage@gmail.com> writes:

> 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?

I think your design problem can probably be solved by "limited with".

- Bob



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

* Re: Proper program structure
  2009-09-30 21:25 Proper program structure Maciej Sobczak
  2009-09-30 22:16 ` Robert A Duff
@ 2009-09-30 23:43 ` Adam Beneschan
  2009-10-01  7:35   ` Maciej Sobczak
  2009-10-01  6:34 ` Brad Moore
  2009-10-01  8:08 ` Dmitry A. Kazakov
  3 siblings, 1 reply; 13+ messages in thread
From: Adam Beneschan @ 2009-09-30 23:43 UTC (permalink / raw)


On Sep 30, 2:25 pm, Maciej Sobczak <see.my.homep...@gmail.com> wrote:
> 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
>
> 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

Just for the record, I've had occasion in the past to believe that
this should be allowed:

   type Car is limited record
      Gear_Box : aliased Gear_Box_Type;
      Engine   : Engine_Type (Car.Gear_Box'Access);
   ...

The syntax is slightly different from what you're looking for, but
it's more consistent with the current Ada rules since I think there's
nowhere else in the language (besides record rep clauses) where a
record component can be reference without some sort of prefix
selector, but there's already a facility for self-references to
types.  The only reason this is illegal is because of 3.8(13), which
limits the use of a type self-reference in a constraint to T'Access or
T'Unchecked_Access, which must appear by itself.  This limitation is
partly because allowing certain other uses of type self-references
would cause problems, and partly to avoid some unnecessary
complexities in compilers if arbitrary expressions were allowed.  But
I think this could be relaxed a bit to allow T.component
[.component...]'Access, with some restrictions.  As long as this were
restricted so that the 'Access value would always be a constant offset
from the beginning of the record in a typical compiler, I don't think
this would add much complexity, and it would solve some problems, such
as yours (I presume---I haven't studied it too closely), and a program
I've seen earlier which would have been more straightforward if this
feature were allowed but instead had to resort to something a bit
tricky.  Anyway, maybe this will motivate me to take a second look at
that earlier program and perhaps submit a proposal for a new language
feature.

Offhand, I think the attribute may have to be 'Unchecked_Access to get
around accessibility level rules, but I don't recall.

                                            -- Adam



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

* Re: Proper program structure
  2009-09-30 21:25 Proper program structure Maciej Sobczak
  2009-09-30 22:16 ` Robert A Duff
  2009-09-30 23:43 ` Adam Beneschan
@ 2009-10-01  6:34 ` Brad Moore
  2009-10-01  7:44   ` Maciej Sobczak
  2009-10-01  9:39   ` Maciej Sobczak
  2009-10-01  8:08 ` Dmitry A. Kazakov
  3 siblings, 2 replies; 13+ messages in thread
From: Brad Moore @ 2009-10-01  6:34 UTC (permalink / raw)


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;



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

* Re: Proper program structure
  2009-09-30 22:16 ` Robert A Duff
@ 2009-10-01  7:13   ` Maciej Sobczak
  0 siblings, 0 replies; 13+ messages in thread
From: Maciej Sobczak @ 2009-10-01  7:13 UTC (permalink / raw)


On 1 Paź, 00:16, Robert A Duff <bobd...@shell01.TheWorld.com> wrote:

> I think your design problem can probably be solved by "limited with".

Which package should be "limited withed" by which one?
If you mean that Cars should "limited with" its components, then we
are at 4b. anyway, which means tha components should be allocated
dynamically ("Java in Ada").

--
Maciej Sobczak * www.msobczak.com * www.inspirel.com

Database Access Library for Ada: www.inspirel.com/soci-ada



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

* Re: Proper program structure
  2009-09-30 23:43 ` Adam Beneschan
@ 2009-10-01  7:35   ` Maciej Sobczak
  0 siblings, 0 replies; 13+ messages in thread
From: Maciej Sobczak @ 2009-10-01  7:35 UTC (permalink / raw)


On 1 Paź, 01:43, Adam Beneschan <a...@irvine.com> wrote:

> Just for the record, I've had occasion in the past to believe that
> this should be allowed:
>
>    type Car is limited record
>       Gear_Box : aliased Gear_Box_Type;
>       Engine   : Engine_Type (Car.Gear_Box'Access);
>    ...

Yes, that was one of my ideas.

> Anyway, maybe this will motivate me to take a second look at
> that earlier program and perhaps submit a proposal for a new language
> feature.

If we are at potential language proposals, then my problem would be
also solved with some conceptual combination of limited with and
private child packages.

Imagine that Car's components are defined in their private child
packages (Car.Engine, Car.Gear_Box, etc.). These packages are withed
by Car. Now, to avoid the cyclic dependency between package and its
children, I would need to somehow express that the specification of
the child package does not need to see the parent anymore than on the
"limited" basis, whereas the body of the child package would still
have a choice to see the parent spec completely.
To be more exact:

with Cars.Engine;   -- not possible today
with Cars.Gear_Box;
package Cars is
   ...
private
   type Car is record
      Engine : Engine_Type (Car'Access);
      Gear_Box : Gear_Box_Type (Car'Access);
   end record;
end Cars;

-- now the new magic child spec
limited private package Cars.Engine is
   type Engine_Type (C : access Car) is ...
end Cars.Engine;

-- and the new magic child body
with Cars;  -- necessary for a complete view of Cars
with Cars.Gear_Box;
package body Cars.Engine is
   procedure Run (E : in out Engine_Type) is
   begin
      E.C.all.Gear_Box.Move_This_As_Well;
   end Run;
end Cars.Engine;

Note:

1. parent package Cars withs its child package Engine, so that it can
use Engine_Type as component of the Car

2. specification of child package Cars.Engine does not see anything
more than a limited view on its parent's types, so it can use access
to Car

3. body of child package Cars.Engine can see complete parent spec,
because it withs Cars explicitly

Technically it should be possible to compile everything without
cycles.

The whole magic comes down to the additional form of childhood, where
the child does not fully depend on the parent. In other words, the
same trick that is used in Ada2005 to cut cyclic dependencies between
peers (limited with) could be also used to cut cyclic dependencies
between parents and their children.

That would be awesome and would allow me to write Ada in Ada.

--
Maciej Sobczak * www.msobczak.com * www.inspirel.com

Database Access Library for Ada: www.inspirel.com/soci-ada



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

* Re: Proper program structure
  2009-10-01  6:34 ` Brad Moore
@ 2009-10-01  7:44   ` Maciej Sobczak
  2009-10-01  9:39   ` Maciej Sobczak
  1 sibling, 0 replies; 13+ messages in thread
From: Maciej Sobczak @ 2009-10-01  7:44 UTC (permalink / raw)


On 1 Paź, 08:34, Brad Moore <brad.mo...@shaw.ca> wrote:

> 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.

Very nice. Yes, I have missed this option.
In my system the packages would have a different structure - Cars is
not a root anyway, but everything is in the hierarchy rooted in (let's
say) Transport. This allows me to have Cars without any artificial
child Vehicle package, and all components are in private package
Transport.Components.

Thank you for this suggestion.

--
Maciej Sobczak * www.msobczak.com * www.inspirel.com

Database Access Library for Ada: www.inspirel.com/soci-ada



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

* Re: Proper program structure
  2009-09-30 21:25 Proper program structure Maciej Sobczak
                   ` (2 preceding siblings ...)
  2009-10-01  6:34 ` Brad Moore
@ 2009-10-01  8:08 ` Dmitry A. Kazakov
  3 siblings, 0 replies; 13+ messages in thread
From: Dmitry A. Kazakov @ 2009-10-01  8:08 UTC (permalink / raw)


On Wed, 30 Sep 2009 14:25:57 -0700 (PDT), Maciej Sobczak wrote:

> 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

They could with MI

   type Engine_Interface is limited interface;
---
   type Gear_Box_Interface is limited interface;
---
   type Engine_Type
      (  Gear_Box : not null access Gear_Box_Interface'Class
      )  is new Engine_Interface with ...
---
   type Gear_Box_Type is new Engine_Interface with ...
---

   type Car is
      new Ada.Finalization.Limited_Controlled
      and Engine_Interface
      and Gear_Box_Interface with private;

   private
      type Car is
         new Ada.Finalization.Limited_Controlled
         and Engine_Interface
         and Gear_Box_Interface with
      record
         Engine   : Engine_Type (Car'Access);
         Gear_Box : Gear_Box_Type;
      end record;

> Am I over-sensitive or did I really hit a design problem?

Yep, any design will ultimately collapse into dynamically typed mess on
further constraints like the engines of the class E_123'Class require gear
boxes from the class G_M25_X'Class.

-- 
Regards,
Dmitry A. Kazakov
http://www.dmitry-kazakov.de



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

* Re: Proper program structure
  2009-10-01  6:34 ` Brad Moore
  2009-10-01  7:44   ` Maciej Sobczak
@ 2009-10-01  9:39   ` Maciej Sobczak
  2009-10-01 15:36     ` Brad Moore
  1 sibling, 1 reply; 13+ messages in thread
From: Maciej Sobczak @ 2009-10-01  9:39 UTC (permalink / raw)


On 1 Paź, 08:34, Brad Moore <brad.mo...@shaw.ca> wrote:

> See my example below. I have each component as a private child of Cars.

Well, I was too quick to reply.
Your example does not do what I need. It allows the components to see
the visible spec of the containing car (in your example, Gear_Box
calls *public* procedure Shift_Gear in Vehicle), but not the car's
structure.
In other words, the Engine still cannot see the Gear_Box within the
same car.

It is of course possible to expose delegating operations, but this
pollutes the public interface of the car with operations that are of
interest only to its private components. Users should not have access
to these operations.

--
Maciej Sobczak * www.msobczak.com * www.inspirel.com

Database Access Library for Ada: www.inspirel.com/soci-ada



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

* Re: Proper program structure
  2009-10-01  9:39   ` Maciej Sobczak
@ 2009-10-01 15:36     ` Brad Moore
  2009-10-01 20:01       ` Maciej Sobczak
  0 siblings, 1 reply; 13+ messages in thread
From: Brad Moore @ 2009-10-01 15:36 UTC (permalink / raw)


Maciej Sobczak wrote:
> On 1 Paz', 08:34, Brad Moore <brad.mo...@shaw.ca> wrote:
> 
>> See my example below. I have each component as a private child of Cars.
> 
> Well, I was too quick to reply.
> Your example does not do what I need. It allows the components to see
> the visible spec of the containing car (in your example, Gear_Box
> calls *public* procedure Shift_Gear in Vehicle), but not the car's
> structure.
> In other words, the Engine still cannot see the Gear_Box within the
> same car.
> 
> It is of course possible to expose delegating operations, but this
> pollutes the public interface of the car with operations that are of
> interest only to its private components. Users should not have access
> to these operations.
> 
> --
> Maciej Sobczak * www.msobczak.com * www.inspirel.com
> 
> Database Access Library for Ada: www.inspirel.com/soci-ada

OK, but I can apply a similar technique to hide the routines from the 
public interface of the car. In this case, I renamed the Cars.Vehicle
package to be Cars.Vehicle_Internal, then created a public Cars.Vehicle
package that wraps the internal one, but hides the interfaces that you
do not want to expose.

Is this version acceptable?

Revised Example:

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

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

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

--------------------------------------------
private with Cars.Vehicle_Internal;
package Cars.Vehicle is
    type Vehicle_Type (<>) is limited private;
    function Construct return Vehicle_Type;

private
    use Cars.Vehicle_Internal;
    type Vehicle_Type is
       record
          Internals : Vehicle_Internal_Type;
       end record;
end Cars.Vehicle;
-----------------------------------------
package body Cars.Vehicle is

    function Construct return Vehicle_Type is
    begin
       return  New_Vehicle : Vehicle_Type :=
         Vehicle_Type'(Internals => Vehicle_Internal.Construct) do
          return;
         end return;

    end Construct;
end Cars.Vehicle;
------------------------------------------
with Cars.Types; use Cars.Types;
private with Cars.Wheels, Cars.Chassis, Cars.Engine, Cars.Gear_Box;
private package Cars.Vehicle_Internal is

    type Vehicle_Internal_Type is limited private;
    procedure Shift_Gears
      (Car : in out Vehicle_Internal_Type;
       Gear : Gear_Type);

    function Construct return Vehicle_Internal_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_Internal_Type is limited
       record
          Wheels : Wheel_Array_Type;
          Chassis : Chassis_Type;
          Engine : Engine_Type;
          Gear_Box : Gear_Box_Type (Vehicle_Internal_Type'Access);
       end record;
end Cars.Vehicle_Internal;
--------------------------------------
package body Cars.Vehicle_Internal is

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

    end Construct;

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

end Cars.Vehicle_Internal;

-------------------------------------------
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;
--------------------------
with Cars.Types; use Cars.Types;
limited with Cars.Vehicle_Internal;
private package Cars.Gear_Box is
    type Gear_Box_Type
      (Containing_Vehicle : access 
Cars.Vehicle_Internal.Vehicle_Internal_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_Internal.Vehicle_Internal_Type)
      is null record;
end Cars.Gear_Box;
-------------------------------------
with Cars.Vehicle_Internal;
package body Cars.Gear_Box is
    procedure Select_Gear
      (Gear_Shift : in out Gear_Box_Type;
       Gear : Gear_Type) is
    begin
       Cars.Vehicle_Internal.Shift_Gears
         (Car => Gear_Shift.Containing_Vehicle.all, Gear => Gear);
    end Select_Gear;
end Cars.Gear_Box;
--------------------------
with Cars.Types; use Cars.Types;
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;



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

* Re: Proper program structure
  2009-10-01 15:36     ` Brad Moore
@ 2009-10-01 20:01       ` Maciej Sobczak
  2009-10-02  5:44         ` Brad Moore
  0 siblings, 1 reply; 13+ messages in thread
From: Maciej Sobczak @ 2009-10-01 20:01 UTC (permalink / raw)


On 1 Paź, 17:36, Brad Moore <brad.mo...@shaw.ca> wrote:

> OK, but I can apply a similar technique to hide the routines from the
> public interface of the car. In this case, I renamed the Cars.Vehicle
> package to be Cars.Vehicle_Internal, then created a public Cars.Vehicle
> package that wraps the internal one, but hides the interfaces that you
> do not want to expose.
>
> Is this version acceptable?

Well, it is acceptable in the same way as the other options that I
have described. In other words, due to the fact that the right way
does not work, I have to use some inferior solution that always has a
price in the design that does not accurately reflect the original
intent.

Thank you very much for you complete examples, but I have finally
settled on two-phase initialization of components (so each has an Init
procedure that is used to wire them together and all Inits are called
from the constructor of the whole). This solution allows to keep the
reasonable package hierarchy (no artificial packages are needed) and
avoids a number of forwarding subprograms.

--
Maciej Sobczak * www.msobczak.com * www.inspirel.com

Database Access Library for Ada: www.inspirel.com/soci-ada



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

* Re: Proper program structure
  2009-10-01 20:01       ` Maciej Sobczak
@ 2009-10-02  5:44         ` Brad Moore
  2009-10-02 13:10           ` Brad Moore
  0 siblings, 1 reply; 13+ messages in thread
From: Brad Moore @ 2009-10-02  5:44 UTC (permalink / raw)


Maciej Sobczak wrote:
> On 1 Paz', 17:36, Brad Moore <brad.mo...@shaw.ca> wrote:
> 
>> OK, but I can apply a similar technique to hide the routines from the
>> public interface of the car. In this case, I renamed the Cars.Vehicle
>> package to be Cars.Vehicle_Internal, then created a public Cars.Vehicle
>> package that wraps the internal one, but hides the interfaces that you
>> do not want to expose.
>>
>> Is this version acceptable?
> 
> Well, it is acceptable in the same way as the other options that I
> have described. In other words, due to the fact that the right way
> does not work, I have to use some inferior solution that always has a
> price in the design that does not accurately reflect the original
> intent.
> 
> Thank you very much for you complete examples, but I have finally
> settled on two-phase initialization of components (so each has an Init
> procedure that is used to wire them together and all Inits are called
> from the constructor of the whole). This solution allows to keep the
> reasonable package hierarchy (no artificial packages are needed) and
> avoids a number of forwarding subprograms.

You're welcome. It was an interesting challenge. Not sure I fully see 
how you've structured things, but glad you found something that at least 
works. I do have one more set of refinements to add to my example
though.

I can get rid of the artificial packages Cars, and Vehicle, by using an
incomplete type declaration. That is, I move the declarations in 
Cars.Vehicle, to Cars, which is where you wanted to declare the type
in the first place. However Vehicle_Type is an access type to an
incomplete type that is completed in the body.
This allows me to delete the Cars.Vehicle package as it is no longer needed.

Since Construct now returns a heap allocated object, I make the type,
Vehicle_Internal_Type a controlled type in the package body, so that 
clients of Cars would not be impacted by memory leakage by declaring a 
Vehicle_Type on the stack.

Also, I made the Cars.Vehicle_Type inherit from 
Cars.Vehicle_Internal.Vehicle_Internal_Type. That way, I eliminate
the forwarding programs. (If that is what you meant by forwarding
subprograms), since the primitive subprograms are inherited directly.

Well not entirely true. The primitive subprograms are available in
the body, but if they need to be exported from the spec, then you
still need forwarding subprograms, since Vehicle_Type is an access type.

Also, other than the Cars package, all the other child packages are 
private packages, which cannot be withed by client code.

If the goal of this exercise is to be able to define a clean interface
that is not impacted by the Ada language restrictions, ie, no evidence
or artifacts in the spec resulting from issues that have been discussed,
then I think this might be a good approach. If we had to jump through
a hoop or two to get the implementations to work, at least we've hidden
those hurdles.

Of course, the approach you've chosen may still be a better approach for
you, since the requirements of your real system may be very much 
different than this toy example.

For example, you may not like the fact that I used heap allocation here, 
or that I still have a Cars.Vehicle_Internal package, though that is a
private package at least. Maybe it just needs a better name.

It is perhaps weird that the vehicle_type in Cars effectively inherits 
from a private child package. Usually it is the other way around, the
child package type inherits from the root package type, but I think
this gives you the effect that the root package type contains all the
details hidden in the child package.


To show the refined approach though...

package Cars is
    type Vehicle_Type (<>) is limited private;
    function Construct return Vehicle_Type;
private
    type Car_Type;
    type Vehicle_Type is access all Car_Type;
end Cars;
------------------------------------------
with Cars.Vehicle_Internal;
use Cars.Vehicle_Internal;
package body Cars is

   type Car_Type is  new Vehicle_Internal_Type;

    function Construct return Vehicle_Type is
    begin
       return new Car_Type'
          (Car_Type (Vehicle_Internal_Type'(Construct)));
    end Construct;
end Cars;
---------------------------------------------
with Cars.Types; use Cars.Types;
private with Cars.Wheels, Cars.Chassis, Cars.Engine, Cars.Gear_Box;
private with Ada.Finalization;
private package Cars.Vehicle_Internal is

    type Vehicle_Internal_Type is private;
    procedure Shift_Gears
      (Car : in out Vehicle_Internal_Type;
       Gear : Gear_Type);

    function Construct return Vehicle_Internal_Type;

private
    use Cars.Wheels, Cars.Chassis, Cars.Engine, Cars.Gear_Box;
    use Ada.Finalization;
    type Wheel_Array_Type is array (1 .. 4) of Wheel_Type;
    type Force_Finalization is new Controlled with null record;
    type Vehicle_Internal_Type is
       record
          Needs_Finalization : Force_Finalization;
          Wheels : Wheel_Array_Type;
          Chassis : Chassis_Type;
          Engine : Engine_Type;
          Gear_Box : Gear_Box_Type (Vehicle_Internal_Type'Access);
       end record;
end Cars.Vehicle_Internal;

----------------------------------------
package body Cars.Vehicle_Internal is

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

    end Construct;

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

end Cars.Vehicle_Internal;

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

-- Also, forgot to include this package last time.
private package Cars.Types is
    type Gear_Type is (Park, Neutral, Forward, Backward);
end Cars.Types;

Brad



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

* Re: Proper program structure
  2009-10-02  5:44         ` Brad Moore
@ 2009-10-02 13:10           ` Brad Moore
  0 siblings, 0 replies; 13+ messages in thread
From: Brad Moore @ 2009-10-02 13:10 UTC (permalink / raw)


Sorry, need to make a correction to my last posting.

The automatic finalization would not have worked like I would have
wanted, since the vehicle_type is an access type, finalization would
have only occurred when the access type went out of scope (at program
finalization). To get the effect I was looking for would have required
garbage collection. To correct this, I make the Vehicle_Type a 
controlled record type containing an access to the internals.
I got rid of the inheritance, since it did not seem to be providing
much of a benefit.

Brad

i.e.

private with Ada.Finalization;
package Cars is
    type Vehicle_Type (<>) is limited private;
    function Construct return Vehicle_Type;
private
    use Ada.Finalization;
    type Car_Type;
    type Car_Access_Type is access Car_Type;
    type Vehicle_Type is new Limited_Controlled with
       record
          Car : Car_Access_Type;
       end record;
    overriding procedure Finalize (Vehicle : in out Vehicle_Type);
end Cars;

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

with Cars.Vehicle_Internal;
use Cars.Vehicle_Internal;
with Ada.Unchecked_Deallocation;
package body Cars is
    type Car_Type is
       record
          Internals : Vehicle_Internal_Type;
       end record;

    procedure Free_Car is new Ada.Unchecked_Deallocation
      (Object => Car_Type,
       Name => Car_Access_Type);

    function Construct return Vehicle_Type is
    begin
       return Vehicle : Vehicle_Type
       do
          Vehicle.Car :=
            new Car_Type'(Internals => Vehicle_Internal_Type'(Construct));
          return;
       end return;
    end Construct;

    overriding procedure Finalize (Vehicle : in out Vehicle_Type) is
    begin
       Free_Car (Vehicle.Car);
    end Finalize;

end Cars;



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

end of thread, other threads:[~2009-10-02 13:10 UTC | newest]

Thread overview: 13+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2009-09-30 21:25 Proper program structure Maciej Sobczak
2009-09-30 22:16 ` Robert A Duff
2009-10-01  7:13   ` Maciej Sobczak
2009-09-30 23:43 ` Adam Beneschan
2009-10-01  7:35   ` Maciej Sobczak
2009-10-01  6:34 ` Brad Moore
2009-10-01  7:44   ` Maciej Sobczak
2009-10-01  9:39   ` Maciej Sobczak
2009-10-01 15:36     ` Brad Moore
2009-10-01 20:01       ` Maciej Sobczak
2009-10-02  5:44         ` Brad Moore
2009-10-02 13:10           ` Brad Moore
2009-10-01  8:08 ` Dmitry A. Kazakov

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