comp.lang.ada
 help / color / mirror / Atom feed
* Subtype conformance... not what I was expecting.
@ 2006-07-26  3:02 Peter C. Chapin
  2006-07-26  6:58 ` Craig Carey
                   ` (3 more replies)
  0 siblings, 4 replies; 11+ messages in thread
From: Peter C. Chapin @ 2006-07-26  3:02 UTC (permalink / raw)


I'm experimenting with Ada's handling of access to subprogram types. I 
was surprised to discover that the following example does not work (I'm 
using GNAT GPL 2006):

procedure Check is
    subtype Narrow is Integer range -10..10;
    type Function_Ptr is access function(X : Narrow) return Integer;

    function F(Para : Integer) return Narrow is
    begin
       return Para;
    end F;

    G      : Function_Ptr := F'Access;
    Result : Integer;
begin
    Result := G(0);
end Check;

The compiler complains about the initialization of G with F'Access 
saying that it is not "subtype conformant." However, I believe this 
initialization would be type safe. Since F's argument types are super 
types of G's argument types, there is no context where G can be called 
that would violate the constraints on the underlying arguments of F. 
Similarly since F's return type is a subtype of G's return type, 
anything F might return would be acceptable as a return from G. I 
assumed that this was what subtype conformance was about, but apparently 
not.

In fact, GNAT appears to require the argument and return subtypes to 
match exactly. However, this seems overly restrictive. I'm curious about 
the rationale for this restriction.

Peter



^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: Subtype conformance... not what I was expecting.
  2006-07-26  3:02 Subtype conformance... not what I was expecting Peter C. Chapin
@ 2006-07-26  6:58 ` Craig Carey
  2006-07-26  7:54 ` Dmitry A. Kazakov
                   ` (2 subsequent siblings)
  3 siblings, 0 replies; 11+ messages in thread
From: Craig Carey @ 2006-07-26  6:58 UTC (permalink / raw)


On Tue, 25 Jul 2006 23:02:56 -0400, "Peter C. Chapin" wrote:

>procedure Check is
>   subtype Narrow is Integer range -10 .. 10;
>   type Function_Ptr is access function (X : Narrow) return Integer;
>
>   function F (Para : Integer) return Narrow is
>   begin
>      return Para;
>   end F;
>
>   G        : Function_Ptr := F'Access;
>   Result   : Integer;
>begin
>   Result := G (0);
>end Check;    --  Redon to pass GNAT style checking
...
>However, this seems overly restrictive. I'm curious about 
>the rationale for this restriction.


(1) In general, a reference to the Ada 95 Reference Manual might be
  obtained by using the Aonix Object Ada compiler.

(2) I guess that the current design is better. You have not said how
  that is better. 

  When there is a range subtype then it is up to the source code to
  make the subtype seem completely different, or seem very similar.
  Above, for your view to result, it is suggested that the subtype is
  similar. Eg.

  type Degrees is new Real;
  subtype Azimuth is Degrees range 0.0 .. 360.0;
  subtype Harmonic_5 is Azimuth range 0.0 .. 72.0;

(3) A workaround could be simple: have a record with 2 pointer fields.
  Also there is the union type (a GNAT 3.15p extension):
     pragma Unchecked_Union (Name) "corresponds exactly to a C union")

(4) Record keeping of the ARG is bad and the matter raised is judged
  minor, by me. If not minor, then maybe Jeffrey Carter would write
  (a man of what remains of Team-Ada).
  My last message about tagged types is proper while official Ada
  collapses. Different forces can lead to the high-priced seize-up of
  compilers of IBM.

(5) GNAT 3.15p (from the Internet) says:
   "error: not subtype conformant with declaration at line xxx"

(6) An argument might be that that safety wins and the convenience is
  worse than small.
  Another argument is to say that the source code is made to be
  ambiguous on whether the range checking of input and output of
  the function, occur inside of the function's code, or in the code
  at the site where it is called.


-- Craig Carey



^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: Subtype conformance... not what I was expecting.
  2006-07-26  3:02 Subtype conformance... not what I was expecting Peter C. Chapin
  2006-07-26  6:58 ` Craig Carey
