Erik Stens
Erik Stens

Reputation: 1807

Change default initial value in derived record

Let's say I create the following record:

type AnimalSound is (None, Woof);

type Animal is tagged
  record
    sound : AnimalSound := None;
  end record;

And then I want to derive another record from this:

type Dog is new Animal with
  record
    nrPuppies : Integer := 0;
    sound : AnimalSound := Woof;
  end record;

The compiler will give a name clash error (conflicts with declaration at), which feels logical (you're redefining something that's already there), but I was wondering if there's a clean way to have a different default for a field in a derived record type in Ada. Or perhaps there is a reasoning why this is bad practise and could result in ambiguities or similar problems?

Upvotes: 1

Views: 457

Answers (3)

debater
debater

Reputation: 466

I would suggest something like the following.

package Animals is

   type Animal_Sound is (Default, None, Woof, Yelp, Howl, Meow, Oink);

   type Animal is abstract tagged
      record
         Special_Sound: Animal_Sound := Default;
      end record;

   function Default_Sound (Creature: in Animal) return Animal_Sound is abstract;

   function Sound (Creature: in Animal'Class) return Animal_Sound;
end;

package body Animals is

   function Sound (Creature: in Animal'Class) return Animal_Sound is
   begin
      if Creature.Special_Sound /= Default then
         return Creature.Special_Sound;
      else
         return Default_Sound (Creature);
      end if;
   end;

end Animals;

Then you could add a dog as follows.

with Animals; use Animals;

package Canine is

   type Puppy_Count is range 0 .. 24;

   type Dog is new Animal with
      record
         Litter_Size: Puppy_Count := 0;
      end record;

   overriding
   function Default_Sound (Pooch: in Dog) return Animal_Sound is (Woof);

end;

The idea is that an animal of a certain class has a default sound, but it could have a special sound. We have a special value Default of Animal_Sound which indicates that the animal's sound is its default sound. We define an abstract function Default_Sound that must be overridden by each class of animal, and a convenience function Sound that returns the default sound or a special sound as appropriate.

Upvotes: 1

LoneWanderer
LoneWanderer

Reputation: 3341

You can't instanciate Dog without providing an AnimalSound.

type AnimalSound is (None, Woof) with Default_Value => None; -- for Ada 2012 scalar types demonstration purpose
type Animal is tagged -- class
   record
      sound : AnimalSound; -- Defaults to None
   end record;
type Dog is new Animal with record -- subclass
   nrPuppies : Integer := 0;
end record;
Default_Dog : constant access Dog := new Dog'(sound     => Woof,
                                              nrPuppies => 0);
animal1 : access Animal := new Animal; -- sound defaults to None
animal2 : access Dog := new Dog'(sound     => Woof, -- forced
                                 nrPuppies => 2);
animal3 : access Dog := Default_Dog;
animal4 : access Dog := new Dog'(nrPuppies => 3); -- does not compile: no value supplied for component "sound"

So, this construction shows that even if you force a default value for a given class and benefit from it at instantiation, any child class would need to be given a value for the field with default value. (I did not checked, but I do believe it also applies to access fields.)

You could also define a constructor for Dog class with 1 arg (nb of puppies). The implementation would set a value for Sound, but the concept would still be the same:

function fancy_default_constructor(nb_puppies : Integer) return not null access Dog is
begin 
    return new Dog'(sound     => Woof, -- forced
                    nrPuppies => nb_puppies);
end fancy_default_constructor;

Upvotes: 2

Jere
Jere

Reputation: 3641

If you use composition instead of type extension, you can change the initial value. If you still need dynamic dispatch (provided by type extension in Ada), you can mimic what Rust does and use composition for the actual data type and an interface to provide the dispatch.

Example:

with Ada.Text_IO; use Ada.Text_IO;

procedure Hello is

    package Animals is

        type Animal_Sound is (None, Woof);

        -- Interface for extension
        type Animal is limited interface;

        function Get_Sound(Self : Animal) return Animal_Sound is abstract;

        -- Implementation for Composition
        type Default_Animal is new Animal with record
            Sound : Animal_Sound := None;
        end record;

        overriding
        function Get_Sound(Self : Default_Animal) return Animal_Sound
            is (Self.Sound)
        with Inline;

    end Animals;

    package Dogs is

        type Dog is new Animals.Animal with private;

        overriding
        function Get_Sound(Self : Dog) return Animals.Animal_Sound
            with Inline;

        function Get_Number_Of_Puppies(Self : Dog) return Natural;

    private

        use Animals;

        type Dog is new Animal with record
            Number_Of_Puppies : Natural := 0;

            -- Reset the initial value here:
            Implementation    : Default_Animal := (Sound => Woof);
        end record;

        function Get_Sound(Self : Dog) return Animal_Sound
            is (Self.Implementation.Get_Sound);

        function Get_Number_Of_Puppies(Self : Dog) return Natural
            is (Self.Number_Of_Puppies);

    end Dogs;

begin
  Put_Line("Hello, world!");
end Hello;

You can add another interface for Dog and Default_Dog if you want to continue the extension further (an potentially change other internal variables).

Upvotes: 2

Related Questions