comp.lang.ada
 help / color / mirror / Atom feed
* Unknown discriminants with nested records
@ 2011-06-16 23:53 Simon Belmont
  2011-06-17  0:39 ` Adam Beneschan
  0 siblings, 1 reply; 7+ messages in thread
From: Simon Belmont @ 2011-06-16 23:53 UTC (permalink / raw)


New to ada (2005), and trying to figure out how to create records of
other records with unknown discriminants, e.g. like this:

package Test_Package is

    type Foo_Type (<>) is limited private;  -- some record with
unknown discriminants.

    t1: Foo_Type := MakeAFooType; -- works fine being built by a
function

    type BarType is
    record
     B1: Integer;
     -- other record elements
     B2: FooType := MakeAFooType;  -- Error, unconstrained subtype.
    end record;

private

    type FooType is limited
    record
     F1: Integer;
    end record;

end Test_Package;


The (<>) seems to be the defacto way to limit creation to a function,
yet it seemingly precludes you from using it in a nested record.  It
seems strange that there is the ability to declare a standalone object
of that type, but trying to nest one within another record throws an
"unconstrained subtype" error; if it knows the constraints one place,
why doesn't it know them several lines down?  I have tried all the
various syntaxes I can find, but nothing seems to work (though it
wouldn't be the first time there was some esoteric syntax that escaped
me).  So, that being said:

1. What is the appropriate syntax to do this?
2. If not, what is the technical reason and design decision why?
3. Are there workarounds?
4. Am I programming Ada the wrong way? (this happens often)
4. Can anyone point me to the section in the LRM that specifies this
either way?


Thanks



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

* Re: Unknown discriminants with nested records
  2011-06-16 23:53 Unknown discriminants with nested records Simon Belmont
@ 2011-06-17  0:39 ` Adam Beneschan
  2011-06-17  1:48   ` Simon Belmont
  2011-06-17  6:03   ` Jeffrey Carter
  0 siblings, 2 replies; 7+ messages in thread
From: Adam Beneschan @ 2011-06-17  0:39 UTC (permalink / raw)


On Jun 16, 4:53 pm, Simon Belmont <sbelmont...@gmail.com> wrote:
> New to ada (2005), and trying to figure out how to create records of
> other records with unknown discriminants, e.g. like this:
>
> package Test_Package is
>
>     type Foo_Type (<>) is limited private;  -- some record with
> unknown discriminants.
>
>     t1: Foo_Type := MakeAFooType; -- works fine being built by a
> function
>
>     type BarType is
>     record
>      B1: Integer;
>      -- other record elements
>      B2: FooType := MakeAFooType;  -- Error, unconstrained subtype.
>     end record;
>
> private
>
>     type FooType is limited
>     record
>      F1: Integer;
>     end record;
>
> end Test_Package;
>
> The (<>) seems to be the defacto way to limit creation to a function,
> yet it seemingly precludes you from using it in a nested record.  It
> seems strange that there is the ability to declare a standalone object
> of that type, but trying to nest one within another record throws an
> "unconstrained subtype" error; if it knows the constraints one place,
> why doesn't it know them several lines down?

To start with, it's helpful to post the actual source you tried.
Clearly this isn't, since you didn't spell Foo_Type consistently, but
the more important problem is that you've stuck the declarations T1
and BarType in the middle of Test_Package where they don't belong.
You can't declare T1 to be a Foo_Type at that point, because
Foo_Type's definition hasn't been finished.  Anyway, I'll assume that
T1 and BarType are actually declared in some other package
Test_Package_2 that with's and uses Test_Package.

The basic problem is this: When Test_Package_2 is elaborated, and it
elaborates the definition of T1, it will call MakeAFooType at that
point.  The discriminants (if any) of the value will then be
determined, so T1's discriminants will then be known.

But when BarType is elaborated, MakeAFooType isn't called.  The use of
MakeAFooType here is a *default* expression.  When you later declare

   B : BarType;

with no initial expression, MakeAFooType will be called at *that*
point, when B is elaborated.  It's also possible to declare B with an
initial expression like

   B : BarType := (B1 => 1, B2 => Make_A_Different_Kind_Of_Foo);

so that MakeAFooType wouldn't be called at all.  So here, the
constraints of the B2 component won't be what MakeAFooType would
return.

Hopefully that will answer your question about "why doesn't it know
[the constraints] several lines down".  It doesn't know because that
syntax doesn't actually result in a function call.