@ 2006-07-26  7:54 ` Dmitry A. Kazakov
  2006-07-26 14:32   ` Peter C. Chapin
  2006-07-26  8:06 ` Georg Bauhaus
  2006-07-26 21:23 ` Adam Beneschan
  3 siblings, 1 reply; 11+ messages in thread
From: Dmitry A. Kazakov @ 2006-07-26  7:54 UTC (permalink / raw)


On Tue, 25 Jul 2006 23:02:56 -0400, Peter C. Chapin wrote:

> I'm experimenting with Ada's handling of access to subprogram types. I 
> was surprised to discover that the following example does not work (I'm 
> using GNAT GPL 2006):
> 
> procedure Check is
>     subtype Narrow is Integer range -10..10;
>     type Function_Ptr is access function(X : Narrow) return Integer;
> 
>     function F(Para : Integer) return Narrow is
>     begin
>        return Para;
>     end F;
> 
>     G      : Function_Ptr := F'Access;
>     Result : Integer;
> begin
>     Result := G(0);
> end Check;
> 
> The compiler complains about the initialization of G with F'Access 
> saying that it is not "subtype conformant." However, I believe this 
> initialization would be type safe. Since F's argument types are super 
> types of G's argument types, there is no context where G can be called 
> that would violate the constraints on the underlying arguments of F. 
> Similarly since F's return type is a subtype of G's return type, 
> anything F might return would be acceptable as a return from G. I 
> assumed that this was what subtype conformance was about, but apparently 
> not.
> 
> In fact, GNAT appears to require the argument and return subtypes to 
> match exactly. However, this seems overly restrictive. I'm curious about 
> the rationale for this restriction.

The semantic of "subtype" in Ada is "same type." So if you allow Narrow to
appear in place of Integer, you must also allow the reverse:

    subtype Narrow is Integer range -10..10;
    type Function_Ptr is access function(X : Narrow) return Narrow;
    function F(Para : Integer) return Integer;
        -- Constraint_Error-unsafe

If you wanted a one-way road, you'd need function(X : Narrow) return
Integer be an override of some primitive subprogram of Integer. That would
make you able to legally judge about conformance to *class* (Narrow <:
Integer). But that works for only operations defined on the class.
Unfortunately Ada does not have either Integer'Class or Narrow'Class.

-- 
Regards,
Dmitry A. Kazakov
http://www.dmitry-kazakov.de



^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: Subtype conformance... not what I was expecting.
  2006-07-26  3:02 Subtype conformance... not what I was expecting Peter C. Chapin
  2006-07-26  6:58 ` Craig Carey
  2006-07-26  7:54 ` Dmitry A. Kazakov
@ 2006-07-26  8:06 ` Georg Bauhaus
  2006-07-26 14:15   ` Peter C. Chapin
  2006-07-26 21:23 ` Adam Beneschan
  3 siblings, 1 reply; 11+ messages in thread
From: Georg Bauhaus @ 2006-07-26  8:06 UTC (permalink / raw)


On Tue, 2006-07-25 at 23:02 -0400, Peter C. Chapin wrote:

> procedure Check is
>     subtype Narrow is Integer range -10..10;
>     type Function_Ptr is access function(X : Narrow) return Integer;
> 
>     function F(Para : Integer) return Narrow is
> ...
>     G      : Function_Ptr := F'Access;

> The compiler complains about the initialization of G with F'Access 
> saying that it is not "subtype conformant." ...
>  However, this seems overly restrictive.

Would you still think the same in the following variation? (I don't know
the rationale for the restriction but then I wasn't surprised :-)

procedure Check is
   subtype Above_Zero is Integer range 1 .. 95; -- from liquid to for tea
   subtype Below_Zero is Integer range -100 .. -5; -- really solid

   type Frozen_Ptr is access function(X: Below_Zero) return Integer;
   type Liquid_Ptr is access function(X: Above_Zero) return Integer;

   function Whirl(Para: Integer) return Above_Zero is
      begin
         return Para;
      end;

   function Cut(Para: Integer) return Below_Zero is
      begin
         return Para;
      end;

   G: Frozen_Ptr := Whirl'access;
   Ice: Below_Zero;
begin
   Ice := G(0);
end Check;


Above_Zero and Below_Zero let me think of different logical types, really.
The set of values in the respective subtypes is disjoint.

I won't want to deliver hot water to/from a function that needs/returns
cold ice. In my view, this will be a contract violation, in this case
at least. 

Maybe the restriction helps avoid specimens of "I'm the programmer, and
I know that the program is right, even though it's not obvious".


-- Georg





^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: Subtype conformance... not what I was expecting.
  2006-07-26  8:06 ` Georg Bauhaus
