Reputation: 1807
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
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
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
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