2
votes

I'm trying to write a very primitive linked list example program in Ada 2012. My code consists of 3 files, linked_list.adb,linked_list.ads and main.adb.

The user will run the program and simply enter a sequence of numbers followed by zero to end the sequence and exit. The program simply reads these numbers from std-in, prints the list out and then quits.

Here's my code in full...

File: "main.adb"

with Linked_List; use Linked_List;

procedure Main is
  L : access List_Item;
begin
  L := new List_Item'(null, 0);

  while Append_Item (L) loop
    null;
  end loop;

  Print_List (L);
end Main;

File: "linked_list.ads"

with Ada.Text_IO; use Ada.Text_IO;

package Linked_List is
  type List_Item is private;

  function Append_Item (List_Head : access List_Item) return Boolean;
  procedure Print_List (List_Head : access List_Item);

private

  type List_Item is
    record
      Next_Item : access List_Item;
      ID : Integer;
    end record;

end Linked_List;

File: "linked_list.ads"

with Ada.Text_IO; use Ada.Text_IO;

package body Linked_List is

  function Append_Item (List_Head : access List_Item) return Boolean is
    Runner : access List_Item := List_Head;
    new_ID : Integer;
  begin
    if Runner.Next_Item = null then -- if we've found the last item
      Put ("Enter ID for new Item (enter 0 to stop): ");
      Get (new_ID);

      if new_ID = 0 then
        return false; -- user wants to quit
      else if;
        -- add a new item to the end of the list
        Runner.Next_Item := new List_Item'(null, new_ID);
        return true;
      end if;
    else;
      Runner := Runner.Next_Item;
    end if;
  end Append_Item;

  procedure Print_List (List_Head : access List_Item);
    Runner : access List_Item := List_Head;
  begin
    if Runner = null then
      return;
    else;
      Put ("Item ID: "); Put (Runner.ID);
      Runner := Runner.Next_Item;
    end if;
  end Print_List;

end Linked_List;

I'm using Gnatmake 7.4.0 and my compiler command line is

gnatmake -gnaty -gnaty2 -gnat12 main.adb

The error message I'm seeing is:

gnatmake -gnaty -gnaty2 -gnat12 main.adb
aarch64-linux-gnu-gcc-7 -c -gnaty -gnaty2 -gnat12 main.adb
main.adb:6:22: expected private type "List_Item" defined at linked_list.ads:4
main.adb:6:22: found a composite type
gnatmake: "main.adb" compilation error
Makefile:2: recipe for target 'all' failed
make: *** [all] Error 4

The syntax I've written appears to be consistent with the book I'm attempting to learn from: "Programming in Ada 2012" by John Barnes.

The record is declared privately so that my client program (main) doesn't see the gory details of the inner workings of the list mechanism. What am I doing wrong?

2
It seems to me that new List_Item'(null, 0); is precisely making use of the gory details of the list mechanism! - Simon Wright
A nitpick - lists and list items are quite different things, best not to give them the same name - Simon Wright

2 Answers

4
votes

The reason is: type List_Item is private (in plain English from the Ada code)!

This means the author of package Linked_List doesn't want its users to make use of its details (that it is a record with two components). In a more complex software, it is useful to hide those details since they are subject to change and users would run into trouble if they used details (in this case, the composite type) that would become incompatible after a design change in the type List_Item. There are two solutions:

  1. Make List_Item public (usually a bad solution if you want to build "real" software)
  2. Add Null_List : constant List_Item; after type List_Item is private and Null_List : constant List_Item := (null, 0); in the private part. Then you can use Null_List in your main.adb.
1
votes

Indeed the type is private, which means the client "main.adb" can't make assumptions about its internals as it does in the "new" call, and both Zerte's solutions work around the problem (and I agree : the first is not a solution!)

A third solution : the "object factory" design pattern.

package Linked_List is
  type List_Item is private;

  function Create_Item(ID_Value : Natural) return List_Item;

The function body should be obvious. Note that for more complex examples especially for limited private types, the "extended return" syntax of Ada-2005 may be required.

It's OK to overload this constructor (really an example of an object factory) with variants for different types of items, and offer Zerte's Null_List : constant List_Item; as an alternative.

The client can use this function as

L := Create_Item(0);