comp.lang.ada
 help / color / mirror / Atom feed
* Active Iteration - Update
@ 1998-05-15  0:00 Matthew Heaney
  1998-05-17  0:00 ` Steve Whalen
  0 siblings, 1 reply; 3+ messages in thread
From: Matthew Heaney @ 1998-05-15  0:00 UTC (permalink / raw)




I figured out a way to do iteratoration using a dispatching iterator
(handle) constructor, and at the same time hide away the calls to
New_Iterator and Free.

I originally posed something like this:

   function New_Iterator 
     (Stack : access Root_Stack) return Iterator_Access;

My issue with this solution is that the client has to remember to
explicitly Free the object returned by that constructor, as in:

procedure Do_Something (S : access Stack'Class) is

   IA : Iterator_Access := New_Iterator (S);

   Iter : Root_Iterator'Class renames IA.all;
begin
   while not Is_Done (Iter) loop
      ... Get_Item (Iter) ...
      <do something with item>
      Advance (Iter);
   end loop;

   Free (IA);
end Do_Something;

There are two reasons why I don't like this:

1) The client has to call a function manually, to get an iterator
indirectly, ie through an access object.

2) The client has to remember to manually call Free, to reclaim storage
associated with the iterator object.

Item (2) is especially pernicious, because 

a) the client can forget to call Free, thus creating a memory leak

b) there could be an unhandled exception raised prior to the call to Free,
thus creating a memory leak

We can do better.

The idea is to wrap the iterator access object inside a Controlled object,
which I've called Iterator_Handle.  That way  Finalize gets called
automatically, and thus no memory leak can occur.

The iterator handle is implemented as a Controlled record containing an
access object that designates an iterator.  As before, the iterator is
constructed by calling a dispatching function that returns the iterator
access type.  The iterator handle implements its operations in terms of the
corresponding operations of the actual iterator.

The iterator handle, during its (automatic) Finalization, calls a
dispatching Deallocate operation to reclaim the storage associated with the
iterator.

The improved version of the classwide operation above is:

procedure Do_Something (S : access Stack'Class) is

   Handle : Iterator_Handle (S);
begin

   while not Is_Done (Handle) loop
      ... Get_Item (Handle) ...
      <do something with item>
      Advance (Handle);
   end loop;

end Do_Something;

Note that there is no call to New, nor is there a call to Free.

Hope that helps,
Matt

P.S. The sources below compile and run using GNAT 3.10p, and are in a
format suitable for use with gnatchop.


-- STX
with Ada.Finalization;

generic

   type Stack_Item is private;
   with function "=" (L, R : Stack_Item) return Boolean is <>;

package Stacks is

   type Root_Stack is abstract tagged null record;

   type Stack_Item_Access is access all Stack_Item;
   for Stack_Item_Access'Storage_Size use 0;

   procedure Push
     (Item : in     Stack_Item;
      On   : in out Root_Stack) is abstract;

   function Get_Top
     (Stack : Root_Stack) return Stack_Item is abstract;

   function Get_Top
     (Stack : access Root_Stack) return Stack_Item_Access is abstract;


   type Iterator_Handle
     (Stack : access Root_Stack'Class) is limited private;

   function Is_Done
     (Handle : Iterator_Handle) return Boolean;

   function Get_Item
     (Handle : Iterator_Handle) return Stack_Item_Access;

   procedure Advance
     (Handle : Iterator_Handle);

   procedure Reset
     (Handle : Iterator_Handle);

private

   type Root_Iterator is
     abstract tagged limited null record;

   function Is_Done
     (Iterator : Root_Iterator) return Boolean is abstract;

   function Get_Item
     (Iterator : Root_Iterator) return Stack_Item_Access is abstract;

   procedure Advance
     (Iterator : in out Root_Iterator) is abstract;

   procedure Reset
     (Iterator : in out Root_Iterator) is abstract;

   procedure Deallocate
     (Iterator : access Root_Iterator) is abstract;


   type Iterator_Access is
      access all Root_Iterator'Class;

   function New_Iterator
     (Stack : access Root_Stack) return Iterator_Access is abstract;

   type Iterator_Handle (Stack : access Root_Stack'Class) is
     new Ada.Finalization.Limited_Controlled with record
        Iter : Iterator_Access := New_Iterator (Stack);
     end record;

   procedure Finalize
     (Handle : in out Iterator_Handle);

end Stacks;


with Ada.Text_IO; use Ada.Text_IO;

package body Stacks is

   function Is_Done
     (Handle : Iterator_Handle) return Boolean is
   begin
      return Is_Done (Handle.Iter.all);
   end;


   function Get_Item
     (Handle : Iterator_Handle) return Stack_Item_Access is
   begin
      return Get_Item (Handle.Iter.all);
   end;


   procedure Advance
     (Handle : Iterator_Handle) is
   begin
      Advance (Handle.Iter.all);
   end;


   procedure Reset
     (Handle : Iterator_Handle) is
   begin
      Reset (Handle.Iter.all);
   end;

   procedure Finalize
     (Handle : in out Iterator_Handle) is
   begin
      Deallocate (Handle.Iter);
   end;


end Stacks;


generic
   Max_Depth : in Positive;
package Stacks.Bounded_G is

   type Bounded_Stack is
     new Root_Stack with private;

   function "="
     (L, R : Bounded_Stack) return Boolean;


   procedure Push
     (Item : in     Stack_Item;
      On   : in out Bounded_Stack);

   function Get_Top
     (Stack : Bounded_Stack) return Stack_Item;

   function Get_Top
     (Stack : access Bounded_Stack) return Stack_Item_Access;


   type Bounded_Iterator
     (Stack : access Bounded_Stack'Class) is
      limited private;

   function Is_Done
     (Iterator : Bounded_Iterator) return Boolean;

   function Get_Item
     (Iterator : Bounded_Iterator) return Stack_Item_Access;

   procedure Advance (Iterator : in out Bounded_Iterator);

   procedure Reset (Iterator : in out Bounded_Iterator);


private

   type Stack_Item_Array is
      array (Positive range <>) of aliased Stack_Item;

   function "=" (L, R : Stack_Item_Array) return Boolean is abstract;

   subtype Items_Range is Positive range 1 .. Max_Depth;

   type Bounded_Stack is
     new Root_Stack with record
        Items : Stack_Item_Array (Items_Range);
        Depth : Natural range 0 .. Max_Depth := 0;
     end record;


   type Bounded_Iterator
     (Stack : access Bounded_Stack'Class) is
     new Root_Iterator with record
        Index : Natural := Stack.Depth;
     end record;


   function New_Iterator
     (Stack : access Bounded_Stack) return Iterator_Access;

   procedure Deallocate
     (Iterator : access Bounded_Iterator);


end Stacks.Bounded_G;


with Ada.Unchecked_Deallocation;
with Ada.Text_IO;

package body Stacks.Bounded_G is

   function "="
     (L, R : Bounded_Stack) return Boolean is
   begin
      if L.Depth /= R.Depth then
         return False;
      end if;

      for I in 1 .. L.Depth loop
         if L.Items (I) /= R.Items (I) then
            return False;
         end if;
      end loop;

      return True;
   end "=";


   procedure Push
     (Item : in     Stack_Item;
      On   : in out Bounded_Stack) is

      Stack : Bounded_Stack renames On;
      Depth : Natural renames Stack.Depth;
   begin
      Depth := Depth + 1;
      Stack.Items (Depth) := Item;
   end Push;


   function Get_Top
     (Stack : Bounded_Stack) return Stack_Item is
   begin
      return Stack.Items (Stack.Depth);
   end Get_Top;


   function Get_Top
     (Stack : access Bounded_Stack) return Stack_Item_Access is
   begin
      return Stack.Items (Stack.Depth)'Access;
   end Get_Top;


   function Is_Done
     (Iterator : Bounded_Iterator) return Boolean is
   begin
      return Iterator.Index = 0;
   end;


   function Get_Item
     (Iterator : Bounded_Iterator) return Stack_Item_Access is

      Stack : Bounded_Stack'Class renames Iterator.Stack.all;
   begin
      return Stack.Items (Iterator.Index)'Access;
   end;


   procedure Advance (Iterator : in out Bounded_Iterator) is
   begin
      Iterator.Index := Iterator.Index - 1;
   end;


   procedure Reset (Iterator : in out Bounded_Iterator) is
   begin
      Iterator.Index := Iterator.Stack.Depth;
   end;


   function New_Iterator
     (Stack : access Bounded_Stack) return Iterator_Access is
   begin
     return new Bounded_Iterator (Stack);
   end;


   procedure Free is
     new Ada.Unchecked_Deallocation
     (Root_Iterator'Class, Iterator_Access);

   procedure Deallocate (Iterator : access Bounded_Iterator) is
      IA : Iterator_Access := Iterator_Access (Iterator);
   begin
      Free (IA);
      Ada.Text_IO.Put_Line ("reclaimed memory for iterator handle");
   end;


end Stacks.Bounded_G;

generic
   with function Image (Item : Stack_Item) return String;
package Stacks.Stack_IO_G is

   procedure Dump (Stack : access Root_Stack'Class);

end Stacks.Stack_IO_G;


with Ada.Text_IO;  use Ada.Text_IO;

package body Stacks.Stack_IO_G is

   procedure Dump (Stack : access Root_Stack'Class) is

      Iterator : Iterator_Handle (Stack);
   begin
      while not Is_Done (Iterator) loop
         Put (Image (Get_Item (Iterator).all));
         Advance (Iterator);
      end loop;

      New_Line;
   end Dump;

end Stacks.Stack_IO_G;


with Stacks;
pragma Elaborate_All (Stacks);

package Integer_Stacks is new Stacks (Integer);


with Stacks.Bounded_G;
pragma Elaborate_All (Stacks.Bounded_G);

package Integer_Stacks.Bounded is
  new Integer_Stacks.Bounded_G (Max_Depth => 10);


with Stacks.Stack_IO_G;
pragma Elaborate_All (Stacks.Stack_IO_G);

package Integer_Stacks.Stack_IO is
   new Integer_Stacks.Stack_IO_G (Integer'Image);


with Integer_Stacks.Bounded;   use Integer_Stacks.Bounded;
with Integer_Stacks.Stack_IO;  use Integer_Stacks.Stack_IO;

procedure Test_Stacks is

   Stack : aliased Bounded_Stack;

begin

   Push (9, On => Stack);
   Push (8, On => Stack);
   Push (7, On => Stack);
   Push (6, On => Stack);
   Push (5, On => Stack);

   Dump (Stack'Access);

end;




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

* Re: Active Iteration - Update
  1998-05-17  0:00 ` Steve Whalen
