[HN Gopher] Ada 95: The Craft of Object-Oriented Programming
___________________________________________________________________
Ada 95: The Craft of Object-Oriented Programming
Author : stefankuehnel
Score : 122 points
Date : 2024-03-01 22:52 UTC (1 days ago)
(HTM) web link (adaic.org)
(TXT) w3m dump (adaic.org)
| pyjarrett wrote:
| My favorite thing about Ada is how it uses modules ("packages")
| for encapsulation and not classes. This separates it from a lot
| of other languages with object-oriented programming by allowing
| you to split up and expose externally visible behavior in modules
| and submodules without making type internals visible, but
| allowing other elements in the implementation side of the module
| and submodules to look inside.
| akritid wrote:
| The explicitly specialised generic packages are nice too
| ReleaseCandidat wrote:
| Yes, they are "officially" ML (no, not that ML) style.
|
| https://ada-lang.io/docs/learn/why-ada#feature-overview
| pjmlp wrote:
| Most languages with OOP also do modules, all the way back
| Simula and Smalltalk started to influence Mesa/Modula-2 derived
| languages.
|
| Additionally in a somewhat naive ways, classes can be seen as
| extensible modules, which in the presence of generics is less
| required as in languages that don't support them.
|
| When using something like Standard ML functors, classes aren't
| really needed.
| com2kid wrote:
| I don't know why integer derived types don't exist in every
| language, from what I understand they are a compile time feature
| that removes large swaths of potential errors.
|
| We all agree that string enums are generally a good idea when we
| want to limit passed in configuration values to a limited set of
| valid strings, so why not ensure that if 2 (or 5...) integers are
| passed into a constructor (looking at you Java) that they are
| actually all of the proper "type" of integer?
|
| For those who don't know what I am talking about, Ada lets you do
| stuff like (taken from the linked book) type
| Age is new Natural range 0..150;
|
| and now you can say that the age parameter on a function is of
| type Age, and if you try to pass some other integer type in, it
| will fail.
|
| Ada's type system is so powerful that the metric system of
| measurements, with physics units, speed, acceleration, all that
| jazz, can be expressed in it.
|
| I've seen C++ people say "but templates can do that!" but if I
| ask about how much eye bleed is included, they always change the
| topic...
| teaearlgraycold wrote:
| It doesn't compare, but TypeScript can do this for small
| ranges, or small sets, very well.
| throwaway13337 wrote:
| Agreed!
|
| Expanded type guards can replace a huge amount of unit tests in
| a way that is more concise and provides a better promise to
| more of the code.
|
| It's about orientation. Orienting around the data makes so much
| more sense. Why test every function's email address param when
| you can instead guard via an email address type?
|
| Typescript is the most popular language to try and their type
| system is a lot of fun. However, the compiler messages remind
| me of working with Boost libs in C++. It's also unfortunate
| that typescript actually provides no guarantee about runtime so
| I never have complete confidence like I do with real static
| typed languages.
|
| You can also get to a point in typescript where you go down a
| type system rabbit hole and find yourself a code philosopher.
| It's been a personal black hole for time wasting on a project
| for me.
|
| Libs like Zod go a long way in JS and they accomplish the same
| but better for me. They leverage typescript and you get compile
| time and runtime support.
|
| Vogan in C# provides compile time warnings but uses generated
| code to achieve it. It is impressive what the new code
| generation support in C# can do now.
|
| Still, it would be nice to have first class support from a
| language.
|
| https://github.com/colinhacks/zod
|
| https://github.com/SteveDunn/Vogen
| mbStavola wrote:
| Rust is considering this under the name of "pattern types"
| [0][1]. If the full idea is realized, you should be able to
| handle strings and enums as well.
|
| There is also a PR for a minimal implementation using a macro
| and targeting integers specifically[2]. It's experimental and
| there is a chance this doesn't get stabilized, but here's
| hoping it makes it through!
|
| [0]: https://github.com/rust-lang/rust/pull/107606 [1]:
| https://cohost.org/oli-obk/post/165584-ranged-integers-via [2]:
| https://github.com/rust-lang/rust/pull/120131
| phoehne wrote:
| I'm glad to hear that. We're moving stuff to Rust at work and
| it's great at memory safety. Much better than relying on
| Unchecked_Deallocation. But that said, there are a lot of
| times when something can't be out of a range, like voltage is
| from 0.00 to 1.85 in steps of 0.01. Ada is much better at
| safely specifying that range, while Rust will happily take
| values that are outside the acceptable range.
| binary132 wrote:
| Almost as though in order to maintain runtime type
| invariants on values you must use some kind of function to
| "construct" them...
| pyjarrett wrote:
| Ada checks the ranges on types assigned to each other at
| compile time and then invariants on assignment and when
| passed as parameters. You can turn the runtime side of
| these checks on or off individually at the module level.
| kevlar700 wrote:
| That's unfortunate. Ada is so much nicer to work with and
| Spark now has deallocation safety and memory leak
| prevention.
|
| https://youtu.be/97G1V2U8Drk?si=wNqNs1GiuO9ZdGwP
| kevlar700 wrote:
| I chose Ada over Rust for my company a few years ago and
| couldn't be happier. My entire embedded and tooling code
| bases are now Spark compatible. I don't believe any
| language can match Ada for drivers, memory register and
| network protocol handling.
| pjmlp wrote:
| While people keep using Ada for ranged types examples, this was
| already present in Pascal and Modula-2. type
| Age = 0..150; type Age = [0..150];
|
| It is a pity that has taken so many years for people to start
| appreciating type driven design.
| ainar-g wrote:
| A feature that combines well with range and enum types are
| typed indexes of arrays: TYPE TWeekday
| = (Monday, Tuesday, Wednesday, Thursday, Friday, Saturday,
| Sunday); VAR WeekdayNameIndex : ARRAY [TWeekday]
| OF ShortString = ('Mon', 'Tue', 'Wed', 'Thu', 'Fri',
| 'Sat', 'Sun');
|
| Now `WeekdayNameIndex` is a properly type- and range-checked
| look-up table.
|
| It's really sad that these simple joys of Pascal are still
| lacking from a lot of mainstream languages.
| pjmlp wrote:
| Indeed, I used that kind of stuff a lot.
| kazinator wrote:
| I also used it, but wouldn't go back.
| pjmlp wrote:
| Me neither, but that is because now I have F#, Scala,
| Haskell, Rust,... :)
| pyjarrett wrote:
| This is an awesome feature, combined with appropriate type
| and bounds checking and prevents so many errors. It can
| also avoid resorting to a heavier-weight map type.
|
| Ada has this as well, including using any arbitrary
| continuous range for array indexing which handles remapping
| indices for you. e.g. if the key range is 20-40 the
| language handles associating it with array indices for you.
| _xerces_ wrote:
| I think Ada, due its DoD origins and mandated use in some
| defense projects is more common than Pascal or Modula-2.
|
| Funnily enough though, I took a basic computer science course
| back in 1995 where Pascal was the first language that was
| taught! I still remember the instructor telling us to imagine
| how we would instruct an alien how to use a vending machine,
| we could not assume it knew anything and had to lay out each
| logical step and it would do exactly as we told it, so we
| better get it right. He said that was the mindset we needed
| for programming.
|
| Fast-forward to 2004 when I resumed my further education and
| now the first language taught in that university was Visual
| Basic, followed by Java for the object-oriented programming
| class and then straight into pure C for Operating Systems
| which was a bit of a jolt. Oh, and then back to Java when we
| had some horrible UML class with design software that auto
| generated code.
|
| Apologies for the off-topic meandering, the mention of Pascal
| brought back memories!
| cfeduke wrote:
| In the early 90s I ended up taking a Pascal course in high
| school for a math credit. Afterwards I joined the military
| in a computer software MOS and was trained in Ada. Of
| course... my actual day to day was often working with
| Visual Basic or VBA. The government employees assigned to
| my unit regularly worked in COBOL or Fortran... we had a
| single Ada system that required rebooting into 16-bit DOS
| to compile, but ran under Windows 95. Imagine the fun that
| was to debug from a single computer!
|
| I am happy to contribute to your off topic meandering.
| pjmlp wrote:
| More common?
|
| Appolo, Lisa and Mac OS were originally written in Pascal.
|
| Apple's Object Pascal was adopted by Borland and left its
| mark in the PC computing world, still present to this day
| in Delphi and Free Pascal.
|
| The MS-DOS Demoscene had mostly Turbo Pascal as companion
| to the otherwise Assembly written demos.
|
| One really needs to be deep into caring about security to
| learn Ada, due to how costly getting access to a compiler
| was during the last century, before GNAT came to be.
|
| I had the books and no compilers to actually write the code
| on.
| pasc1878 wrote:
| The Windows 16 bit APIs had the PASCAL calling convention
| and MS sold a Pascal at the same time or possibly earlier
| than C.
| vram22 wrote:
| Yes. "long far pascal" was part of C function prototypes
| for those APIs used in Windows programming.
|
| They also sold MS Fortran and MS Lisp.
| phoehne wrote:
| I don't know if Niklaus Wirth (the designer of both Pascal,
| Modula, Modula-2, and Oberon) participated in the DoD
| competition, but Ada was heavily inspired by those ideas.
| The shame was Dr. Wirth (out of ETH in Switzerland) didn't
| define enhancements to Pascal. That left vendors to plug
| those gaps (like Object Pascal on Mac or Borland), which
| made those extensions non-standard. But he wasn't making a
| business out of it. He was purely academic.
| pjmlp wrote:
| He collaborated somehow with Apple on Object Pascal and
| Clascal.
| nonrandomstring wrote:
| Ada was a love-hate of mine. We had to use it for
| avionics/defence stuff and it always took a week just to
| get a project started, writing pages of type definitons.
| But:
|
| 1) It made you really think about the ideas. Sometimes it
| revealed misunderstandings in the requirements gathering or
| translation. That was the optimal time to find problems in
| the recspec and bounce them back before it got too late.
|
| 2) One you get moving in a very strongly typed language the
| rate of serious obstacles falls off rapidly. It really does
| pay yo do the work up front. I think of it as having to
| tidy up your room before you're allowed to play.
| kevlar700 wrote:
| Ada has been shown time and again to be more cost
| effective than C, C++ or Java. Even in the old days
| before Ada2022 and Spark 2014s recent improvements.
|
| http://sunnyday.mit.edu/16.355/cada_art.html
| cjfd wrote:
| C++ can do this very well, no eye bleed is involved.
| criddell wrote:
| I'm skeptical that C++ can manage anything as clear and
| concise as: type Age is new Natural range
| 0..150;
| jcelerier wrote:
| Well, for the end user it'd look like
| using age = in_range<int, 0, 150>;
|
| Not the end of the world
| 9659 wrote:
| a little bigger, more complete, example would help. this
| is something i have never seen in C++.
|
| (note C++ is a big language that changes a lot)
| ziotom78 wrote:
| Does this really define a new range-checked type? I never
| used in_range, but it seems a function [1] that you must
| call whenever you want to do the check on one variable,
| while in Pascal all the checks are injected automatically
| every time you modify a variable of that type.
|
| [1] https://en.cppreference.com/w/cpp/utility/in_range
| Rexxar wrote:
| If I understand correctly the other comments, they just
| say it's possible to do in c++ with templates/operator
| overloading/constexpr. It's not an existing type of the
| standard library. This type could work in simple
| situations but will probably not be as good as a native
| type in more complex situations.
| criddell wrote:
| That's a function, not a type.
| Jtsummers wrote:
| It's a hypothetical. The actual `in_range` template
| function doesn't work like that at all and would be
| annoying to use for this if you tried (it returns
| true/false so you have to wrap all operations in a
| conditional). jcelerier is suggesting we'd have something
| like: template<typename T, int min, int
| max> // probably not int for min/max, but whatever
| class in_range { ... };
|
| which would be instantiated with `using <my_type_name> =
| ...;` and would have all the necessary operator overloads
| and checks. Still, it's only bringing in the runtime, not
| compile time, checks and can't be "turned off" (well,
| maybe take a fourth boolean value and have two code paths
| everywhere that can be optimized down to one if you want
| to turn off the checks) like it can in Ada (whether
| that's a good idea or not depends on how well you've
| proved out your code). It's also introducing all the
| overhead of an object which isn't necessary in Ada.
| pjmlp wrote:
| Calling in_range explicilty at every spot, versus letting
| the compiler do the work, isn't the same.
| lolinder wrote:
| For those of us who don't know C++, would you care to provide
| a code sample (or a link to one) demonstrating what it would
| look like?
| cjfd wrote:
| #include <stdexcept> template <int min, int
| max> class Range { public:
| Range(int i): i(i) { if (i < min)
| throw std::runtime_error("value lower than minimum " +
| std::to_string(min)); if (i > max)
| throw std::runtime_error("value higher than maximum " +
| std::to_string(max)); } int to_int()
| const { return i; }; private: int i;
| }; using Month = Range<1, 12>; int
| main() { Month month(-1); }
|
| terminate called after throwing an instance of
| 'std::runtime_error' what(): value lower than minimum 1
| touisteur wrote:
| I wish rust had learned from that and also kept range/overflow
| checks on by default and not only in debug mode.
| lolinder wrote:
| One of the key principles of Rust is zero-overhead
| abstractions. The more runtime code they insert the harder it
| would be to sell the language to C/C++ programmers.
| trealira wrote:
| They do keep runtime array indexing checks on. I'm pretty
| sure I remember reading a comment from Steve Klabnik on
| here that they only turned off overflow checks in release
| builds by default because array range checks stay on. With
| neither over/underflow checking nor array index checks,
| it's a lot more likely you'd accidentally use an out-of-
| bounds index.
|
| For example, there was an iMessage vulnerability a while
| ago that relied on unsigned overflow to create an
| undersized buffer so that data would be written out of
| bounds later [0]. If it had been written in Rust (or Ada),
| with overflow checking on, it would have panicked upon
| overflow, and with it off, it would have panicked when
| using an out-of-bounds index. With both off, you get this
| vulnerability.
|
| [0]: https://googleprojectzero.blogspot.com/2021/12/a-deep-
| dive-i...
| touisteur wrote:
| And yet bounds checks were kept. Other compilers for other
| languages gave the option to disable some or all of the
| range checks, or to enable thorough validity checks. Not an
| all-or-nothing 'debug' toggle, but project- or file-level
| options for runtime checks.
|
| On most code I worked on the bounds checks are really not
| having a large performance impact, especially on modern
| processors and it's been a very rare occasion I had to
| remove them, most recently converting the code to SPARK and
| proving the absence or runtime error. If people are OK
| pleasing the borrow-checker I'm sure they'll enjoy
| interaction with the prover (which farms most of the work
| to why3 and SMT solvers) to make sure their optimisation is
| actually safe.
| MarcusE1W wrote:
| I think this is a good example of how Ada handles data. The
| approach is to describe the data structures and let the
| compiler handle the best implementation.
|
| I don't know if that always leads to the most efficient code,
| but what it does is that you often think about your data first
| before you start programming. It's a different type of
| prototyping. Once that is done a lot of the programme flows
| naturally around that.
|
| Nothing stops you to do that in other languages of course but
| Ada makes it quite easy and part of the concept.
|
| Together with pre- and post-contracts you get a long way in
| writing code that avoids some of the usual error groups without
| much effort.
|
| Plus it is (in my view) easier for someone to read code if you
| can quickly check the range of an integer or to read the pre-
| post-contracts of functions. It is surprising for me how much
| of an implementation is fixed, once that sort of thing is
| clear.
| bdw5204 wrote:
| You could potentially do the same thing with a class but it'd
| be really verbose:
|
| Class Age { private int value = null;
| public boolean SetValue(int value) { if (value >= 0 &&
| value <= 150) { this.value = value; return
| true; } return false; } public
| int getValue() { return this.value; }
|
| }
|
| That's approximately what the equivalent would look like in any
| language that supports classes (any errors are because it was
| typed on my phone). Being able to do that in 1 line would be
| much more convenient. That's why most programmers working in
| those languages just define age as an int and move on.
| tored wrote:
| Not only in one line but also you don't need to wrap your
| function argument with a class instantiation, you can just
| pass the integer into the function you are calling and the
| compiler takes care of the rest.
|
| And that is a problem with many class based languages, that a
| class can't work as a primitive, compare Java's int vs
| Integer. program TestType; type age
| = 0..150; procedure test(age:age);
| begin writeln(age); end;
| begin test(100); test(200); end.
| Jtsummers wrote:
| That's a runtime check which does part of what Ada will do.
| Ada will also perform compile time checks related to uses of
| these restricted range variables. SPARK can go even further
| and detect potential overflow/underflow errors (and other
| things) forcing you to add preconditions (one resolution) to
| functions that declare that the values being added (or
| otherwise operated on) are small enough that overflow won't
| happen.
|
| Ada will also use a reasonable storage size. If Age is ranged
| [0,150] then it can place it in a single byte (you can also
| influence the storage size so you can increase this if you
| want for some reason), there is less memory overhead than an
| object. Since it's a range it brings in (automatically) all
| the arithmetic operations you'd expect.
|
| EDIT: Regarding storage. That's technically implementation
| defined. GNAT, at least, will use a reasonable storage size
| by default, you have to override it and ask for something
| bigger. I had occasion to use Green Hills years ago and it
| did the same as I recall. I'd expect any other commercial
| implementation to use an appropriate size and not something
| absurdly large like 64 bits for a range that easily fits in 8
| bits, it would not fit their general market (safety-critical,
| performance-critical, real-time, and embedded systems). Poor
| use of memory could cripple the utility of a compiler in
| these kinds of systems (especially performance-critical,
| real-time, and embedded).
| kaba0 wrote:
| You can't generally make it a strictly compile-time
| feature, as far as I know. You do need what constitutes to
| overflow checks, basically.
| Jtsummers wrote:
| Yes, but Ada does offer some compile time checks. That
| example _only_ does the runtime check part of range
| checking, it does do compile time checking for type
| mismatches (or would in most statically typed OO
| languages). And SPARK offers more compile time checks
| than plain Ada (but it restricts what you can do since it
| 's a subset of Ada, useful within specific packages or
| for libraries but not necessarily globally depending on
| circumstances).
|
| Ada also offers the option to turn off the checks if you
| think you know what you're doing (you've proved out your
| system with SPARK, a lot of testing, or a lot of other
| analysis).
|
| For instance the class based version won't catch this
| until runtime (I'll assume some operator overloading for
| ease of demonstration, syntax is C-ish just because it's
| more engrained in my fingers): // suppose
| range is 0..127 my_type val = 200; // runtime error
| with the class version, compile time in Ada
| my_type a, b, c; // somehow a and b get assigned
| values c = a + b; // SPARK can warn about potential
| overflow here, class based version won't, straight Ada
| won't
| samatman wrote:
| Julia has Value types for when a primitive type can be used
| during compilation:
| https://docs.julialang.org/en/v1.10/manual/types/#%22Value-t...
|
| They aren't the same thing, I mostly brought them up to point
| out that Ada can only throw compile-time errors for range types
| under limited circumstances, otherwise you're expressing a
| runtime bounds check using the type system. Which I agree is a
| nice feature for a static language, which Julia is not; Ada
| won't let you leave out the runtime check, so numbers outside
| the range are unrepresentable in the program.
|
| It should be possible to use Value types to write a generated
| function which instantiates a constructor for a primitive type
| which is restricted to a range. All type errors in Julia are at
| runtime, but like I said, Ada has to push many range exceptions
| to runtime as well, and the property that a number outside of
| the range is unrepresentable is preserved in both cases.
| Although I can't point you at an implementation of this in
| Julia, I'm increasingly confident that it could be done.
|
| Julia's type system is also powerful enough to express units
| https://github.com/PainterQubits/Unitful.jl
| kevlar700 wrote:
| Ada is very flexible and does let you leave out the runtime
| check. However the program will be stopped or atleast be
| exceptive by default if a logic error creates an invalid
| value that you haven't checked the validity of. Spark can be
| used for a higher degree of value analysis at compile time
| because flow analysis is obviously needed in many cases.
| Volatility can still be an issue but in most cases Ada knows
| the inputs such as for API usage validity.
| tubthumper8 wrote:
| In this example, how do you get an instance of `Age`? i.e. If a
| user is asked for their age and they type in a number
| Jtsummers wrote:
| Ada has had generics since the start.
|
| https://learn.adacore.com/courses/intro-to-
| ada/chapters/gene... - Generics in Ada. This is from a good
| intro course for Ada and the site has several other courses
| as well.
|
| https://learn.adacore.com/courses/intro-to-
| ada/chapters/gene... - generic IO portion of the above.
|
| A sketch, you'd need to place these lines in valid locations
| since it won't run directly like this but these are the lines
| you'd use: type Age is range 0..150;
| package Age_IO is new Ada.Text_IO.Integer_IO (Age);
| -- declared somewhere A : Age; -- from user input
| Age_IO.Get(A); -- if we "use" the above use
| Age_IO; -- now we can skip the prefix Get(A);
|
| This would result in a runtime error if the read value is out
| of range.
| kazinator wrote:
| > _and if you try to pass some other integer type in, it will
| fail._
|
| But since that passage has to be done in order to complete the
| programmer's task, the fix will be to stick in a coercion. Over
| time, those things will pile up and uglify the program.
|
| Then an age shows up that is 151, because of non-human animals
| and objects. Oops!
| jeff-davis wrote:
| It looks like Ada 2022 has a way to track at compile time whether
| functions block or not. Seems cool for async programming.
|
| http://www.ada-auth.org/standards/22over/html/Ov22-2-2.html
| synack wrote:
| I don't think any compiler implements that yet.
| jeff-davis wrote:
| Do you believe it was designed and standardized as a
| reasonably implementable feature?
| mikeflynn wrote:
| Ada 95 was the first language taught in my college CompSci
| program back in 1999 (this might have even been my textbook).
|
| Everyone would complain about Ada because no one had heard of it
| before, but looking back it was the right call. A great language
| to learn the basics on and not get in to trouble.
|
| They've since moved to Java and now probably something else.
___________________________________________________________________
(page generated 2024-03-02 23:01 UTC)