@ 2006-07-26 14:15   ` Peter C. Chapin
  0 siblings, 0 replies; 11+ messages in thread
From: Peter C. Chapin @ 2006-07-26 14:15 UTC (permalink / raw)


Georg Bauhaus wrote:

> procedure Check is
>    subtype Above_Zero is Integer range 1 .. 95; -- from liquid to for tea
>    subtype Below_Zero is Integer range -100 .. -5; -- really solid
> 
>    type Frozen_Ptr is access function(X: Below_Zero) return Integer;
>    type Liquid_Ptr is access function(X: Above_Zero) return Integer;
> 
>    function Whirl(Para: Integer) return Above_Zero is
>       begin
>          return Para;
>       end;
> 
>    function Cut(Para: Integer) return Below_Zero is
>       begin
>          return Para;
>       end;
> 
>    G: Frozen_Ptr := Whirl'access;
>    Ice: Below_Zero;
> begin
>    Ice := G(0);
> end Check;

In the statement Ice := G(0) you are assigning an Integer to a 
Below_Zero. Naturally a runtime check would be necessary to verify the 
constraint and Constraint_Error would be raised if there was a 
violation. It's no different than

	Hot : Integer := 1000;
	...
	Ice := Hot;

> I won't want to deliver hot water to/from a function that needs/returns
> cold ice. In my view, this will be a contract violation, in this case
> at least.

G accesses a function returning Integer. Putting Whirl'Access into G is 
safe because Whirl can't return anything that would violate G's 
contract. What you subsequently do with what G returns is another matter.

Peter



^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: Subtype conformance... not what I was expecting.
  2006-07-26  7:54 ` Dmitry A. Kazakov
@ 2006-07-26 14:32   ` Peter C. Chapin
  2006-07-26 15:38     ` Georg Bauhaus
  2006-07-26 18:40     ` Dmitry A. Kazakov
  0 siblings, 2 replies; 11+ messages in thread
From: Peter C. Chapin @ 2006-07-26 14:32 UTC (permalink / raw)


Dmitry A. Kazakov wrote:

>> procedure Check is
>>     subtype Narrow is Integer range -10..10;
>>     type Function_Ptr is access function(X : Narrow) return Integer;
>>
>>     function F(Para : Integer) return Narrow is
>>     begin
>>        return Para;
>>     end F;
>>
>>     G      : Function_Ptr := F'Access;
>>     Result : Integer;
>> begin
>>     Result := G(0);
>> end Check;
>>

[snip]

> The semantic of "subtype" in Ada is "same type." So if you allow Narrow to
> appear in place of Integer, you must also allow the reverse:
> 
>     subtype Narrow is Integer range -10..10;
>     type Function_Ptr is access function(X : Narrow) return Narrow;
>     function F(Para : Integer) return Integer;
>         -- Constraint_Error-unsafe
> 
> If you wanted a one-way road, you'd need function(X : Narrow) return
> Integer be an override of some primitive subprogram of Integer. That would
> make you able to legally judge about conformance to *class* (Narrow <:
> Integer). But that works for only operations defined on the class.
> Unfortunately Ada does not have either Integer'Class or Narrow'Class.

Hmmm. I'll have to think about this a little; I don't quite follow you 
right now. Consider this

	subtype Narrow is Integer range -10..10;
	X : Integer;
	Y : Narrow;
	...
	X := Y;
	Y := X;  -- Might raise Constraint_Error.

Even though one statement is Constraint_Error-unsafe, both are legal. 
However, I don't think this is the point I was trying to make. When I do 
G := F'Access (in my original example) it is type safe in that I can't 
do anything with G that would cause a problem for F. I might get a 
Constraint_Error when G's arguments are evaluated or when G's return 
value is used but no such error can occur because the actual underlying 
function is actually F.

Using the notation common in functional languages, let F : t1 -> t2 and 
G : t1' -> t2'. Then t1 -> t2 is a subtype of t1' -> t2' (that is, 
t1->t2 <: t1'->t2') iff t2 <: t2' and t1' <: t1. In this case I realize 
that G is an access type but it "feels" like a function when it is used. 
Since I'm trying to use F where G is expected and since F and G have the 
proper subtype relationship to each other one might suppose that the 
above rule would be obeyed.


Peter



^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: Subtype conformance... not what I was expecting.
  2006-07-26 14:32   ` Peter C. Chapin
