There is a lot more. You need to take into account indefinite types. These cannot have instances being indefinite. You can create a definite subtype of by setting a constraint on it. You can write operations in terms of unconstrained types and inherit them upon constraining.
The type system term for this is specialization.
You can use subtypes in all places where some subset is specified, e.g. in loops, in case statements, in variant records. The compiler would drop checks if you pass a narrower subtype. It is an easy way to hint the compiler and the reader. For example an access type (pointer) to a constrained array will use no bounds. If you pass a constrained array as an unconstrained argument, bounds are added. Furthermore, you can fix hardware issues, like IEEE 754 by excluding non-numbers.
A SW engineering view on subtype S is as a problem-specific “type” while S’Base were the underlying machine-specific implementation.
In my opinion, John Barnes’ book is the bible on Ada. It is really good. His first pass at the start of the book is an introduction to Ada. It pretty much covers all that you actually need to know about Ada. Then it is followed by much more detail. You can easily jump to those more detailed sections if you already know a bit about Ada. But if you know nothing about Ada, that first section gives you that broad overview that you need.
Don’t worry about only learning half the key words. His book is way better than that, which you would expect from one of the developers of the language (see here).
I was a practised Ada developer when I first purchased the book (back then it was the Ada 95 version) and I got it to get me quickly up to speed on Ada 95 from Ada 83. The book was a gold mine! I now have the nearly current version. In both cases, it has been worth every cent. So if you want just one book, then I would say that this is the one to get.
As an aside, it seems to me it seems that Ada does guarantee its standard library, within reason. For example, the RM states at the beginning of Annex A,
This Annex contains the specifications of library units that shall be provided by every implementation.
Maybe someone who knows better will correct me on that, though. (I’m not even an Ada amateur IMHO; more of a dilettante.)
That aside, I think I understand what you’re saying. FWIW I used to use C++ a lot (still wouldn’t call myself an expert though) and still use it occasionally (when work requires it). My snark on the C++ standards committee and compiler writers relates to a horrific experience I had using std::visit with std::variant, so on the one hand that’s outside the language itself, but on the other hand it’s the only way I’m aware of to get C++ to verify at compile time that you’ve checked all the variants of … well, it’s not an enumeration, but std::variant is about the closest you can get to a true enumeration in C/C++. g++ will indeed report that you’ve overlooked a variant; it will also surround that error report (on both sides!) by screenful after screenful of nearly incomprehensible complaints pointing to lines in the standard library.
Yeah… I don’t regret switching to Ada for my personal projects at all.
Anywa, I would heartily recommend the Barnes book. I borrowed it from my university library when I was first learning Ada, and found it wonderful. I want to buy the new book on Ada 2022, but I haven’t gotten round to it yet.
As someone who self-studied into the ability to program something non-trivial in over a dozen languages, I don’t think you should worry too much about not learning all of Ada’s constructs in one go, but the Barnes text definitely seems comprehensive, and I was able to move around it pretty easily as needed, so if it seems it’s missing something, that’s probably because of Ada’s unique vocabulary. If you can swing it, buy the book and then, if you don’t like it, let me know & I’ll tell you my address & I’ll be glad to take it off your hands. Maybe even for money.
I think you are thinking of the “annotated” Ada reference manual (AARM). The Rationale documents are not the RM, they just go into discussion and give examples of why some the changes came about and how the language features might be used.
The pages I linked will have links to the Rationale documents if you want to take a look. They will also have links to the annotated Ada reference manual as well, so just make sure to check the links for the correct one.
Partially. Another aspect is just how useful restriction is; consider the following:
Subtype Natural is Integer range 0..Integer'Last;
Subtype Positive is Natural range Natural'Succ(Natural'First)..Natural'Last;
Now you can say things like if X in Positive and if Y not in Natural, as well as using the constrained subtype in parameters and function-results, allowing (a) self-documenting code that is compiler-enforced/-checked, and (b) this makes the code much more amiable to editing. For example we could say Type Example is Range 0..127; before Natural and replace the Integer in Natural’s definition with Example and all your dependent code is now using Example. (i.e. something like recompiling for a different-sized architecture could be as simple as that, or if they steered clear of Standard.Integer then simply recompile.)
I’ve been working on this for a while. Basically, the only way to know is to put it to practice. There are patterns to it’s hallucinations. Basically, it’s like googling: if you have to go to page 2, not likely you’ll find what you’re looking for. Similarly with chatgpt, if something feels like something that’ll get you to page 2, you better double-check it.
You can conversely safely ask it for the side effects of any common drug. Ask it about a new drug, it’ll hallucinate. The reason seems to be that chatgpt is determined to give you an answer, but it’s also really big on preserving resources. So in other words, imagine it like a friend googling a topic for you: some things it doesn’t have to google, other things it’ll stick to page 1 of google searches and lie if it has to get to page 2.
So i was right to doubt my conclusions. Thank you.Finally figured out the syntax to quote on these boards (i knew it was different, but i didn’t realize how similar), as i hit my newbie post limit. Time to shove into gedit until it’s lifted!
So i was right to doubt my conclusions. Thank you. I suspect i still don’t fully get it, but it seems this is one of the more unique features of Ada. I’ve seen fancy things that are like constraints but not quite constraints (predicates, or are they considered constraints since they still constraint it but with a different syntax?) that seem to be particularly useful. For example, apparently if i had to pull in c functions that return 0 on error, I could use this to error check automatically instead of using my famous “1-line if-crashes” that got me in hot water over on X recently (apparently some people don’t like the idea of if idioms for error checking null returns).
I guess i’ll have to get a copy when i get paid again (christmas). I’m not afraid of multiple sources, but there would have to be a clear path.
Technically so does C and C++, but Ada more or less expects it while C++ does not. I think this goes more with how Ada tries to treat it as built in, but still importing it, while C/C++ tries to treat everything as included code. Now really fun is when i tried to use C++ on DJGPP for DOS and discovered new, delete, etc required i implement alot of functions myself. To be fair there was a reason i was not linking against the C++ runtime but i forget why exactly that was. For Ada, there’s a stronger emphasis in having it where possible.
Yeah, this is a huge problem with where C++ is going. Standard library can’t be promised, but it’s how you get that sort of thing “built in”. But looking at std::variant (new in C++17, so i had to look this one up) is basically a union not an enum. Basically, what it’s really doing is sticking a union as a variable inside itself while another variable of a reliable size maintains an ID of what type it actually is, and any time you do an operation or explicitly check, it actually verifies that the IDs actually match. Taking one look at the thing, i’d say you’d be 10 times better off implementing this yourself than using the actual library, and other than enumerating the types in an enum as you’re using it, it’s actually not a lot of work and you could probably end up doing less work than using this monstrosity. This thing appears to be designed for alot of edge-cases, too, but everything is built in to make compile-time checks for runtime code to check what most likely you could check compile time. You can even get your own to auto-grow your structure using macros (which is another ugly point of C++).
Personally i think you should avoid ever needing to use that, but there are effective ways to built something more efficient (and easier to use) for your own needs rather than this ugly one-size-fits-all monstrosity that’s both painful to use (both for you and your computer) and understand. Like, i get WHY they did it, but while they’re making additions to the language they could’ve just added a few keywords that actually make this implementation easier instead. Something like “istype” or “typehash” (which could create an int out of a type by doing an md5 on class names) would’ve made this trivial and solve more adjacent problems at the same time.
One of the many huge reasons for this desire is because i plan on trying to do everything in Ada if i can actually get it down, and i plan on using it properly right away rather than doing a bunch of projects in it first. My understanding of compilers and their failures to optimize as well as readability issues with not knowing a full language tells me that if I don’t know the whole thing, i’m going to be violating one of my major goals of Ada in the first place by having to rewrite anything complex i make later once i learn “the better way.” As i code right now, i usually thinking while doing mundane tasks at work on the best ways to implement algorithms then actually implement them when i get home, which saves me a ton of time coding and makes the night go much faster. And given one of my first goals is to implement a sane CGI-bin library to get out of the insanity that is PHP’s constant deprecation (less than 24 hours ago my email client broke because the maintainers aren’t able to keep up with php’s rapid changes requiring a manual fix, which was fortunately a 1-liner: they were partially prepared, and it was actually the preparation that broke it).
Ah, that’s why. When i clicked the links on my phone i landed on the annotations. Rationale would be good documents for understanding the changes from an earlier version for sure if i were to take that route.
I’m curious why if Ada is so strong on type enforcement (to me this is a novelty of Ada since my real goal is maintainability of my code not so much the other features, but having seen it first hand i am incredibly curious how well the compiler does catch my mistakes in practice rather than theory) you wouldn’t try to enforce usage via derived types. Maybe it’s overactive cynicism on my part with my background in throwing caution to the wind with pointer magic but my impression has been that drilling safety via type-enforcement into the commmunity was intended.
For example, if i had a dice roll, i should want to force explicitly casting the die (hehe) to score to ensure i didn’t accidentally forget a modifier before adding to score. Or has the community been consistently giving the middle-finger to the language nannies (for some reason committees that change languages always seem to feel the need to police coders in addition to giving them tools, and this is not restricted to Ada) that lead to the creation of this?
It is a question of how much a [sub]type declaration defines behaviour. You can invent any sorts of constraints, e.g. even numbers, numbers with 9 in the third place of its decimal representation, number that would halt the subprogram F when passed as an argument. You cannot define all behaviour by the type. You can only do some reasonable part of it. Reasonable means evident to the reader, provable under some circumstances, preserving most of the predicates (Liskov’s substitutability). It is a technique the programmers use all the time without even knowing it because in the language like C you cannot express constraining in the language terms like -1 is not a valid value and thus is an exceptional state or, maybe is a bug). In Ada you can.
The only comprehensive guide to Ada (other than the ARM, which is a specification for compiler writers, not a learning guide) is Barnes’ books, which are massive (the content of his Ada-12 book is 850 190x247 mm pages). But I find your insistence on “comprehensive” misguided. If you think you have to understand every aspect of Ada before you can begin using it, then you don’t understand an important aspect of Ada’s design: Ada’s features are orthogonal. Once you understand the improved Pascal + packages subset, you can use the language for the kinds of things you do in a language like C. You can then add other features to your knowledge as you need them.
The predicates are constraints, though not as “tightly-bound” as e.g. Subtype X is Y Range A..B; — The reason is illustrated in the common Dynamic_Predicate being, well, dynamic: consider a type which has unique but [dynamically-]grouped values: you cannot know what the values are in advance. (Example: With a Player_ID base-type and a Blue_Team and Red_Team subtypes, such that some player cannot be a member of both at once.)
Implementation_Limit : Constant := 12;
Type ID is Range 1..Implementation_Limit;
Package Team_Group is Ada.Containers.Ordered_Set( ID );
Red_Roster, Blue_Roster : Team_Group.Set:= Team_Group.Empty_Set;
Subtype Red_Team is ID
with Dynamic_Predicate => Red_Roster.Contains(Red_Team)
and not Blue_Roster.Contains(Red_Team);
Perhaps the biggest key to using Ada the right way is to understand the type-system: use the type-system to define your problem-space, and then use that to solve your problem.
Also helpful to remember:
Ada’s notions of type/subtype:
Type: A set of values and operations on those values.
Subtype: A [possibly null] set of restrictions on the values of a type.
What an attribute is:
A query to the language (“language” because it might be compile-time resolvable, or it might require runtime) for some information or entity. (eg X'Last or Y'Callable.)
It works very well in practice, IMO.
As @dmitry-kazakov used as an example, removing the non-numerics from IEEE754, but perhaps an illustration regarding pointers would help; consider for a moment all the pointers passed around as parameters in (eg) the Win32 API and similar.
Consider a procedure to minimize some window: Procedure Minimize( Object : access Window'Class ) — right?
Well, we can improve things a little; how much nicer would it be to have Procedure Minimize( Object : not null access Window'Class )? I mean, now we never have to check in the implementation that Object is not null, thus saving us the manual-check, as well as raising the Constraint_Error to when it is called with Null. This could be close to the call-site, or not.
We can improve things by hoisting the constraint into a subtype:
Type Reference is access Window'Class;
Subtype Handle is not null Reference;
Making the procedure into Procedure Minimize( Object : Handle );, but also allowing us to have variables that enforce the constraint: Main : Handle := GUI.Make_Window( "Example" ); — note that the type/subtype also allows you to use the base (Reference) to model the “might exist” as in Pop_Up : Reference := Null;.
I’m not quite sure how the first sentence works in, but typically you would write a conversion-function that would handle bonuses, yes. (Rather than “casting”/Unchecked_Conversion.)
I’m not quite sure how the ARG/community dynamic matches with your “language nannies”/community one. Sure, there are some things that [generally newer] community members want that just aren’t good suggestions: perhaps the strawman argument would be the “updating syntax” (ie adding curly-braces).
So what you’re saying is, even if I wanted to put multiple constraints down, syntax only allows one per declaration? I’m sort of surprised this wasn’t fixed at some point.
Yeah, the woman and I went to a mall yesterday, but unfortunately there wasn’t a book store. I’ve found that going to book stores you can often find the same books at a discount.
You don’t have to know how to use structs in C to use C, either, but you eventually find that making structs is infinitely more readable than passing 10+ variables to a function. The point I have is that the entire reason languages have features is because they solve one problem or another. For my own code, to make it reasonably readable and efficient, you need to know what features of the language fit each part of your project. You cannot do that if you don’t even know what those features are. I mean, sure, i think brainfuck proved that most things can be coded with only a handful of language features, but that hardly makes it ideal.
Maybe a less exaggerated example would be polymorphism. Without class inheritance as a concept down, if you wanted to extend a class you could make the parent merely the first varible of the class and then write your own stuff on top (and change the function pointers in the constructor as needed) which is pretty much what happens under the hood with inheritance. The problem with this is that it’s terribly clunky and unreadable.
Or yet an even more practical example, is that if i took your advice, i’d already be coding in Ada, but using C mentality and extensively abusing unchecked_conversion. Because, really, the only thing i need in Ada to do most projects is int, float, string, and classes as well as some bitwise operations to do my usual pointer magic. And I really don’t think you want me releasing that code into the wild to represent Ada.
Logically yes, but i wasn’t sure how the vocabulary worked on that. For example, in most languages, a long long int or a short int is still an integer but not necessarily an int (the distinction on size is important) where the predicates are a wholly new construct i didn’t know if the “constraint” term was accurate or merely technically accurate.
Perhaps the biggest key to using Ada the right way is to understand the type-system: use the type-system to define your problem-space, and then use that to solve your problem.[/quote]
Yeah, i noticed Ada leans into type resolution for solving as many errors (whether syntactical or logical) as possible. This in turn seems to make ada front-loaded when trying to pick up. It’s also why it’s my current sticking point.
[quote]1Also helpful to remember:
Ada’s notions of type/subtype:
Type: A set of values and operations on those values.
Subtype: A [possibly null] set of restrictions on the values of a type.
What an attribute is:
A query to the language (“language” because it might be compile-time resolvable, or it might require runtime) for some information or entity. (eg X’Last or Y’Callable.)
See, given subtype restrictions only apply to variables declared as the subtype it still reads to me as “a different type” even if the language doesn’t see it that way when it does type checking. And this seems to be where the confusion I (and possibly others) have on subtypes, because when we come from other langauges this behavior seems to be exactly the same as class inheritance minus the concept of constraints: if class corvette inherits from car (derived type), using corvette in any function asking for a car as a parameter is perfectly valid and doesn’t generate a warning much less an error (polymorphism). Ada seems to be using subtypes to do this, but techically that’s what derived types and/or tagged types are for, but that introduces the need to caste which changes the behavior expected from other languages.
It works very well in practice, IMO.As used as an example, removing the non-numerics from IEEE754, but perhaps an illustration regarding pointers would help; consider for a moment all the pointers passed around as parameters in (eg) the Win32 API and similar.
Consider a procedure to minimize some window: Procedure Minimize( Object : access Window’Class ) — right?
Well, we can improve things a little; how much nicer would it be to have Procedure Minimize( Object : not null access Window’Class )? I mean, now we never have to check in the implementation that Object is not null, thus saving us the manual-check, as well as raising the Constraint_Error to when it is called with Null. This could be close to the call-site, or not.
We can improve things by hoisting the constraint into a subtype:
Type Reference is access Window’Class;
Subtype Handle is not null Reference;
Making the procedure into Procedure Minimize( Object : Handle );, but also allowing us to have variables that enforce the constraint: Main : Handle := GUI.Make_Window( “Example” ); — note that the type/subtype also allows you to use the base (Reference) to model the “might exist” as in Pop_Up : Reference := Null;.
See, when i see that, i wonder why your “is not null Reference” constraint is not applied to Reference instead of Handle. Ultimately, you get a null handle, you have an error. I can understand why you might want some constraints to apply to specific variables and not all variables of a specific type, but why is this a subtype and not applied to said variables (or the main type if it’s only used this way) at delcaration and such to avaoid identifier saturation/conflict?
I’m not quite sure how the first sentence works in, but typically you would write a conversion-function that would handle bonuses, yes. (Rather than “casting”/Unchecked_Conversion.)[/quote]
Right, but i’ve been lead to believe that the “proper way” of handling dice is not a subtype because by making it it’s own type, and by making that type error checked, that prevents someone from “accidentally” not writing something to handle the bonuses.
[quote]I’m not quite sure how the ARG/community dynamic matches with your “language nannies”/community one. Sure, there are some things that [generally newer] community members want that just aren’t good suggestions: perhaps the strawman argument would be the “updating syntax” (ie adding curly-braces).
For a while, it was possible in java to error on this:
else return false;
The reason being that you’re placing returns in conditional statements, that even if you cannot progress past that point the fact that both returns were in branches that you wouldn’t have an unconditional return which Java felt was unsafe. Another one i saw recently is apparently in Zig or some other C addon, having a space on one side of an operator, but not the other side, is a syntax error (not a style warning). Furthermore, there are people that tend to want to get rid of “goto” in langauges (while technically goto is not needed anymore, that doesn’t mean it’s never cleaner: for eample a “while(true)” loop isn’t cleaner than goto, it’s just am idiom that people came up with to avoid using a backwards goto, though ada has the naked loop which shows intent without knowing the idiom).
Often people new to a language will abuse certain constructs in a language because they don’t have experience and/or knowledge of the entire language, which causes unreadable, messy code (back to why learning the whole language is important). The goto spaghetti code is easily the most famous example, but that doesn’t mean many, let alone most, cases of goto usage are similarly unreadable, but you have people who want to remove it, and that’s just a moderate position. There are people who want to get rid of pointers (which is why Java tries to hide them) because they’ve either met too many people who don’t understand them or they themselves don’t understand them.
The “language nannies” i refer to are people that fail to see that from malware that people will misuse and abuse language features no matter what is done, but instead think that by breaking changes to a language they can force “good behavior.” I get that Ada wanted to “tighten” certain things in some of it’s updates to the langauge, but this was to restore original intent. Alire’s style checks are also an example of this. I get that, for example, not everyone uses the same length of tabs (although in the current age it’s about 8 characters nearly universally), but that should be part of alire’s publish stage (because it’s totally fair that alire would want it’s package repository to follow a certain standard) not a compile-time check (because not everyone using alire will be using it for publishing their project, and alire should not be the police of ada project styles).
This is incorrect. A major use of subtypes is as subprogram parameters. For example, a Sqrt function might have the specification
function Sqrt (Value : in Real) return Real;
where Real is a floating-point type. But the real square root of a negative number is undefined, so you have to reject calls with such values. Since Ada 83, the way to do this is with a subtype
subtype Natural_Real is Real range 0.0 .. Real'Last;
function Sqrt (Value : in Natural_Real) return Real;
(The result subtype could also be Natural_Real in this case.)
You can then use a variable of type Real for your calculations, and pass it to Sqrt
V : Real := 4.0;
V := Sqrt (V); -- 2.0
V := -1.0;
V := Sqrt (V); -- Raises Constraint_Error
In real code, V might be calculated in steps, and after some of those steps might have a negative value, but will always have a non-negative value after the step that gives the value passed to Sqrt.
This is a form of precondition. It is not as general as the preconditions added in Ada 12, but it is generally better to use subtypes rather than preconditions or predicates when possible; several SPARK experts have given this advice. Given a good subtype name, it’s easier to remember the restrictions of a subtype than a precondition.
(The astute reader will notice that Ada.Numerics.Generic_Elementary_Functions.Sqrt does not use a subtype for this. But given that the package name repeats information that is obvious from the code, we can conclude that it is not a good example.)
Huh, any combination of constraints is a constraint. No?
This is not how Ada is used. You should define problem-specific types unless you write some general purpose library. Pointer arithmetic is newer used/needed in Ada.
The first thing is to look beyond the language-specific word salad. Both Ada subtypes and tagged types are inheritance, both introduce classes, both represent polymorphism. They are same in that sense. They are different in how inheritance works.
When you derive an Ada subtype you inherit a subset of values, the representation of, all operations combined with constraint. New operations are exported to the base type.
When you derive an extension type (tagged types) the set of values is a direct product of an old set and the extension. The representation is that the base is contained inside the derived type. The operations are inherited with the view change to the base. New operations are not exported..[The consequence is that tagged types are by-reference and tag is kept in the representation. Subtypes are free of these limitations.]
Subtype and extension are two quite different methods of creating classes (among others, e.g. by generics) though some tasks can be solved using either.
The class introduced by Ada subtypes is defined upfront. The programmer plans everything in advance and then chips out unused parts by constraining.
The class of type extensions is open and frequently abstract. The programmer adds things later.
Not by the standard you’ve loosely implied. How much knowledge is “enough”? Is there “standard subsets” of the language that are “complete enough” that can be defined in some tangible way? You can absolutely wwrite programs in C without understanding structs, enums, bitwise operations, etc, it’s just you’d be much better at it if you did understand all of that. It’s also not like C wasn’t used before the most recent version, so were those people before not using C because they didn’t have what they have now?
But there inlies the point: there are some constructs of a given language that are rarely used in most projects, while some constructs that are used in most projects, and you need to know what those are. And, heaven forbid, you have a project that’s not common (which should be the case, else why are you not using what someone else wrote instead of writing your own?), you might need that niche information.
Stages, yes, perfectly normal. But at that point you’re still learning the language basics, you would be a student at that point, not someone trying to put the language to genuine practice.
That’s true, but then why not use a derived type?
Well, i mean why not declare the initial type with all the constraints or a variable. I could create a d20 with range 1 .. 20, then the more traditional die as a subtype of 1 .. 6, but if i don’t use a d20, or even the whole integer set, why subtype instead of making a dice type or simply constraining the one die to 1 .. 6? My understanding is that if you’re making things that tight constraints, it shouldn’t necessarily be a subtype but rather a type to force people to acknowledge they know what they’re doing with a caste.
I agree, but this is in response to the argument you don’t need to comprehensively know ada to use it. The question ultimately is, “Ok, If i don’t need to know all of Ada to use it, how does that differ from every other language out there?” Technically speaking, yes, it’s true, I can use a bare minimum of Ada mechanics with an Ada compiler and basically break common convention enough to “get it to work” and I’ve written a program that’s technically Ada but doesn’t solve my problem in a remotely normal, readable way. I just would’ve made an ada program using C style mentality by making Ada into C.
That’d be like me creating x86 assembly abstraction with C syntax by doing the following:
#define jmp goto
void add(int &dest, int src){ seteflags(dest, dest += src); }
void sub(int &dest, int src){ seteflags(dest, dest -= src); }
void and(int &dest, int src){ seteflags(dest, dest &= src); }
void or(int &dest, int src){ seteflags(dest, dest |= src); }
void xor(int &dest, int src){ seteflags(dest, dest ^= src); }
//etc, define the x86 registers as int type, have check figure out the flags based on before and after, etc
int main(){
mov(rdi, "hello world\n");
call(printf); //Call would translate the x86 abi to the real abi of the system
return 0;
}
Yes that can be done, and no that’s not a good idea for a number of reasons.
So if i overload an operator to use the subtype as a parameter, it affects the base type as well?
But what i’m seeing here is that subtype is more or less a class but can only emplace more restrictions rather than create more content (new operations). Then i would ask “why subtype instead of making a class”? Inheritance means you still get everything as the base class, anyway, so I don’t see the advantage unless you can’t contrain a class.
Perhaps my mistake is in assuming that whatever Ada uses as classes (tagged type and/or derived types) works the way it does in other languages. I’m not seeing what subtypes add to the table that applying the concept of constraints to classes wouldn’t, other than a lack of need to convert (which seems to fly against the Ada mentality of relying ton type-checking to catch errors).
No, you can.
… but it’s often a good idea to look at all your constraints and see if there’s a place where (given Z as all constraints; X+Y = Z) constraint X (but not Y) is useful. (The simple example: Integer, Natural, & Positive.)
You’re going to have a bad time if you go for pointers and pointer-magic in Ada.
C (and C++) have trained you to reach for pointers all the time; to counter this natural reaction, tell yourself that you NEVER need pointers in Ada. — This is a very slightly exaggerated rule: in Ada you really don’t need pointers all that often, because:
Ada has in, out, & in out parameter modes.
Ada’s arrays “know their own lengths”; A'Range being short for A'First..A'Last.
Generics let you have, as formal parameters, functions, procedures, other generic-packages, and objects (compiler, not OOP; which may be of in out mode).
About the only places you really need pointers is discriminants, foreign-function interface, and [some] data-structures. (Even w/ data-structures, you need pointers less often.)
-- Look, ma! No pointers!
Type Message(Length : Natural) is record -- Note: the discriminant
Text : String(1..Length):= (others => ASCII.NUL); -- binds Length to the array
End record; -- and the default ensures
-- no uninitialized memory.
Ada kinda goes the other way; consider the following:
Type Fahrenheit is new Integer; -- (a)
Type Celsius is new Integer; -- (b)
Type Nybble is range 0..15 -- (c)
with Size => 4; -- (d)
Here we’re deriving some new types [(a) & (b)] from Integer, they have the same values and operations, but are not the same type. Then with (c) we are declaring a completely new type ranging from 0 to 15, the compiler is free to use whatever size is best… until (d) tells it you must use 4 bits!
Perhaps an illustration of what a subtype does would help?
Let’s use plain old Integer, Positive, Natural, and show why “more constraints on values”/“not a full type” is useful:
X : Integer:= Get_Delta; -- assume -1;
Y : Positive := abs X; -- would raise constraint_error if X=0
Z : Natural := X*X*X* + Y; -- impossible if intermediates were checked.
-- …
Ada.Texy_IO.Put_Line( "Value:" & Positive'Image(X) ); -- is fine.
-- Note: the IMAGE attribute is querying "what function takes POSITIVE values
-- and returns a string", the answer (because all POSITIVE values are values
-- of INTEGER, is obviously INTEGER'IMAGE.
No, subtype is constricting possible values; inheritance is widening them:
Type Base is tagged record
X : Integer;
end record;
Type Extended is new Base with record
Y : Integer;
end record;
Subtype Restricted is Extended
with Static_Predicate => Restricted.X in Nybble
and Restricted.Y in Nybble;
So, with Base your values are that of X (Integer'Range); with Extended your values are that of X×Y (Integer'Range×Integer'Range). With Restricted your values are much reduced (Nybble'Range×Nybble'Range)… 16 ×16=256, small enough that you could make it a byte.
Not necessarily, do you want the Pop_up to always be in-memory? It’s perfectly valid design to say: We’ll need a spot to refer to this thing, if it exists, so we’ll give it a name and location to find it. …which is exactly what the example was showing.
In general, this is correct. Type Die is range 1..6; is modeling a single die, and this is where the usage of the type-system comes in: how are you going to be using things? How are they going to be interacting?
Type Throw is Array(Positive range <>) of Die;
Function Do_Throw( Dice : Natural ) return Throw is
Begin
Return Result : Throw(1..Dice):= ( Others => RANDOM_DIE );
End Do_Throw;
is a perfectly valid way to model it for a game… but it might be clunky and awkward if you are really doing statistical analysis on things. (So, again, back to your problem-space.)
LOL, it’s probably closer to the opposite: there’s been several people thinking that breaking changes to fix past design mistakes in the language would be excellent; the ARG is highly resistant to breaking backwards compatibility though.
IIUC, these are GNAT’s style-checks, and I really don’t like them.
(I never use them, and if I were going to use a style checker I’ve heard good things about AdaControl.)
In the initial design-phase of Alire I had advocated not using text-files, but an actual IR; this completely obliviates “tabs-vs-spaces” AND forces non-compliable [well, non-parsable] code out AND would be nicer for use in a DB-based system where you could offload various checks into the DB like, say, license compatibility, or version-requirements.
type <anonymous> is range -<machine-dependent>..<machine-dependent>;
subtype Dice is <anonymous> range 1..6;
You always have so-called first subtype. You have a symmetric range around zero for integer types in order to get reasonable arithmetic.
Yes. They are mutually substitutable for the compiler.
You must first define what do you understand under the term “class”. C++'s class = type of special kind. Ada’s class = set of types sharing some properties.
All subtypes of a type form a class. All integer types form a class. All tagged types derived from some type form a class. All types of generic instances form a class etc. You choose what you want. Each way of having a class has its advantages and disadvantages.
Huh, but C++ has classes similar to ones of Ada subtypes: conversion operators do the thing. [Templates completely railroaded C++ which stopped evolving its type system in favour of becoming a C preprocessor on steroids.]
No language has a consistent type system based on solely extension model. It is impossible to do. So they have first class and second class types (No pun intended). Can you extend int? Can you write a polymorphic subprogram working for all integer types? Class-wide in Ada terms. You cannot in C++ though C approached it using implicit conversions. Types under implicit conversions form a class and you can write printf for this class. Ada numeric subtypes are similar.
Tagged types can have subtypes in Ada, no problem. But I do not see your point. Extension is a direct product. Constraint makes a subset. They are mathematically different.
As to subtypes though, as @JC001 pointed out, subtypes are very useful in parameters.
They’re also surprisingly useful in conditionals (if X in Something/When Something =>).
But, perhaps coming from C, a progressive set of C-defined API examples would help; I don’t have the time to type this up right now, but when I get back from church, if I remember.
In the interim, consider the value in subsets: that is what subtype does: it creates a subset of values of a type by applying a set of constraints (possibly null, thus being a sort of renaming) onto the values of a type. — OOP inheritance is the other way around: creating supersets.
Applications that are OLE 2 servers typically form composite monikers for their
OLE links by combining a file moniker representing the document with one or
more item monikers representing a particular section of the document. This
approach often does not work in environments where Document Management
Systems are in use because the filename that the application sees is usually just
temporary. This function lets the application obtain a leading moniker from the
DMS that can be used in place of the file moniker.
This function will only be available on platforms supporting OLE 2. This function
may not be supported by some DMSs; those DMSs will return ODM_E_FAIL. In
this case the application should go ahead and use the file moniker as though
ODMA were not present. Note that this function is prototyped in odmacom.h
instead of odma.h, so that non-OLE-aware applications do not have to #include
the OLE header files.
Parameters:
handle - in - A handle obtained by a previous ODMRegisterApp call. lpszDocId - in - An ODMA document ID. ppMoniker - out - A leading moniker for the specified document ID will be
returned here if successful. Otherwise Null will be returned here.
Return value:
ODM_SUCCESS if successful. ODM_E_FAIL if the DMS that created the specified document ID does
not support OLE moniker building. ODM_E_DOCID if the document ID is invalid or refers to a document
that no longer exists. ODM_E_HANDLE if handle is invalid.
ODMSTATUS ODMGetLeadMoniker(
ODMHANDLE handle,
LPSTR lpszDocId,
LPMONIKER FAR *ppMoniker
)
Now, from the first iteration; a more direct/mechanical approach. This might be amiable to direct mechanical processing; for example, see GCC’s -fdump-ada-spec parameter(Note: The parent section clearly states “This capability is not intended to generate 100% correct Ada specs and it will in some cases require you to make manual adjustments, although it can often be used out of the box in practice.”).
ODM_SUCCESS : Constant := 0;
ODM_FAIL : Constant := 1;
-- …
ODM_E_HANDLE : Constant := 8;
-- …
ODM_E_DOCID : Constant := 11;
-- …
ODM_E_OFFLINE : Constant := 22;
Subtype ODMSTATUS is Interfaces.C.int;
Function GetLeadMoniker (
This : Access IODMDocMan;
lpszDocId : in winnt_h.LPSTR;
ppMoniker : out System.Address -- pointer-to-pointer as address, per tool
) return ODMSTATUS;
Ok, so here we are with a C-callable function (if we were to slap Convention => C on it, and either import or export it). But it’s really kind of ugly, and it’s honestly not going to fit very well in Ada code. So let’s clean it up a bit. First, let’s collect those error-codes into a type and give ourselves a general-ish “half-error” handler:
Type Error_Code is (Success, Fail, …, Offline)
with Size => ODMSTATUS'Size;
For Error_Code Use (
Success => ODM_SUCCESS,
FAIL => ODM_E_FAIL,
-- …
OFFLINE => ODM_E_OFFLINE
);
-- We can just overlay the ERROR_CODE type on the Interfaces.C.int.
Function "+"( Code : ODMSTATUS ) return Error_Code is
Value : Error_Code with Import, Address => Code'Address;
FAILURE, BAD_DOCID, BAD_HANDLE : Exception
Begin
if not Value'Valid then
Raise Constraint_Error with
"Undefined error-code: " & ODMSTATUS'Image(Code) & ".";
end if;
Return Value;
End "+";
-- Note: type LPSTR is new Interfaces.C.Strings.chars_ptr;
Function GetLeadMoniker_L2 (
This : Access IODMDocMan;
lpszDocId : in Interfaces.C.Strings.chars_ptr;
ppMoniker : out System.Address
) return Error_Code
with Post => GetLeadMoniker_L2'Result in Success|Fail||DocID|Handle;
------------ BODY:
Function GetLeadMoniker_L2 (
This : Access IODMDocMan;
lpszDocId : in Interfaces.C.Strings.chars_ptr;
ppMoniker : out System.Address
) return Error_Code is ( +GetLeadMoniker );
But what if a lot of functions in the API have the same set of results? (Or the same parameter restrictions.)
Subtype Common_Code is Error_Code
with Static_Predicate => Common_Code in Success|Fail||DocID|Handle;
-- If these were all contiguous, we could say "Success..Handle".
Function GetLeadMoniker_L3 (
This : Access IODMDocMan;
lpszDocId : in Interfaces.C.Strings.chars_ptr;
ppMoniker : out System.Address
) return Common_Code;
And, if we’re being honest, a more Ada idiomatic way we would recognize that these Error_Codes aren’t what we’re interested in, there’s only one “success” state and everything else is error, so…
Procedure GetLeadMoniker_L4 (
This : Access IODMDocMan;
lpszDocId : in Interfaces.C.Strings.chars_ptr;
ppMoniker : out System.Address
) is
-- Rename the conversion of the error-code returned by the call.
Code : Common_Code renames "+"( GetLeadMoniker_L3(This, lpszDocId, ppMoniker) );
Begin
null;
End GetLeadMoniker_L4;
---- We could also add exceptions and an ELSE to the "+" function:
-- else
-- case Value is
-- when Success => Null;
-- when Fail => Raise Failure;
-- when DocId => Raise Bad_DocID;
---- …
-- end case;
Oh, but we have an out parameter, so it should be a function:
Function GetLeadMoniker_L5 (
This : Access IODMDocMan;
lpszDocId : in Interfaces.C.Strings.chars_ptr;
) return System.Address is
Begin
Return Result : System.Address do
Declare
Code : Common_Code renames "+"( GetLeadMoniker_L3(This, lpszDocId, Result) );
Begin
Null;
End;
End Return;
End;
And reiterating, until you get to something thick/idiomatic
Function Get_Lead_Moniker(
Object : in out IManager'Class;
Document : in Document_ID;
) return Moniker;
The above being the “public facing” part of your library/implementation, and thus you can save yourself tons of headaches, but note how there were multiple levels points where “thinking in sets” was used: and subtype is how to apply that to values in a type.
It works on parameters, too:
-- The accuracy of our thermostat sensor.
Type Fahrenheit is range -40..212;
-- Livable room temps.
Subtype Room_Temp range 60..90;
-- Consider:
Procedure Set_Temprature_Full( Room : Fahrenheit ); -- A value of -40 or 212 is valid!
--vs
-- And here we raise CONSTRAINT_ERROR if you try to call with invalid values.
Procedure Set_Temprature_Part( Room : Room_Temp ); -- Restricted to 60..90.
--and
-- Raises CONSTRAINT_ERROR if value is out of range.
Function Get_Temprature return Room_Temp );
And, note also, there is NO OOP at all in this temperature example.
Well, yeah, that’s fair and that goes without saying: creating a subtypes with constraints that neither you nor anyone else uses just adds bulk to your code making it hard to read.
I agree, i’m just making an example of why the iea of only bothering to learn a portion of the language is a bad idea. I’m sure i could get along just fine doing that, but it’s a really terrible idea and it defeats the purpose of most of the language. But, that’s the entire point. Bad ideas like this that lead to unstable and unreadable code are the result of trying to get around the language because either it lacks soemthiong or I lack something. Ergo, if i want to write good code, i should know the whole language. Of course i can technically get by without it, but then i’m going to want to fix it later and then i’ve defeated the purpose of using Ada over C which i already know.
I wouldn’t say this is “the other way” but really the same, except Ada so far is the only one i’ve seen that offers the concept of contraints to make your own types to begin with. At the end of the day, the major category of what you’re representing with the variable is the same (a number meant to be viewed as a number), but of different limitations.
Technically, though, Nybble is not an Integer, but a sibling of Integer (and an aunt/uncle of Fahrenheit and Celcius). Which is also interesting. What i can tell, Nybble is, conceptually, a derived type of an unnamed type of sorts of whatever Integer is derived from.
Why not constrain X on declaration rather than create a subtype to solve the problem? Under a more complex problem, if you had a bunch of these, then whatever reason you wanted to force them positive to begin with might imply it’s more complex than simply a number and i should instead be using something other than subtypes to force my logic to be checked?
Oh, see what angle you’re looking at now, and yes. But i imagine there’s some mechanic by which instead you could do something like what i’m going to attempt:
type Nybble is new Base with Static_Predicate => X in Nybble;
null; --Do i need this if i don't have "with record"?
end record;
I would find it strange if I couldn’t both add and restrict within the same statements (perhaps i’m over-assuming). So my immediate thought is that subtype is a restricted/“simple” version of the latter, where the latter is when you want to containerize things within the boundaries of the type so that you don’t have to use “use” to import everything.
Windows will free that memory whether you like it or not, but i guess guess i get your point: you may have something else. Or am i looking at your example wrong and we’re not directly talking about the win32 api? Because you really, really shouldn’t have anything that does exist but doesn’t have a valid handle. Not having a handle is the win32 way of referring to something that does not exist, and is only needed when writing calls (so making a popup with no parrent window, to which you don’t need to store it).
Now you have me curious how you’d handle it differently if you were trying to dostatistical analysis (i assume to see if your dice roll ends up fair for each value over time).
Count me with that team, then. I’d rather let people get away with poor decisions than to have to fix my code to some new standard, especially if the standard is unnecessary.
Gnat makes it possible, but the checks themselves are set by alire. This is what i have right now:
[kohlrak@kohlrak-gaming ~]$ cat ada.sh
export PATH="$(echo $HOME/.local/share/alire/toolchains/gnat_native_*/bin):$HOME/.alire/bin:$PATH"
function adabin {
alr init "$1" --bin
cd "$1" || exit 1
cat ~/alire.toml >> alire.toml
sed "s/%PROJECT%/$1/g" << 'EOF' > true_release.gpr
--This is for optimization to extremes to the degree of unsafe as it even disables runtime checks.
project True_Release extends "%PROJECT%.gpr" is
for Create_Missing_Dirs use "True";
package Compiler is
for Default_Switches ("Ada") use (
"-gnatp",
"-march=native",
"-flto", --Consider -flto=thin if you experience OOM or timeouts during compiling.
"-ffunction-sections",
"-fdata-sections",
"-fipa-pta",
"-fipa-sra",
"-fpredictive-commoning",
"-fgcse-after-reload",
"-fgraphite",
"-fgraphite-identity",
"-fstrict-aliasing" --Supposedly Ada does this for us already as a language feature.
);
end Compiler;
package Linker is
for Default_Switches ("Ada") use (
"-Wl,--gc-sections",
"-flto"
);
end Linker;
end True_Release;
EOF
}
function adatrue {
alr exec -- gprbuild -P true_release.gpr
}
[kohlrak@kohlrak-gaming ~]$ cat alire.toml
[build-switches."*"]
style_checks = []
source_encoding = ["-gnatW8"]
[build-switches."development"]
optimization = ["-O0"]
debug_info = ["-g"]
runtime_checks = []
compile_checks = ["-gnata"]
[build-switches."release"]
optimization = ["-Os"]
compile_checks = ["-gnatn"]
debug_info = ["-g0"]
Obviously a work in progress with all the typos and the fact most of it was chatgpt generated, but i figured i’d release later. Obviously the intent is to selectively re-enable checks where user input is involved.
By IR you mean a new IR not the pre-compiled files, right? I could see why that’d be an issue: people want to be able to look at the code. The problem is, people want to be able to look at the code of developer X while developer X is being so nice to code something they can use in the first place, but they also want to make demands of developer X, but make it appropriate by saying “it’s the community standard” or something like that, so they’re not the rude ones. Sad part is, they probably don’t even realize that’s what is actually going on in their own heads.
I figured that’s what was really going on behind the scenes, but if there only is one variable with a particular constraint, why make a subtype instead of applying the constraint to the one variable on declaration? I get that there might not be currently a syntax for this, but the name resolution paradigm seems to be centered around avoiding risk of collisions and such. This only increases the chances.
Ah yeah, that is a fundamental bigger problem when discussing things: i don’t know Ada, i’m trying to figure it out, but Ada has different definitions for the same vocabulary, and i can only use terms that i know. So by “class” in ada, it refers to the “abstract unnamed types” from which each of the primitives derives (from a C++ perspective). My constraints when naming a type, i’m making a derived type of that class, rather than of an existing type.
We’re in agreement, there. I feel like templates were a way of trying to get rid of macros. The idea that someone would try to turn a class definition into a macro so they came up with the cleaner template instead, but it’s own complication just made things disasterous.
As for conversion operators, i wouldn’t argue they’re even remotely close to what examples i’ve seen here. Conversion operators are overriding the conversion (if it even exists), where as subtypes seem to be for the purpose of throwing errors when something doesn’t fit.
Thing is, at least in examples, subtypes are not trying to be separate types (that’s what the “type” keyword is for) but making smaller variables, but at the same time keeping everything. Which i can get, but i thought a major goal of Ada was avoiding implicit conversions.
Perhaps here what we see differently is that i see any deviation from a base type as a new type which can include both new features and new constraints. I see it as trying to make a new thing out of an old thing, which may or may not add features here and remove features there. Take an ipV4 address: it is 4 bytes that can both be treated as an unsigned int and a 4 byte unsigned char array, and both representations can be useful if you’re trying to do something mathematical with it (for example, trying to find a way to turn it into something more human readable). I might extend integer for this.
These do look useful but not in the context of the subtype discussion. The 3rd in particular (in the interest of time for this discussion) i did ctrl+f and looked up subtype. Once again seeing restrictions applied to types instead of simply using type only seems to server avoiding the type check.
Inheritance works both ways, though, it’s just it’s usually supersets. One of the thing inheritance does is brings everything in for you to then rewrite. If i have a function for an object, unless i use “final” from java, i can change (or even erase) the function and add constraints to it using the syntax of the language.
The unique feature seems to be that constraints can be used to make exceptions, but what i’m struggling with is why use “subtype” instead of “type” when that very feature is tied to constraints (which can be applied to types) not just subtypes. Perhaps to distill this further is: if it’s such a big deal with Ada to enforce type-checking and avoid implicit conversion on everything, why is “subtype” such an easy way to bypass this?
I appreciate you taking the time to take the examples in, but i’m not understanding why, with the mentality you’re addressing, instead of having subtypes at each step, you’re not modifying the initial type to have these constraints instead of leveling the subtypes. If you find out that ultimately everything is using the same limited set, why add all these aliases with tighter restrictions instead of modifying the base and reducing overall code?
I’m curious why you bothered creating a Fahrenheit type in this example. Also, shouldn’t you need to specify Fahrenheit as being the base of the subtype?