The problem here is that since the initial expression on the component
is just a *default* and may not be the actual initial expression for a
B2 component, the compiler thus doesn't know what constraints a B2
will have.  In theory, since it's possible for objects with different
constraints to have different sizes, the compiler thus may not know
how big a BarType will be, and I think that's the technical reason why
this sort of component type is prohibited.  (In practice, it might be
possible to make this work, since often the size of a FooType won't
vary; but it might be hard to do without introducing some incompatible
changes in the language.  I don't know if any possible language
changes have ever been discussed.)

As for how to get around this: if it were me, I'd just lose the (<>).
The (<>) was intended to define types with unknown discriminants;
using it to force initialization functions to be used, when you know
there won't really be any discriminants, is using this feature for a
purpose for which it wasn't intended, I think.  Someone clever found
that it works for this purpose, but obviously it doesn't work all the
time.  Maybe a language feature should be added that allows the user
to define a *definite* type that still isn't allowed to be
uninitialized.  Here's a possible way to force all uses of this type
to be initialized:

package Test_Package is
    type Foo_Type is limited private;
    ... etc. etc.
private
    function Blow_Up return Integer;
    type Foo_Type is limited
       record
          F1 : Integer := Blow_Up;
       end record;
end Test_Package;

where Blow_Up is defined like:

    function Blow_Up return Integer is
    begin
       raise Program_Error with "You forgot to initialize your
Foo_Type";
       return 0;  -- never executed
    end Blow_Up;

or something along those lines.  This won't be caught at compile time
(although some compilers may display a warning that Program_Error will
be raised), but a program that declares a variable that is or contains
an uninitialized Foo_Type will bomb.


> 4. Can anyone point me to the section in the LRM that specifies this
> either way?

3.6(10).  This is in the section on array types, but the restriction
on a component's type applies to record type components too.

                                 -- Adam



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

* Re: Unknown discriminants with nested records
  2011-06-17  0:39 ` Adam Beneschan
@ 2011-06-17  1:48   ` Simon Belmont
  2011-06-17 14:50     ` Robert A Duff
  2011-06-17  6:03   ` Jeffrey Carter
  1 sibling, 1 reply; 7+ messages in thread
From: Simon Belmont @ 2011-06-17  1:48 UTC (permalink / raw)


On Jun 16, 8:39 pm, Adam Beneschan <a...@irvine.com> wrote:
> On Jun 16, 4:53 pm, Simon Belmont <sbelmont...@gmail.com> wrote:
>
>
>
>
>
> > New to ada (2005), and trying to figure out how to create records of
> > other records with unknown discriminants, e.g. like this:
>
> > package Test_Package is
>
> >     type Foo_Type (<>) is limited private;  -- some record with
> > unknown discriminants.
>
> >     t1: Foo_Type := MakeAFooType; -- works fine being built by a
> > function
>
> >     type BarType is
> >     record
> >      B1: Integer;
> >      -- other record elements
> >      B2: FooType := MakeAFooType;  -- Error, unconstrained subtype.
> >     end record;
>
> > private
>
> >     type FooType is limited
> >     record
> >      F1: Integer;
> >     end record;
>
> > end Test_Package;
>
> > The (<>) seems to be the defacto way to limit creation to a function,
> > yet it seemingly precludes you from using it in a nested record.  It
> > seems strange that there is the ability to declare a standalone object
> > of that type, but trying to nest one within another record throws an
> > "unconstrained subtype" error; if it knows the constraints one place,
> > why doesn't it know them several lines down?
>
> To start with, it's helpful to post the actual source you tried.
> Clearly this isn't, since you didn't spell Foo_Type consistently, but
> the more important problem is that you've stuck the declarations T1
> and BarType in the middle of Test_Package where they don't belong.
> You can't declare T1 to be a Foo_Type at that point, because
> Foo_Type's definition hasn't been finished.  Anyway, I'll assume that
> T1 and BarType are actually declared in some other package
> Test_Package_2 that with's and uses Test_Package.
>
> The basic problem is this: When Test_Package_2 is elaborated, and it
> elaborates the definition of T1, it will call MakeAFooType at that
> point.  The discriminants (if any) of the value will then be
> determined, so T1's discriminants will then be known.
>
> But when BarType is elaborated, MakeAFooType isn't called.  The use of
> MakeAFooType here is a *default* expression.  When you later declare
>
>    B : BarType;
>
> with no initial expression, MakeAFooType will be called at *that*
> point, when B is elaborated.  It's also possible to declare B with an
> initial expression like
>
>    B : BarType := (B1 => 1, B2 => Make_A_Different_Kind_Of_Foo);
>
> so that MakeAFooType wouldn't be called at all.  So here, the
> constraints of the B2 component won't be what MakeAFooType would
> return.
>
> Hopefully that will answer your question about "why doesn't it know
> [the constraints] several lines down".  It doesn't know because that
> syntax doesn't actually result in a function call.
>
> The problem here is that since the initial expression on the component
> is just a *default* and may not be the actual initial expression for a
> B2 component, the compiler thus doesn't know what constraints a B2
> will have.  In theory, since it's possible for objects with different
> constraints to have different sizes, the compiler thus may not know
> how big a BarType will be, and I think that's the technical reason why
> this sort of component type is prohibited.  (In practice, it might be
> possible to make this work, since often the size of a FooType won't
> vary; but it might be hard to do without introducing some incompatible
> changes in the language.  I don't know if any possible language
> changes have ever been discussed.)
>
> As for how to get around this: if it were me, I'd just lose the (<>).
> The (<>) was intended to define types with unknown discriminants;
> using it to force initialization functions to be used, when you know
> there won't really be any discriminants, is using this feature for a
> purpose for which it wasn't intended, I think.  Someone clever found
> that it works for this purpose, but obviously it doesn't work all the
> time.  Maybe a language feature should be added that allows the user
> to define a *definite* type that still isn't allowed to be
> uninitialized.  Here's a possible way to force all uses of this type
> to be initialized:
>
> package Test_Package is
>     type Foo_Type is limited private;
>     ... etc. etc.
> private
>     function Blow_Up return Integer;
>     type Foo_Type is limited
>        record
>           F1 : Integer := Blow_Up;
>        end record;
> end Test_Package;
>
> where Blow_Up is defined like:
>
>     function Blow_Up return Integer is
>     begin
>        raise Program_Error with "You forgot to initialize your
> Foo_Type";
>        return 0;  -- never executed
>     end Blow_Up;
>
> or something along those lines.  This won't be caught at compile time
> (although some compilers may display a warning that Program_Error will
> be raised), but a program that declares a variable that is or contains
> an uninitialized Foo_Type will bomb.
>
> > 4. Can anyone point me to the section in the LRM that specifies this
> > either way?
>
> 3.6(10).  This is in the section on array types, but the restriction
> on a component's type applies to record type components too.
>
>                                  -- Adam- Hide quoted text -
>
> - Show quoted text -