@ 2006-07-26 15:38     ` Georg Bauhaus
  2006-07-27  0:02       ` Peter C. Chapin
  2006-07-26 18:40     ` Dmitry A. Kazakov
  1 sibling, 1 reply; 11+ messages in thread
From: Georg Bauhaus @ 2006-07-26 15:38 UTC (permalink / raw)


On Wed, 2006-07-26 at 10:32 -0400, Peter C. Chapin wrote:

Sounds like the technical/mathematical versus practical/real-world
co(ntra)variance issue?

> Using the notation common in functional languages, let F : t1 -> t2 and 
> G : t1' -> t2'. Then t1 -> t2 is a subtype of t1' -> t2' (that is, 
> t1->t2 <: t1'->t2') iff t2 <: t2' and t1' <: t1. In this case I realize 
> that G is an access type but it "feels" like a function when it is used. 
> Since I'm trying to use F where G is expected and since F and G have the 
> proper subtype relationship to each other one might suppose that the 
> above rule would be obeyed.

Let F : Person -> Room and G : Girl -> Girls_Dormitory.
Then Person -> Room is a subtype of Girl -> Girls_Dormitory (that is,
Person->Room <: Girl->Girls_Dormitory) iff Room <: Girls_Dormitory
and Girl <: Person.

Let F : Girl -> Girls_Dormitory and G : Person -> Room.
Then Girl -> Girls_Dormitory is a subtype of Person -> Room (that is,
Girl->Girls_Dormitory <: Person->Room) iff Girls_Dormitory <: Room
and Person <: Girl.

Now add Boy.
Granted Persons and Rooms are distinct type classes, but are the
transcriptions correct otherwise? (The example is from Meyer's OOSC2
as I remember it, but I don't have the book here to check.)





^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: Subtype conformance... not what I was expecting.
  2006-07-26 14:32   ` Peter C. Chapin
  2006-07-26 15:38     ` Georg Bauhaus
@ 2006-07-26 18:40     ` Dmitry A. Kazakov
  2006-07-27  0:00       ` Peter C. Chapin
  1 sibling, 1 reply; 11+ messages in thread
From: Dmitry A. Kazakov @ 2006-07-26 18:40 UTC (permalink / raw)


On Wed, 26 Jul 2006 10:32:20 -0400, Peter C. Chapin wrote:

> Dmitry A. Kazakov wrote:
> 
>>> procedure Check is
>>>     subtype Narrow is Integer range -10..10;
>>>     type Function_Ptr is access function(X : Narrow) return Integer;
>>>
>>>     function F(Para : Integer) return Narrow is
>>>     begin
>>>        return Para;
>>>     end F;
>>>
>>>     G      : Function_Ptr := F'Access;
>>>     Result : Integer;
>>> begin
>>>     Result := G(0);
>>> end Check;
>>>
> 
> [snip]
> 
>> The semantic of "subtype" in Ada is "same type." So if you allow Narrow to
>> appear in place of Integer, you must also allow the reverse:
>> 
>>     subtype Narrow is Integer range -10..10;
>>     type Function_Ptr is access function(X : Narrow) return Narrow;
>>     function F(Para : Integer) return Integer;
>>         -- Constraint_Error-unsafe
>> 
>> If you wanted a one-way road, you'd need function(X : Narrow) return
>> Integer be an override of some primitive subprogram of Integer. That would
>> make you able to legally judge about conformance to *class* (Narrow <:
>> Integer). But that works for only operations defined on the class.
>> Unfortunately Ada does not have either Integer'Class or Narrow'Class.
> 
> Hmmm. I'll have to think about this a little; I don't quite follow you 
> right now. Consider this
> 
> 	subtype Narrow is Integer range -10..10;
> 	X : Integer;
> 	Y : Narrow;
> 	...
> 	X := Y;
> 	Y := X;  -- Might raise Constraint_Error.
> 
> Even though one statement is Constraint_Error-unsafe, both are legal. 
> However, I don't think this is the point I was trying to make. When I do 
> G := F'Access (in my original example) it is type safe in that I can't 
> do anything with G that would cause a problem for F. I might get a 
> Constraint_Error when G's arguments are evaluated or when G's return 
> value is used but no such error can occur because the actual underlying 
> function is actually F.

Yes it is type-safe, and one could allow type-conformant subroutines as
actuals. This would probably impose some minor distributed overhead, I can
only guess if that was the reason. A similar case is requeue statement,
which also requires subtype-conformance.

My point was just, that there is no good choice in-between. It should be
either type- or subtype-conformant.

> Using the notation common in functional languages, let F : t1 -> t2 and 
> G : t1' -> t2'. Then t1 -> t2 is a subtype of t1' -> t2' (that is, 
> t1->t2 <: t1'->t2') iff t2 <: t2' and t1' <: t1. In this case I realize 
> that G is an access type but it "feels" like a function when it is used. 
> Since I'm trying to use F where G is expected and since F and G have the 
> proper subtype relationship to each other one might suppose that the 
> above rule would be obeyed.

Yes, this is close to my view on the issue of subtyping.

If t1' is substitutable (=compiler accepts it) in F as an in parameter,
that means, in my view, that t1' inherits F from t1 and so t1' <in: t1 (1).
Whether substitution causes Constraint_Error or not is no matter. The
result is just an out parameter. So t2' <out: t2. Both combined are
equivalent to:

t1' -> t2'  inherits F 

The inherited F is F', with a different profile. G is type conformant to
F', but not to F. So it should be substitutable for t1'Class -> t2'Class,
but not for t1 -> t2.

This is a possible way to unify "subtype" and "is new ... with" under one
roof, and also have a fine control over substitution (t1 vs. t1'Class).

-------------
(1)  We have to distinguish <in:, <out:, <inout:, and, well, <access:.

-- 
Regards,
Dmitry A. Kazakov
http://www.dmitry-kazakov.de



^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: Subtype conformance... not what I was expecting.
  2006-07-26  3:02 Subtype conformance... not what I was expecting Peter C. Chapin
                   ` (2 preceding siblings ...)
  2006-07-26  8:06 ` Georg Bauhaus
@ 2006-07-26 21:23 ` Adam Beneschan
  3 siblings, 0 replies; 11+ messages in thread