@ 1998-05-17  0:00   ` Matthew Heaney
  0 siblings, 0 replies; 3+ messages in thread
From: Matthew Heaney @ 1998-05-17  0:00 UTC (permalink / raw)



In article <swhalenEt361C.EAp@netcom.com>, swhalen@netcom.com (Steve
Whalen) wrote:

(start of quote)
Thanks for posting the updated and improved Iterator package.
(end of quote)

You're welcome - and I've enclosed another version that I think is even better.

There was one aspect of the previous version that bothered me.  The
primitive operations for Iterator_Handle are the same as those of the
iterator type, and in fact are implemented by just calling through to the
iterator operations.  Why duplicate code this way?

The whole point of the iterator handle is to take care of deallocating the
iterator for the convenience of the client, so we can eliminate memory
leaks.  But it ended up mirroring the functionality of the iterator itself. 
Can't we simplify the Iterator_Handle somehow, so it only has to worry
about memory reclaimation, instead of iteration too?

Here's what I did.  By moving the Root_Iterator type up into the public
part of the spec, I can have the Iterator_Handle return an access object
that designates the actual iterator.  The client can then use the iterator
directly, and there is thus no need to have an extra operations for the
handle type that merely call those of the iterator.

The new spec looks like this:

   type Root_Iterator is 
      abstract tagged limited null record;

   type Iterator_Access is 
      access all Root_Iterator'Class;
   
   <...primitive ops for iterator...>

   type Iterator_Handle 
     (Stack : access Root_Stack'Class) is limited private

   function Get_Iterator
      (Handle : Iterator_Handle) return Iterator_Access;

private
...

There's now one extra step for the client:

   Handle : Iterator_Handle (Stack);
 
   Iterator : Root_Iterator'Class renames
      Get_Iterator (Handle).all;
begin


Note that here's the reason I name selector operations Get_XXX: so I can
rename the object returned by the selector XXX.  Had the selector been
named just XXX, as in

   function Iterator (Handle : Iterator_Handle) return Iterator_Access;

then the object couldn't be called Iterator, because within a declaration
that name can only be used once, ie

   Iterator : Root_Iterator'Class renames Iterator (Handle).all;

is illegal.


(start of quote)
Studying it has encouraged me to once more go back over old code I've
written that works, but isn't "elegant".  My experience is that if
code I write isn't clear and at least somewhat elegant, then I
probably have NOT taken enough time to match either the data or the
algorithms to my Ada95 implementation.
(end of quote)

Ahhh, the voice of reason in a chaotic world.  I meet many programmers -
unlike you - who don't seem to understand software as a medium for the
construction of systems.  Elegance isn't even on their radar screen,
because they spend all their time just getting the software to emit _some_
answer without crashing.  Sigh.

Your sage observation brings me some measure of relief, knowing that
there's someone else who understands what I'm talking about.  Thank you!

(start of quote)
I think your iterator package is a good example that if you keep
pushing to find a "clean" solution in Ada, the language will almost
always support it.
(end of quote)

Indeed, this is true.  Funny, this idea came to me while driving to a
movie.  Maybe I should go see more movies...

Matt

P.S.  The sources for the improved version are below (only Stacks and
Stacks.Stack_IO changed).  As before, the sources are in a format suitable
for input to gnatchop (you'll have to remove my signiture though).

--STX
with Stacks.Bounded_G;
pragma Elaborate_All (Stacks.Bounded_G);

package Integer_Stacks.Bounded is
  new Integer_Stacks.Bounded_G (Max_Depth => 10);


with Stacks.Stack_IO_G;
pragma Elaborate_All (Stacks.Stack_IO_G);

package Integer_Stacks.Stack_IO is
   new Integer_Stacks.Stack_IO_G (Integer'Image);


with Stacks;
pragma Elaborate_All (Stacks);

package Integer_Stacks is new Stacks (Integer);


with Ada.Unchecked_Deallocation;
with Ada.Text_IO;

package body Stacks.Bounded_G is

   function "="
     (L, R : Bounded_Stack) return Boolean is
   begin
      if L.Depth /= R.Depth then
         return False;
      end if;

      for I in 1 .. L.Depth loop
         if L.Items (I) /= R.Items (I) then
            return False;
         end if;
      end loop;

      return True;
   end "=";


   procedure Push
     (Item : in     Stack_Item;
      On   : in out Bounded_Stack) is

      Stack : Bounded_Stack renames On;
      Depth : Natural renames Stack.Depth;
   begin
      Depth := Depth + 1;
      Stack.Items (Depth) := Item;
   end Push;


   function Get_Top
     (Stack : Bounded_Stack) return Stack_Item is
   begin
      return Stack.Items (Stack.Depth);
   end Get_Top;


   function Get_Top
     (Stack : access Bounded_Stack) return Stack_Item_Access is
   begin
      return Stack.Items (Stack.Depth)'Access;
   end Get_Top;


   function Is_Done
     (Iterator : Bounded_Iterator) return Boolean is
   begin
      return Iterator.Index = 0;
   end;


   function Get_Item
     (Iterator : Bounded_Iterator) return Stack_Item_Access is

      Stack : Bounded_Stack'Class renames Iterator.Stack.all;
   begin
      return Stack.Items (Iterator.Index)'Access;
   end;


   procedure Advance (Iterator : in out Bounded_Iterator) is
   begin
      Iterator.Index := Iterator.Index - 1;
   end;


   procedure Reset (Iterator : in out Bounded_Iterator) is
   begin
      Iterator.Index := Iterator.Stack.Depth;
   end;


   function New_Iterator
     (Stack : access Bounded_Stack) return Iterator_Access is
   begin
     return new Bounded_Iterator (Stack);
   end;


   procedure Free is
     new Ada.Unchecked_Deallocation
     (Root_Iterator'Class, Iterator_Access);

   procedure Deallocate (Iterator : access Bounded_Iterator) is
      IA : Iterator_Access := Iterator_Access (Iterator);
   begin
      Free (IA);
      Ada.Text_IO.Put_Line ("reclaimed memory for iterator handle");
   end;


end Stacks.Bounded_G;
generic
   Max_Depth : in Positive;
package Stacks.Bounded_G is

   type Bounded_Stack is
     new Root_Stack with private;

   function "="
     (L, R : Bounded_Stack) return Boolean;


   procedure Push
     (Item : in     Stack_Item;
      On   : in out Bounded_Stack);

   function Get_Top
     (Stack : Bounded_Stack) return Stack_Item;

   function Get_Top
     (Stack : access Bounded_Stack) return Stack_Item_Access;


   type Bounded_Iterator
     (Stack : access Bounded_Stack'Class) is
      new Root_Iterator with private;

   function Is_Done
     (Iterator : Bounded_Iterator) return Boolean;

   function Get_Item
     (Iterator : Bounded_Iterator) return Stack_Item_Access;

   procedure Advance (Iterator : in out Bounded_Iterator);

   procedure Reset (Iterator : in out Bounded_Iterator);


private

   type Stack_Item_Array is
      array (Positive range <>) of aliased Stack_Item;

   function "=" (L, R : Stack_Item_Array) return Boolean is abstract;

   subtype Items_Range is Positive range 1 .. Max_Depth;

   type Bounded_Stack is
     new Root_Stack with record
        Items : Stack_Item_Array (Items_Range);
        Depth : Natural range 0 .. Max_Depth := 0;
     end record;


   type Bounded_Iterator
     (Stack : access Bounded_Stack'Class) is
     new Root_Iterator with record
        Index : Natural := Stack.Depth;
     end record;


   function New_Iterator
     (Stack : access Bounded_Stack) return Iterator_Access;

   procedure Deallocate
     (Iterator : access Bounded_Iterator);


end Stacks.Bounded_G;
with Ada.Text_IO;  use Ada.Text_IO;

package body Stacks.Stack_IO_G is

   procedure Dump (Stack : access Root_Stack'Class) is

      Handle   : Iterator_Handle (Stack);
      Iterator : Root_Iterator'Class renames
        Get_Iterator (Handle).all;
   begin
      while not Is_Done (Iterator) loop
         Put (Image (Get_Item (Iterator).all));
         Advance (Iterator);
      end loop;

      New_Line;
   end Dump;

end Stacks.Stack_IO_G;

generic
   with function Image (Item : Stack_Item) return String;
package Stacks.Stack_IO_G is

   procedure Dump (Stack : access Root_Stack'Class);

end Stacks.Stack_IO_G;
package body Stacks is

   procedure Finalize
     (Handle : in out Iterator_Handle) is
   begin
      Deallocate (Handle.Iter);
   end;


   function Get_Iterator
     (Handle : Iterator_Handle) return Iterator_Access is
   begin
      return Handle.Iter;
   end;

end Stacks;
with Ada.Finalization;

generic

   type Stack_Item is private;
   with function "=" (L, R : Stack_Item) return Boolean is <>;

package Stacks is

   type Root_Stack is abstract tagged null record;

   type Stack_Item_Access is access all Stack_Item;
   for Stack_Item_Access'Storage_Size use 0;

   procedure Push
     (Item : in     Stack_Item;
      On   : in out Root_Stack) is abstract;

   function Get_Top
     (Stack : Root_Stack) return Stack_Item is abstract;

   function Get_Top
     (Stack : access Root_Stack) return Stack_Item_Access is abstract;


   type Root_Iterator is
     abstract tagged limited null record;

   type Iterator_Access is
      access all Root_Iterator'Class;

   function Is_Done
     (Iterator : Root_Iterator) return Boolean is abstract;

   function Get_Item
     (Iterator : Root_Iterator) return Stack_Item_Access is abstract;

   procedure Advance
     (Iterator : in out Root_Iterator) is abstract;

   procedure Reset
     (Iterator : in out Root_Iterator) is abstract;


   type Iterator_Handle
     (Stack : access Root_Stack'Class) is limited private;

   function Get_Iterator
     (Handle : Iterator_Handle) return Iterator_Access;

private

   function New_Iterator
     (Stack : access Root_Stack) return Iterator_Access is abstract;

   procedure Deallocate
     (Iterator : access Root_Iterator) is abstract;

   type Iterator_Handle (Stack : access Root_Stack'Class) is
     new Ada.Finalization.Limited_Controlled with record
        Iter : Iterator_Access := New_Iterator (Stack);
     end record;

   procedure Finalize
     (Handle : in out Iterator_Handle);

end Stacks;
with Integer_Stacks.Bounded;   use Integer_Stacks.Bounded;
with Integer_Stacks.Stack_IO;  use Integer_Stacks.Stack_IO;

procedure Test_Stacks is

   Stack : aliased Bounded_Stack;

begin

   Push (9, On => Stack);
   Push (8, On => Stack);
   Push (7, On => Stack);
   Push (6, On => Stack);
   Push (5, On => Stack);

   Dump (Stack'Access);

end;




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

* Re: Active Iteration - Update
  1998-05-15  0:00 Active Iteration - Update Matthew Heaney
@ 1998-05-17  0:00 ` Steve Whalen
  1998-05-17  0:00   ` Matthew Heaney
  0 siblings, 1 reply; 3+ messages in thread
From: Steve Whalen @ 1998-05-17  0:00 UTC (permalink / raw)




Thanks for posting the updated and improved Iterator package.

Studying it has encouraged me to once more go back over old code I've
written that works, but isn't "elegant".  My experience is that if
code I write isn't clear and at least somewhat elegant, then I
probably have NOT taken enough time to match either the data or the
algorithms to my Ada95 implementation.

I think your iterator package is a good example that if you keep
pushing to find a "clean" solution in Ada, the language will almost
always support it.

Steve
-- 
{===----------------------------------------------------------------------===}
                    Steve Whalen     swhalen@netcom.com
{===----------------------------------------------------------------------===}




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

end of thread, other threads:[~1998-05-17  0:00 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
1998-05-15  0:00 Active Iteration - Update Matthew Heaney
1998-05-17  0:00 ` Steve Whalen
1998-05-17  0:00   ` Matthew Heaney

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