A few questions about inheritance and controlling parameters/result

Hi, I have a few technical questions, mostly about dynamic dispatching and inheritance.

A call on a dispatching operaiton shall not have both dynamically tagged and statically tagged controlling operands". Reason: This restriction is intended to minimize confusion between whether the dynamically tagged operands are implicitly converted to, or tag checked against the specific type of the statically tagged operand(s).

What is this rule ? I would have supposed a runtime check would rather ascertain that the dynamic and static variables match, instead of ruling out the combination a priori.

The book includes a paragraph on controlling results, and how tag-indeterminate expression are determined by context. It makes sense that you need neighboring static operands to resolve this. But if this works already, why do people talk of banning controlling results ?

What is the purpose of this rule: only immutably limited types can have anonymous access discriminants designating a limited type. But it does not apply to discriminants of a named access type.

package main is
	type KK is limited null record;
	type KKAccess is access KK;
	type BB (J: access KK) is null record;
	function create return BB is (J => new KK); -- No
	type CC (J: KKAccess) is null record;
	function create return CC is (J => new KK); -- Yes
end main2;

Am I right that in the following, it is impossible to use anything in Complex_Functions at all ? It feels wrong to have access to a package but not to the types it references !

with Ada.Numerics.Generic_Complex_Types;
with Ada.Numerics.Generic_Complex_Elementary_Functions;
use Ada.Numerics;
generic
	type NewF is new Float;
	with package Complex_Types is new Generic_Complex_Types (NewF);
	with package Complex_Functions is new Generic_Complex_Elementary_Functions (<>);
package main2 is
	A: Complex_Types.Complex := Compose_From_Cartesian (2.3,4.9);
	B: Complex_Types.Complex := Sqrt (A);
-- not same type, can I access whatever type Complex_Functions has access to ?
end main2;

Why can’t abstract types have/inherit non-abstract functions, while they inherit procedures ? I get that you can’t return a value of an abstract type but you can’t instanciate it to use in a procedure call either. The compiler and runtime should know which types are abstract and which aren’t anyway, no?

package P2 is
	type T1 is tagged record
		A: Integer;
	end record;
	function Create return T1 is (A => 3);
	type T2 is abstract new T1 with null record;
	type T3 is new T2 with null record; -- Create's body not inherited
end P2;

I can’t make (non) overriding indicators work:

package P1 is
	type T1 is tagged record
		A: Integer;
	end record;
	procedure Works (K: in out T1) with Import;
	type T2 is new T1 with null record;
	not overriding procedure Works (K: in out T2);
end P1;

p1.ads:7:24: error: subprogram “Works” overrides inherited operation at line 6

Thanks.

So long no full multiple dispatch is allowed early detection is what we need.

See the Annotated Ada Reference Manual. It has an explanation for this. AARM 3.7 (10)

Controlling result is covariance. Contravariance is a type error unless class-wide because under extension there is no obvious way to ensure substitutability for a result. This does not apply to out arguments as you cannot change the tag.

Is incorrect. It should have been:

generic
   type NewF is new Float;
   with package Complex_Types is new Generic_Complex_Types (NewF);
   with package Complex_Functions is new Generic_Complex_Elementary_Functions (Complex_Types);
package main2 is

Or going bottom-up:

generic
   with package Complex_Functions is
      new Generic_Complex_Elementary_Functions (<>);
package main2 is
   package Complex_Types renames Complex_Functions.Complex_Types;
   subtype NewF is Complex_Types.Real;

Generics are such a fun!

Because you must override. Not overriding a covariant result is type error because there is no way for the compiler to safely compose the override in order to inherit.

not overriding is meant to protect you from overriding something you think were a new operation. It is not to force contravariance, which is a type error.

1 Like

I don’t know what covariance means… I would need practical example lest it be even more opaque as the AARM :stuck_out_tongue:

So I wasn’t wrong, you can indeed see subprograms but not see the type they use. Very weird corner case.
But since you can query a package visible in another package like that, isn’t the following useless ?

with Ada.Numerics.Generic_Complex_Types;
with Ada.Numerics.Generic_Complex_Elementary_Functions;
generic
	use Ada.Numerics;
	with package Complex_Types is new Generic_Complex_Types (<>);
	with package Complex_Functions is new Generic_Complex_Elementary_Functions(Complex_Types);
package Generic_Complex_Vectors;

Straight from the book. Not the first I see it either. I don’t see the point in requiring a package that already exists and which you can query from the first formal parameter. It only makes instanciations more complicated.

Covariance means that the argument’s/result’s type changes the type upon derivation. Primitive operations are covariant in controlling arguments:

type T is tagged null record;
procedure Foo (X : T);
type S is new T with null record; -- derivation
-- Now we have Foo (X : S) overridden or inherited per covariance

Contravariance is when the type does not change. As an example consider Ada’s subtypes:

subtype Positive is Integer range 1..Integer'Last; -- derivation
-- "+" is contravariant the arguments and the result are still Integer'Base

No, the point is about named equivalence Ada uses. When you write:

package Complex_Types is new Generic_Complex_Types (<>);
package Complex_Functions is new Generic_Complex_Elementary_Functions (<>);

you get totally unrelated set of types from each. You must bind them to the same actual parameters to make these types related. In particular Complex_Functions has its Complex_Types. You must say the compiler that these two instances of Complex_Types are same:

package Complex_Functions is new Generic_Complex_Elementary_Functions (Complex_Types);

This is different from C++ where template instances are all same when parameters are.
This example is trivial. Things start getting interesting with diamond diagrams, when generic package D uses generics C and B and these in turn use A. You must ensure that A’s instance is same

This does not compute. Note generics are a meta language. A formal package is not a package. You can pass a proper package (instance) as the actual. Why should language care which instances exist anywhere? You tell it.

BTW, there is a third way to bind generic packages - child generic packages. If you use generics in a non-trivial way would you oscillate between these three. There is no way to select the right way in advance, you would have to rework all design if stuck…