* 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 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
* 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
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