Thank you very much, that was the exact answer I was looking for.  I
was supposing too much about the operation of the unknown discriminent
marker; I figured it was more of a "known-but-I-won't-tell-you-because-
you-don't-need-to-know" marker than an actual "unknown", and that the
compiler would be able to determine that the record in question
didn't, in fact, have any real discriminents.

It would certainly be nice if there were some sort of language feature
for this, especially considering that, like you said, the entire
community makes it standard practice to deliberity misuse an
operator.  But if it's been 16 years without one, I doubt there it
would be high on the priority list (though IMHO these priorities seem
skewed; some of the things on the 2012 list seem frivilous compared to
more basic things like making sure objects are properly initialized).

As for the suggestion, I am loathe to circumvent the compile-time
safety (after all, that seems like the whole point of using Ada).  My
next thought is to simply make it an access type in the record, and
then create a new Foo on the heap each time someone uses the
hypothetical MakeABar, though I'm not sure using the heap that much is
any better...

In either case, thank you again for your spot-on explanation.



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

* Re: Unknown discriminants with nested records
  2011-06-17  0:39 ` Adam Beneschan
  2011-06-17  1:48   ` Simon Belmont
@ 2011-06-17  6:03   ` Jeffrey Carter
  1 sibling, 0 replies; 7+ messages in thread
From: Jeffrey Carter @ 2011-06-17  6:03 UTC (permalink / raw)


On 06/16/2011 05:39 PM, Adam Beneschan wrote:
>
> The (<>) was intended to define types with unknown discriminants;

I thought it was initially created to fix the problem with generics, where in 
Ada 83 you could have

generic -- G
    type T is limited private;
    ...
package G is
    ...
end G;

package body G is
    ...
    V : T;
    ...
end G;

package I is new G (T => String, ...);

and it wasn't caught when you would like it. In Ada 95 and later, you have to 
have (<>) on the formal to have an indefinite actual, and the generic cannot 
have an uninitialized object of the type; without (<>), the actual must be 
definite; and the problem is caught when compiling the instantiation.

-- 
Jeff Carter
"Many times we're given rhymes that are quite unsingable."
Monty Python and the Holy Grail
57



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

* Re: Unknown discriminants with nested records
  2011-06-17  1:48   ` Simon Belmont