From: Adam Beneschan @ 2006-07-26 21:23 UTC (permalink / raw)


Peter C. Chapin wrote:
> I'm experimenting with Ada's handling of access to subprogram types. I
> was surprised to discover that the following example does not work (I'm
> using GNAT GPL 2006):
>
> procedure Check is
>     subtype Narrow is Integer range -10..10;
>     type Function_Ptr is access function(X : Narrow) return Integer;
>
>     function F(Para : Integer) return Narrow is
>     begin
>        return Para;
>     end F;
>
>     G      : Function_Ptr := F'Access;
>     Result : Integer;
> begin
>     Result := G(0);
> end Check;
>
> The compiler complains about the initialization of G with F'Access
> saying that it is not "subtype conformant." However, I believe this
> initialization would be type safe. Since F's argument types are super
> types of G's argument types, there is no context where G can be called
> that would violate the constraints on the underlying arguments of F.
> Similarly since F's return type is a subtype of G's return type,
> anything F might return would be acceptable as a return from G. I
> assumed that this was what subtype conformance was about, but apparently
> not.
>
> In fact, GNAT appears to require the argument and return subtypes to
> match exactly. However, this seems overly restrictive. I'm curious about
> the rationale for this restriction.

I don't know what the language designers' rationale was for requiring
the subtypes to statically match, although I do think that trying to
define a one-way "subtype conformance" that requires subtypes for IN
parameters and function results to be "statically a subset" of another
type may just be too complex to be worth the effort if it's not needed
by real programs.

