Let's say I have a Planet:
type Planet is tagged null record;
type Planet_Ref is access Planet'class;
Now I subclass it:
type Habitable_Planet is new Planet with null record;
type Habitable_Planet_Ref is access Habitable_Planet'class;
Now I define some variables:
p: Planet_Ref := Make_Planet;
hp: Habitable_Planet_Ref := Make_Habitable_Planet;
I would naively expect that assigning p := hp would work, because a Habitable_Planet is a subclass of Planet. But of course that won't work because every type defined with type is distinct and doesn't interoperate with any other type.
So I'd expect to have to declare Habitable_Planet_Ref to be a subtype of Planet_Ref to make this work. But the syntax doesn't seem to allow for this.
How do I make this work?
(Yes, I know I can use an explicit view conversion to cast a Habitable_Planet_Ref to a Planet_Ref, but that's really ugly and I'd like to avoid it.)
Ada recognizes types by name, so indeed you would need a view conversion here.
But if you are using Ada 2005, you can use anonymous access types instead. For instance:
hp: access Habitable_Planet'Class := Make_Habitable_Planet;
p: access Planet'Class := hp; -- valid with anonymous access types
One the drawbacks of using anonymous access types is that the code is more
verbose (although in general you would not use them for local variables, but
as parameters to subprograms or as fields in a (tagged) record.
They also can't be used with Unchecked_Deallocation. In fact, I personally often
use them exactly because of that: when I have a field in a record which is of an
anonymous access type, I know that the record does not "own" the accessed data,
and therefore it should not free it (in fact, I would have to write some convoluted
code to free them).
And of course as per your request the result for type matching are slightly more
relax, which is nice too.
ajb is correct in his comment. Ada is too strict for many practices you might be used to in other languages. An alternative would be to just not use objects and instead just simple records or discriminate records. I understand this may not be what you are looking for, but from my experience more can be done with less lines of code and the solution tends to me easier to understand.
Simple record
--...
type Rec_Planet is record
--.. stuff
end record;
--...
type Rec_Habitable_Planet is record
Planet : Rec_Planet := (others => <>);
--.. stuff
end record;
Discriminate record
type Enum_Planet is (Normal_Planet, Habitable_Planet);
type Rec_Planet(Kind : Enum_Planet := Normal_Planet) is record
-- rec_Planet stuff..
case Kind is
when Habitable_Planet => -- Rec_Habitable_Planet stuff
when others => null;
end case;
end record;
So #manuBriot gave me the answer I needed, but there were some other things I was doing wrong in my question which I should clarify because they'll confuse anyone else reading this question.
I was confusing the issue by using accesses. From Ada's point of view all accesses defined with type are distinct, so it never gets as far as looking at what the access is pointing at; it just disallows the assignment.
However, Ada does support implicit upcasting of class-wide types (and also discrete types, where an instance of a subtype will get implicitly cast to its parent type --- implement ALL the class hierarchies! But that's not really relevant here.) Example here:
With Ada.Text_IO; Use Ada.Text_IO;
With Ada.Integer_Text_IO; Use Ada.Integer_Text_IO;
procedure Prog is
package Superclass is
type Class is tagged record
null;
end record;
procedure Announce(self: in out Class);
subtype Var is Class'class;
end;
package body Superclass is
procedure Announce(self: in out Class)
is
begin
Put_Line("I am the superclass");
end;
end;
package Subclass is
type Class is new Superclass.Class with null record;
procedure Announce(self: in out Class);
end;
package body Subclass is
procedure Announce(self: in out Class)
is
begin
Put_Line("I am the subclass");
end;
end;
osuper: Superclass.Class;
osub: Subclass.Class;
vsuper: Superclass.Var := osuper;
vsub: Superclass.Var := osub; -- implicit upclass here
begin
vsuper.Announce;
vsub.Announce;
end;
(It's in ideone here: http://ideone.com/M79l0a Interesting sidenote. If you define subtype Var is Superclass.Var in the Prog package, and then use Var in the definitions of vsuper and vsub, ideone's Ada compiler crashes.)
Of course, like all indefinite types, once the variable has been initialised then its type cannot be changed. So I can assign any Subclass.Object to vsub, but I can't assign a Superclass.Object to it. And of course I'm physically copying the object rather than referring to an object elsewhere.
Implicitly upcasting accesses to class-wide types should be safe. Because assigning to a class-wide type does a runtime instance check to make sure that the physical type of the objects are compatible, it ought not to be possible to accidentally corrupt objects like you can in C++ --- see Overwriting an instance of a subclass with an instance of a superclass, for example. Therefore assigning to a dereferenced access shouldn't be a problem. However, it's 2100 at night and my brain has turned to sludge, so it's entirely possible that I'm missing something here. (Although given that when using anonymous accesses there aren't any problems, I suspect not.) Elucidation welcome...
Related
In the following code Mix_Card_Reader inherits from Mix_IO_Device, the latter being an abstract tagged record.
Previously it contained one Positive and two Stream_Access members. I'd like to alter the code so that it uses File_Type members instead.
The reason for that is that I want each instance of this type to be able to open and close its files as and when required, or not at all if need be.
The problem is that I cannot initialise this inheriting type because File_Type is a limited type. How can I write my Create_Mix_Card_Reader function to allow this?
.ads...
type Mix_IO_Device is abstract tagged limited
record
Block_Size : Positive;
Input_File : File_Type;
Output_File : File_Type;
end record;
type Mix_Card_Reader is new Mix_IO_Device with null record;
.adb...
function Create_Mix_Card_Reader return Mix_IO_Device_Access is
Ret : Mix_IO_Device_Access := new Mix_Card_Reader'(16, null, null);
begin
return Ret;
end Create_Mix_Card_Reader;
GNAT is complaining that I cannot pass null, null into the pair of File_Type members because they are not compatible of course, the nulls are a left-over from when this used to have Stream_Access members. It seems that I have to pass something in here but I don't want to have to prematurely open the files simply to placate the compiler.
What to do?
Edit:
I have a couple of obvious options:
use access File_Type instead (but I still have to maintain the opening/closing of the files elsewhere).
store all the File_Type objects in an array separately and just refer to them using Streams as before but this seems messy.
This should do the trick:
function Create_Mix_Card_Reader return Mix_IO_Device_Access is
Ret : Mix_IO_Device_Access := new Mix_Card_Reader'(
16, Input_Type => <>, Ouptut_Type => <>);
begin
return Ret;
end Create_Mix_Card_Reader;
The box notation is a placeholder for the default value. You need at least Ada 2005 to use it in aggregates and must not use positional notation, details are explained in the Ada 2005 Rationale. You can shorten the two assignments to others => <> if you want.
You don’t really need to initialize the File_Type variables, since they start off initialized (but not opened).
I got the impression that you didn’t start off using an access type? Try this (not an answer to the question as posed, but may still be useful):
with Ada.Text_IO; use Ada.Text_IO;
package Wossname is
type Mix_IO_Device is abstract tagged limited
record
Block_Size : Positive;
Input_File : File_Type;
Output_File : File_Type;
end record;
type Mix_Card_Reader is new Mix_IO_Device with null record;
function Create_Mix_Card_Reader return Mix_IO_Device'Class;
end Wossname;
I’m not 100% sure of the exact legality here, but I think this is "initializing in place":
package body Wossname is
function Create_Mix_Card_Reader return Mix_IO_Device'Class is
begin
return Ret : Mix_Card_Reader do
Ret.Block_Size := 16;
end return;
end Create_Mix_Card_Reader;
end Wossname;
and as you can see it compiles (and runs!) OK.
procedure Wossname.Test is
Reader : Mix_IO_Device'Class := Create_Mix_Card_Reader;
begin
begin
Create (Reader.Output_File, Name => "wossname.out", Mode => Out_File);
exception
when Use_Error =>
Open (Reader.Output_File, Name => "wossname.out", Mode => Out_File);
end;
Put (Reader.Output_File, "hi!");
Close (Reader.Output_File);
end Wossname.Test;
When an aggregate contains only one element, as below, positional notation results in a compilation error and we have to use named notation only. Why?
type singleton is record
v : integer;
end record;
v1 : singleton := (0);
results in the compiler message
check.adb:6:23: positional aggregate cannot have one component
check.adb:6:23: write instead "V => ..."
gnatmake: “check.adb" compilation error
whereas this is OK:
v2 : singleton := (v => 0);
Parentheses round an expression are redundant, so (0) = 0 and it's an integer not an array aggregate.
So, for the special case of a one-element aggregate, named association is required to distinguish an aggregate from a simple value.
Contrast this with (0,0) which can only be an aggregate; therefore there is no ambiguity.
Even though in the context of the question, it's obvious to a human programmer which is intended, that will not always be the case.
Consider a one-element aggregate in a multi-dimensional array which is one field of a record; there can be ambiguities that the compiler cannot resolve (at least, before reading a whole lot more of the source file!) and would make life pretty difficult for anyone reading the program.
You don't have to use named notation.
v1 : singleton := (others => 0);
This will assign 0 to all elements in v1 and the compiler will know that is not a number but an array instead.
If the record happen to have different types you could use
v1 : singleton := (others => <>);
I'm trying to use OOP and TDD inside of Oracle. Yes, I know that sounds crazy. And, I need some advice.
I'm writing a test for the following constructor method (simplified for the purposes of this question):
CONSTRUCTOR FUNCTION PERSON(p_pidm NUMBER, p_throw_exception NUMBER DEFAULT 0, p_program_id NUMBER DEFAULT 0)
RETURN SELF AS RESULT IS
BEGIN
-- Attach constructor parameters to internal attributes
self.throw_exception := p_throw_exception;
self.program_id := p_program_id;
-- TESTING STUDENT INSTANTIATION
self.student := NEW STUDENT(self.a_pidm);
RETURN;
END;
In the corresponding test, I'll need to verify that self.student is set to a valid instance of STUDENT. In other languages, I do this with a typeof method, but I'm not aware of one in PL/SQL.
So, the question is, does anyone know a function/procedure that I can pass a user-defined type into and get back its class/type name?
Thanks.
You probably want to use the IS OF <<type>> syntax.
Something like
IF l_variable IS OF( student )
THEN
<<do something>>
END IF;
Is there a way to ignore return values in Ada functions?
I have a function which imports from an Intrinsic.
subtype int32 is Interfaces.Interger_32;
function Intrinsic_Sync_Add_And_Fetch
(P : access int32; I : int32) return int32;
pragma Import(
Intrinsic,
Intrinsic_Sync_Add_And_Fetch,
"__sync_add_and_fetch_4");
If I want to use this in a procedure, I need to accept the return value or I will get a compiler error:
cannot use function Intrinsic_Sync_Add_And_Fetch in procedure call.
But, if I create a variable that simply takes the return value of the function and is never used then I get compiler warnings. Obviously, I'd rather avoid those.
I can't very well assign the value back to the value I'm adding to; this would undermine the point of the add operation being atomic.
There is the option of taking the value and doing something with it, like:
val := Intrinsic_Sync_Add_And_Fetch(...);
if val := 0 then null; end if;
It forces the code to compile without errors or warnings, but it seems stupid to me. How can I "get around" this language feature and safely ignore the return value?
Edit: What is __sync_add_and_fetch_4?
This is a built-in atomic operation available on Intel CPUs. As such, part of my Autoconf/Automake process would be deciding if the operation is available, and use a fallback implementation, which involves a critical section, if it's not.
You can read about this and similar operations in GCC's section on atomic builtins.
The __sync_add_and_fetch_4 does pretty much exactly what it says. In C, it would look something like this:
int32_t __sync_add_and_fetch_4(int32_t *ptr, int32_t value) {
*ptr += value;
return *ptr;
}
So it's an atomic addition operation, which returns the result of the addition. Basically, it's an atomic += operator. The _4 means that it takes a 4-byte integer.
Edit: I understand that I could probably just switch off that particular compiler warning, but that always feels dirty to me. If there's a solution available that allows me to continue using -Wall -Werror then I'd love to see it.
declare
dummy : constant return_type := my_function;
pragma Unreferenced (dummy);
begin null; end;
or write a wrapper procedure.
If you never want to reference the return value, why not declare the subprogram as a procedure? The value is going to be returned in a register, so throwing it away won’t cause a lot of grief. (I stand to be corrected on this one!)
subtype int32 is Interfaces.Integer_32;
procedure Intrinsic_Sync_Add_And_Fetch
(P : access int32; I : int32);
pragma Import(
Intrinsic,
Intrinsic_Sync_Add_And_Fetch,
"__sync_add_and_fetch_4");
You said you're only targeting the GNAT compiler. The GNAT User's Guide says:
Note that a special exemption applies to variables which contain any of the substrings DISCARD, DUMMY, IGNORE, JUNK, UNUSED, in any casing. Such variables are considered likely to be intentionally used in a situation where otherwise a warning would be given, so warnings of this kind are always suppressed for such variables.
So the simplest solution to your problem is :
unused := Intrinsic_Sync_Add_And_Fetch(...);
Though you might want to wrap that in a procedure if you are going to use it more than a couple of times :
procedure Intrinsic_Sync_Add_And_Fetch(P : access int32; I : int32) is
unused : int32;
begin
unused := Intrinsic_Sync_Add_And_Fetch(P : access int32; I : int32);
end Intrinsic_Sync_Add_And_Fetch;
i don't know of any way to ignore the return value of a function in Ada: the language has been especially designed to force you to store those return values.
personally, i would store the return value and ignore any warning regarding the use of the variable. anyway, the said warning is quite strange since the variable is indeed used to store the return value.
I'm a bit new to Oracle's PL/SQL (using 10g), I was wondering if there's a way to make a private method in an object type, as is often done for private helper methods in other languages (Java, C++, C#, etc...). I know it is possible to make private methods in packages, but I can't seem to find a way to do this for object types. I keep getting compiler errors telling me:
Error: PLS-00539: subprogram 'FOO' is declared in an object type body
and must be defined in the object type specification.
Ok, here's a potential solution that I tested very briefly, and it seems to work so far:
Create a parent object type that will marked as NOT FINAL and NOT INSTANTIABLE and then put all the private code in there. The private methods won't be truly private, but putting them in a type that is not final and not instantiable prevents them from being called. In the instantiable subtype, reference the "private" methods in the supertype through SELF.
Example:
create or replace type PrivateFoo under SuperFoo
(
member procedure setUpCommonFoo
) NOT INSTANTIABLE NOT FINAL;
create or replace type body PrivateFoo is
-- Member procedures and functions
member procedure setUpCommonFoo is
begin
SELF.someAttrib:='Some Common Default Value';
end;
end;
create or replace type Foo under PrivateFoo
(
CONSTRUCTOR FUNCTION Foo RETURN SELF AS RESULT,
CONSTRUCTOR FUNCTION Foo(fkey FooKey) RETURN SELF AS RESULT -- assume fkey is defined in SuperFoo, and FooKey type is defined somewhere else ;)
)
create or replace type body Foo is
--no-arg Constructor For basic Foo set up.
CONSTRUCTOR FUNCTION PartyConvertor RETURN SELF AS RESULT AS
BEGIN
self.setUpCommonFoo;
RETURN;
END;
--alt constructor for other situations...
CONSTRUCTOR FUNCTION PartyConvertor(fkey FooKey) RETURN SELF AS RESULT AS
BEGIN
self.setUpCommonFoo;
SELF.rarelyUsedAttrib:='Special Value!'; --just assume that someAttrib and rarelyUsedAttrib actually exist ;)
self.fkey := fkey;
RETURN;
END;
--Other Members go here...
end;
Now I have to admit, I don't really like this pattern. It seems awkward and kludgy. I'm probably going to just avoid Object Types as much as I can and stick to packages (or very simlpe object types). A package-as-fatory only helps me solve the private common code problem for constructors, not for other types of common code refactoring.
...unless there's a better way to work with Object Types.... anyone? anyone?
If you just need to use the subprogram (function/procedure) from one subprogram PL/SQL does allow you to nest a subprogram within another in the declaration block.
It's not as ideal as having private methods or functions but it might be worth a try before you go creating an inheritance hierarchy.
create or replace type body some_t
as
member function foo
return varchar2
as
function some_private_foo
return varchar2
as
begin
return 'Foo!';
end some_private_foo;
begin
return some_private_foo();
end foo;
end;
If you're on Oracle 12 you're in luck. You can create a package that only your type can code against using the ACCESSIBLE BY clause. In the example below the PL/SQL compiler will only allow code from FOO_T to reference FOO_PRIVATE_PKG.
CREATE OR REPLACE package foo_private_pkg
accessible by ( foo_t )
as
function some_private_foo ( object_in in out nocopy foo_t )
return varchar2;
end;
You can't have private methods in pl/sql objects, you can have polymorphism and inheritance but no encapsulation.
When you want encapsulation (private methods) you can use pl/sql packages.
All three at once isn't possible.
The answer to this is somewhat difficult, but I'll explain it as best as I can. First, it's not completely Oracle's fault; ANSI SQL defines a thing called Abstract Data Types (ADT's) which can be used to extend SQL. Oracle follows their specifications pretty well I think. Part of the lack of encapsulation comes with the difficulty of referencing and storing objects in SQL. I won't get into the details here however since I don't fully understand it myself.
ADT's are useful to give structure to your data, either in code or in tables, but they can't be very complex. For example, you cannot have Object A that has Object B which again has Object A. This cannot be stored in a SQL table. You can get around this by using REFs in Oracle (not sure how other vendors go about this) but that becomes another problem to solve in code.
I've found that ADT's are good for very simple structures and very simple member methods. Anything more elaborate requires packages. Often, I'll write a package that implements member methods for a given Object Type, since you can't call private methods inside an object.
It is a pain...