@ 2011-06-17 14:50     ` Robert A Duff
  2011-06-18  8:33       ` Yannick Duchêne (Hibou57)
  0 siblings, 1 reply; 7+ messages in thread
From: Robert A Duff @ 2011-06-17 14:50 UTC (permalink / raw)


Simon Belmont <sbelmont700@gmail.com> writes:

> Thank you very much, that was the exact answer I was looking for.  I
> was supposing too much about the operation of the unknown discriminent
> marker; I figured it was more of a "known-but-I-won't-tell-you-because-
> you-don't-need-to-know" marker than an actual "unknown", and that the
> compiler would be able to determine that the record in question
> didn't, in fact, have any real discriminents.

If it worked that way, then it would break the contract on the private
type.  That is, you could make changes to the private part that would
affect the legality of clients.  Ada is designed to avoid that sort
of privacy breaking -- the content of the private part should never
affect the legality of any code outside that package.

Jeff Carter is correct about the original reason for the (<>) feature
-- to fix the generic contract model, which was broken in Ada 83.

- Bob



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

* Re: Unknown discriminants with nested records
  2011-06-17 14:50     ` Robert A Duff
@ 2011-06-18  8:33       ` Yannick Duchêne (Hibou57)
  2011-06-20 16:16         ` Adam Beneschan
  0 siblings, 1 reply; 7+ messages in thread
From: Yannick Duchêne (Hibou57) @ 2011-06-18  8:33 UTC (permalink / raw)


Le Fri, 17 Jun 2011 16:50:52 +0200, Robert A Duff  
<bobduff@shell01.theworld.com> a écrit:
> Jeff Carter is correct about the original reason for the (<>) feature
> -- to fix the generic contract model, which was broken in Ada 83.
This also had the nice side effect to allow to define types forcing  
entities initialization.
(if that was really as Jeff said with Ada 83, that was indeed a sad hole)

-- 
“Syntactic sugar causes cancer of the semi-colons.”  [Epigrams on  
Programming — Alan J. — P. Yale University]
“Structured Programming supports the law of the excluded muddle.” [Idem]
Java: Write once, Never revisit



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

* Re: Unknown discriminants with nested records
  2011-06-18  8:33       ` Yannick Duchêne (Hibou57)
@ 2011-06-20 16:16         ` Adam Beneschan
  0 siblings, 0 replies; 7+ messages in thread
From: Adam Beneschan @ 2011-06-20 16:16 UTC (permalink / raw)


On Jun 18, 1:33 am, Yannick Duchêne (Hibou57)
<yannick_duch...@yahoo.fr> wrote:
> Le Fri, 17 Jun 2011 16:50:52 +0200, Robert A Duff  
> <bobd...@shell01.theworld.com> a écrit:> Jeff Carter is correct about the original reason for the (<>) feature
> > -- to fix the generic contract model, which was broken in Ada 83.
>
> This also had the nice side effect to allow to define types forcing  
> entities initialization.

A side effect that cannot be relied on, as is evident from this
thread.  As I tried to say earlier, if the language needs a feature to
allow the user to define a type for which objects must be initialized,
we should add a feature specifically for that purpose.  Trying to be
clever and pressing some other language feature into double duty seems
to come back to bite us most of the time.

As Jeff said, the problem in Ada 83 arose because the actual for a
generic formal could be a type for which you weren't supposed to be
able to declare an object (including a variable, record component,
array element type, etc.).  The (<>) feature was added not only to
generic formal types but also to private types; I'm not sure whether
the feature was added to private types just because it was being added
to generic formal types and someone thought it would make sense to add
it there too, or whether there were other considerations.  Anyway, I'm
guessing that the intent was more to *prevent* objects of the type
from being declared, much as it was in the generic formal case,
although one could still declare objects of an access to that type,
and one could still declare parameters of that type.  The fact that
you could still declare a variable of that type if it had an initial
value apparently gave some people a bright idea that the feature could
be used to force initialization.  But I don't think that was the
original intent; and when you use a feature for something for which it
wasn't intended, you run the risk that it won't always work for you,
which is what the OP found.

                            -- Adam



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

end of thread, other threads:[~2011-06-20 16:16 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2011-06-16 23:53 Unknown discriminants with nested records Simon Belmont
2011-06-17  0:39 ` Adam Beneschan
2011-06-17  1:48   ` Simon Belmont
2011-06-17 14:50     ` Robert A Duff
2011-06-18  8:33       ` Yannick Duchêne (Hibou57)
2011-06-20 16:16         ` Adam Beneschan
2011-06-17  6:03   ` Jeffrey Carter

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