However, I do foresee a possible problem if the rules were loosened.
In your example, if a function has a parameter of subtype "Narrow", a
compiler could work things out so that the parameter could be passed in
an 8- or 16-bit register, or something like that.  This probably
wouldn't make a difference in a simple example like yours.  But on some
processors, and if there were a larger number of parameters, this sort
of optimization *could* make a difference.  But a compiler wouldn't be
able to do this if there were a possibility that, in some other library
package (say), there were an access-to-subprogram type that were
compatible with *both* this function *and* a similar function whose
parameter has type "Integer" (assuming Integer is 32 bits).  Under the
current language rules, I don't think this is a problem.

                             -- Adam




^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: Subtype conformance... not what I was expecting.
  2006-07-26 18:40     ` Dmitry A. Kazakov
@ 2006-07-27  0:00       ` Peter C. Chapin
  0 siblings, 0 replies; 11+ messages in thread
From: Peter C. Chapin @ 2006-07-27  0:00 UTC (permalink / raw)


Dmitry A. Kazakov wrote:

> (1)  We have to distinguish <in:, <out:, <inout:, and, well, <access:.

Yes I can see that 'out' or 'inout' parameters complicate things. This 
alone convinces me that trying to do what I was suggesting is probably 
not worth the trouble.

Thanks for your replies. I will reflect on them.

Peter



^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: Subtype conformance... not what I was expecting.
  2006-07-26 15:38     ` Georg Bauhaus
@ 2006-07-27  0:02       ` Peter C. Chapin
  0 siblings, 0 replies; 11+ messages in thread
From: Peter C. Chapin @ 2006-07-27  0:02 UTC (permalink / raw)


Georg Bauhaus wrote:

> Let F : Person -> Room and G : Girl -> Girls_Dormitory.
> Then Person -> Room is a subtype of Girl -> Girls_Dormitory (that is,
> Person->Room <: Girl->Girls_Dormitory) iff Room <: Girls_Dormitory
> and Girl <: Person.
> 
> Let F : Girl -> Girls_Dormitory and G : Person -> Room.
> Then Girl -> Girls_Dormitory is a subtype of Person -> Room (that is,
> Girl->Girls_Dormitory <: Person->Room) iff Girls_Dormitory <: Room
> and Person <: Girl.

Yes, this is what I was getting at: covariance and contravariance. I 
think what you say above is correct. However, it is not intuitive for 
Room <: Girls_Dormitory or for Person <: Girl.

Peter



^ permalink raw reply	[flat|nested] 11+ messages in thread

end of thread, other threads:[~2006-07-27  0:02 UTC | newest]

Thread overview: 11+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2006-07-26  3:02 Subtype conformance... not what I was expecting Peter C. Chapin
2006-07-26  6:58 ` Craig Carey
2006-07-26  7:54 ` Dmitry A. Kazakov
2006-07-26 14:32   ` Peter C. Chapin
2006-07-26 15:38     ` Georg Bauhaus
2006-07-27  0:02       ` Peter C. Chapin
2006-07-26 18:40     ` Dmitry A. Kazakov
2006-07-27  0:00       ` Peter C. Chapin
2006-07-26  8:06 ` Georg Bauhaus
2006-07-26 14:15   ` Peter C. Chapin
2006-07-26 21:23 ` Adam Beneschan

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox