Giving Ada a Chance
ajxs.me
ajxs.me
At my university, the first courses you took in CS used Ada. I think it was a really good choice but I was in the minority I guess because after my year they switched to either using Java or Python depending on who taught which of the courses in that first series.
People found it frustrating how much work it'd take to get their programs to even compile but that's a good thing in my view. If it wasn't compiling, that was normally because the compiler found an error that would still be there at runtime in another language.
Makes sense if they're students working on small projects. Ada is explicitly designed to make large programs readable, and willingly trades off on writeability when the two come into conflict. It isn't going to shine if you're writing small 'single shot' applications, that isn't what Ada is for.
(Ada also commits to using many English language words where languages like C use symbols. SQL does this too. I'm not sold on the idea that this improves readability. Of course, there's far more to Ada than the skin-deep matter of its wordy syntax.)
This is similar to the readability/writeability tradeoff of moving from JavaScript (more writeable) to TypeScript (more readable and more refactorable). See the current discussion thread at [0].
Interesting reading (for some of us at least): the original Ada design rationale document, from 1986 [1].
I think you mean a syntactic facelift, without changing semantics. You mean making the syntax look more like TypeScript, Kotlin, or something, right?
I don't think it's a serious problem that Ada uses and then rather than &&, and uses or else rather than ||. It's the kind of thing you can get used to with time.
Ada's and and or operators have no direct equivalents in C or C++.
https://en.wikipedia.org/wiki/Short-circuit_evaluation#Suppo...
Wasn't the hypothesis behind COBOL that this would allow managers to gain a certain level of understanding of the code?
I find that it made COBOL code notoriously unreadable.
It's lack of a normal, standard scoping model alone teaches a very flawed reasoning about computer programs.
Scoping in class definitions is weird, yes. But is that your complaint? What's wrong with the scoping model in general?
That for-loops work by assignment rather than creating a new scope, or that if-conditions do not create a new scope for their arms is most unusual.
Not only does it not teach programmers to properly reason about scope, but it results into subtle bugs that are easy to miss. Consider the following:
list = []
for x in iterator:
list.append(lambda y: some_code_that_closes_over(x))
This almost certainly does not behave as the programmer intended, for the for-loop does not create a new scope, so all iterations of the loop share the same scope, and thus every closure that closes over `x` handles the same `x`, which will have the value that `x` had at the last loop, thus effectively mutating the closure after appending it to the list, so that the list will only contain effectively identical closures.The proper way do it is by using the fact that functions do create a scope:
list = []
def loop_function(x):
list.append(lambda y: some_code_that_coses_over(x))
for x in iterator:
loop_function(x)
Certainly code that looks quite hackey to work around the lack of normal, expected block scoping. list.append(functools.partial(some_code_that_closes_over, x))
If you replace a lambda with a function definition in the for loop, it's easier to see the scoping issue is actually a closure. And that makes sense because lambda is shorthand for a function.Apart from that, your code technically does not do the same thing as it calls the inner function with two arguments, whereas mine simply discards the second argument.
Maybe your complaint is about the fact that statements inside a lambda isn't called until the lambda is called. But again, lambda's are just functions, and closures are closures.
Or maybe your argument is that closures are easy to make in python, which is true. But that doesn't mean the language is bad, since lots of languages use closures.
x = something
# some lines of code
for x in iterator:
# something
# try to use x again here
# x has been re-assigned by the loop
In about any other language, the loop would create it's own scope, and thus shadow the existence of the outer `x`, rather than re-assigning it.But even so, this is a lousy example since x is already defined at the top. So the for loop could theoretically use the defined x already.
I think what you're looking for is something akin to the following in C:
int x = 5;
for (int x=0; x<10; x++) {
int y = x;
}
But I would argue that this is terrible C code since the inner x shadows the outer scope leading to confusion as to which x the loop is iterating on. But you could also write the for loop like this: for (x<0; x<10; x++) { ... }
So C doesn't dictate the scope of the loop which could be argued by your standards, even more confusing than python. Python just says your scope is just function level. Subtle C code changes can create scoping bugs.So even the scoping example your trying to use is fraught with issues for new developers. Is "function-level" scoping better than "C-style" scoping say? I tend to think that function level scoping forces developers to simplify their code as it discourages symbolic shadows.
In your example, the variable `x` is also shared with all iterations of the loop. Rather, it is more so as so in C, like syntax what is the common approach:
while(1) {
int x = next(iterator)
if(STOPITER) { break }
/* code that uses x */
}
Every iteration of the loop receives a brand new `x` rather than re-assigning the old `x`, this problem is illustrated by creating a closure that closes over the `x`, for which the expected behavior is not that the `x` is then assigned to another value on the next iteration of the loop.The only way to achieve this in Python is to create an ad-hoc function which is passed `x` as a formal parameter, for every new function call in Python does create a new scope rather than simply re-assigning to the last one.
In that case I would prefer to be explicit that the closure "wraps" a new instance of the variable rather than relying upon implicit language behavior to guarantee the scope is correct.
As python says in "import this": "Explicit is better than implicit."
Then you have argued that using any form of functions or subroutines in any language is bad design.
> since now you're constantly asking "When does a variable leave scope." If I change the scope of a variable, it causes a subtle change that breaks the closure.
Yes, that is what one must ask oneself as a programmer and that is what programmers who have not been taught wrong practices by having used Python as their introduction are instinctively constantly asking themselves.
Python programmers must also ask themselves this whenever they use functions and Python comes with a variety of ugly hacks around it's initial wanton design by a programmer who clearly does not understand scope how he originally designed it such as `nonlocal`.
Inside any function in Python there are four kinds of variables in terms of their scope: normal, formal, global, and nonlocal, each of them has a different scope and the programmer best be mindful of which is which as he codes, because they have widely different semantics and because Python has the same syntax for assignment and initialization and this difference especially creates very different interpretations for those four types.
> In that case I would prefer to be explicit that the closure "wraps" a new instance of the variable rather than relying upon implicit language behavior to guarantee the scope is correct.
I feel that you have a fundamental misunderstanding of what a closure or function is if you think it even possible for it to wrap a new instance of a variable.
You seem to be of the same misunderstanding as another user above that `let x = x;`-esque behavior would affect this issue in any way rather than be an irrelevant line.
> As python says in "import this": "Explicit is better than implicit."
Not only is this very rich coming from a language that uses exceptions for flow control, and the same syntax for initialization and assignment, it's also irrelevant and not a matter of explicitness versus implicitness.
Whether wrapping variables would be implicit or explicit in this context would not have solved the issue at all. Even if Python mandated explicit wrapping or made shadowing illegal, which some languages do, for which there is argument to be had, it would not change this subtle bug at all.
(And if we broaden our view to include more of software engineering in general, like solving the right problems and validating hypotheses with fast feedback, it seems even more insignificant.)
Python code seems to either be written from a mentality of not at all caring about variable lifetime and making it far longer than it should be, or programmers that do care, and uses classes and functions as makeshift tools to attempt to limit the lifetime of variables to make up for the lack of block scope of the language.
list = []
for x in iterator:
list.append(lambda y, x=x: some_code_that_closes_over(x))
That way the closure is explicit and much clearer when you look at it later.("list" is a builtin, so not a good variable name.)
Your code is illegal Python on two levels.
- Lambdas in Python do not allow assignment at all.
- Variables in Python cannot be assigned using themselves in their r.h.s., without first being assigned something else, because Python's scoping is strange.
You serve well as an example of a programmer that does not understand Python's semantics here, because they are very counter-intuitive, and you also serve as an example of a programmer that does not understand how scope works at all, even if your example be worked into something of valid Python syntax:
thunks = []
for x in "abcd":
def thunk():
y = x
print(y, end='')
thunks.append(thunk)
for thunk in thunks:
thunk()
The output is `dddd`, not `abcd`; the `y = x` part is completely irrelevant in this case, for when the thunk is called, `x` has already been re-assigned, and so `y = x`, is assigned the new value.Again, `x` is re-assigned on every new iteration of the loop, as such the `x` in every single one of those thunks contains the value `x` had at the last iteration of the loop when the loop completes.
There are many ways to solve this issue, such as the one I initially gave, but yours isn't one of them and that you thought it was shows the counter-intuitive nature of Python's behavior here.
> ("list" is a builtin, so not a good variable name.)
Which would be another problem with Python's lack of scope. Shadowing the names of library functions and constants is not problematic in languages with proper block scope.
Yours:
In [1]: list = []
...: for x in range(5):
...: list.append(lambda y: y+x)
...: [f(100) for f in list]
Out[1]: [104, 104, 104, 104, 104]
Mine: In [2]: list = []
...: for x in range(5):
...: list.append(lambda y, x=x: y+x)
...: [f(100) for f in list]
Out[2]: [100, 101, 102, 103, 104]
I understand the semantics just fine, and it's very clear what's going on. In my version, each lambda has two parameters, one called y, and one called x. The one called x has a default value, and the default value is whatever the loop variable x's current value is: a copy is made of the loop variable's value, and stored as a default parameter value. There is no assignment, and a variable is not assigned to itself. The parameter has the same name, but the scope is different; within the lambda, the parameter shadows external variables, just as in any function.This is idiomatic Python code, taught to beginners (search for n=n - it's about this same loop thing): https://realpython.com/python-lambda/
You do the same thing when passing parameters through to an inner function, like this:
def outer(x, y):
def inner(z, x=x):
return z+x
print(inner(2*y))
Shadowing list in a function scope is fine (just confusing), but your code does it in the global scope.You seem to have a bone to pick with Python for some reason, but to me your attempts at criticism fall completely flat, as they are either factually incorrect or amount to "Python is different from [language x]".
lambda x: x = x; do_thing(x)
Rather than the: lambda x, x = x: do_thing(x)
And yes, your example works, and is functionally similar to my initial solution of using that function scope does exist by simply creating a function for no other reason than to call it to create a new scope.That doesn't stop that both examples are ugly hacks needed to solve a problem with the language that most languages do not have. And despite your calling it idiomatic, I have never seen your example in the wild and it is, frankly, simply a hack that few programmers that haven't been explicitly taught this trick would quickly see the intend behind.
IMO the ideal programming track would be as follows:
Intro to programming track:
* Assembly --> to understand the basics of how computers work and learn simple procedural and abstraction rules.
* C --> more procedural code, more abstractions, and higher order memory management
* Java --> to learn interfaces, object oriented code, and an introduction to functional programming concepts like lambdas, pure functions, the value of immutable objects
* {Racket/LISP or Favorite functional language} --> to gain intuition with more pure functional programming
Then, I'd have separate tools track:
* web dev
* database
* embedded dev
* AI, etc.
Then a separate theory track:
* context free languages, regular expressions, compiler design
* algorithms and data structures
* some network theory and fault tolerance results
Then I'd have a professional track:
* tools: learn source control systems, IDEs, debuggers, profilers, bug management systems, different dev systems, using Jenkins or other CIT tools.
* skills: learn about secure coding (best practices for writing auditable code with known failure modes), efficient coding (calculating big O for a given code, fixing performance bottlenecks), maintainable coding (best practices for decoupling code, writing testable code, writing unit tests)
* theory: track where you learn advanced algorithm theory, design patterns, hopefully centered around case studies.
With elective tracks for things like OS theory, networking, etc.
Too many people leave college with lots of information about data structures and algorithms but don't know how to write a unit test, how to use an IDE, how to write unit tests, how to write secure, maintainable code.
I don't think it's really practical for every scenario. I still think it's a great tool for teaching students about many aspects of how a modern computer, or operating-system functions. You definitely won't get far in gaining a holistic understanding of any modern operating-system without understanding C.
Jesus Christ, Java as an introduction to functional programming? I don't even know what to say.
There are many languages which hammer worse practices than either such as POSIX shell scripts, but they are seldom used as a teaching language.
Python is in the unique space of having made horrible design decisions but somehow often used as a teaching language.
One would assume that programmers are to understand such concepts as block scoping rules and the difference between lexical and dynamic variables. — how is Python to teach them that?
While in CS they tends to start to Python or Java. And you start learning all the OO, Procedure or whatever paradigm before going into Web or other area of development with PHP. Where you get some results earlier.
I used to think EE way of teaching sucks. Old fashioned, not following the industry trend. Boring. Teaching you about OSI Models while CS students were already having some fun with the higher level Web Development.
Now I tend to think EE's way is far better.
After a few years in industry, I believe that CS should start with either C or Scheme. C to teach you about real machines, Scheme to ignore the machine and do math (algorithms).
But I agree that learning C is valuable because I believe that learning about manual memory management is valuable.
You need to do it in Ada too :-)!
>C was designed for portability across different architectures Absolute bullshit. This is a claim that has been going on for far too long, the whole "portable assembler" myth. IEEE694-1985 is a portable assembler, hell read "Using a high level language as a cross assembler" ( DOI: 10.1145/954269.954277 ), for that matter.
>and, believe it or not, was considered high-level and abstract at one time. The "gotcha!"-factor was increasingly ignored when I was learning about computer languages, but I had an advantage in having my first language be Turbo Pascal and self-taught using the manual and compiler.
> But I agree that learning C is valuable because I believe that learning about manual memory management is valuable. That's doable much easier in Ada, Forth, or BLISS. C is becoming more and more of a liability, to the point I would say that it has no real value in any professional setting, and an incredibly limited one in the academic.
Fairly easily. My professional career has been mainly in maintenance and, as such, I get to see the gritty back-end of things, the end-result of all the technical-debt... and being more correctness and security-minded than most, I often note how a good design could help prevent problems, both on the language being used to implement and on the project itself.
From that perspective, most defenses of C as productive or useful fall flat on their faces, especially in recent years as security becomes more and more important a concern — about the only place that C makes any sense anymore is micro-controllers because "all the micro-controllers have a C compiler." — But let's not make the mistake thinking that "having a C compiler" means that C is a good (or even appropriate) language for the task.
Forth, Assembly, and Ada all exceed C's capabilities in many of what have been traditionally claimed as C's strong suits: * Ada: much, MUCH, better as a systems-language. Any project of medium or large size should seriously consider Ada instead of C. * Assembly: very fine control, especially important for the severely-constrained controllers. * Forth: Very fast, very low-level; would recommend for small/medium-small projects on small controllers. (Doesn't have calling-conventions, doesn't manipulate stack-frames; this makes it faster than C.)
> Look at the amount of C code in any Linux distribution, even ignoring the kernel itself.
And? That says NOTHING about having value in a professional setting, only that it (a) was chosen by a project that got big, and (b) enjoys popularity.
Nearly EVERYTHING that C is claimed to be [very] good about is done much better by some other language. C is particularly bad at large-systems, given the complete lack of modules, and entirely unsuitable for many things that it's commonly used for like multithreaded applications (honestly, take a look at Ada's TASK construct and consider how that might be used in [e.g.] a game-engine).
It is used inappropriately? Sometimes. Can there be better alternatives? Yes. But to say it has "no real value" is a bit extreme.
In the case of C, and other C-like languages, I most recently have four or five custom-made programs [some requiring specialized tools] that have little/nothing in the way of documentation: what it does, the "why-for"/motivation, any sort of high-level architecture-plan, or design-documents.
Fortunately for me, most of the programs actually do have documentation thanks to a true hero that left before I arrived.
The lower level C library was pretty clever. The original developer of most of it left about a year into my tenure there. He was one of the smartest guys I ever worked with.
The C++ "app" layer was a different story. The worst part it was a 3000 line switch/case block with about 100 different cases, chock full of copy-and-pasted code. It went on... and on... and on... I still have nightmares about it.
Ouch. That sounds brutal. If I had to do something similar, or maintain that, in Ada I'd leverage nested subprograms, local type/subtype definitions, and mandatory case-coverage — and I've done similar with VMs, particular opcodes — so you get something like:
Type Opcode is ( NOP, Add_A, SUB_A, ..., Rem_D );
Procedure Execute_Instruction( State : in out Machine_State; Instructions : in Instruction_Stream ) is
Subtype A_Series is Opcode range Add_A..Sub_A;
Subtype B_Series is Opcode range Add_B..Sub_B;
Subtype C_Series is Opcode range Add_C..Sub_C;
Subtype D_Series is Opcode range Add_D..Sub_D;
Procedure Do_Add_A;
-- other subprograms.
Current : Opcode renames Decode( Next_Token( Instructions ) );
--...
Begin
Case Current is
when A_Series =>
case A_Series'(Current) is
when Add_A => Do_Add_A;
end case;
-- other series.
end case;
End Execute_Instruction;
Of course you could structure it so that all the Do_OPCODE subprograms are local to the top-level switch, or local to the nested switches, as best suits the design; or decompose along 'families' of operation (Add_A, Add_B, Add_C, Add_D), but the important thing there is keeping things local/nested for maintainability.I learned Basic, C, Assembly, and Matlab in college (in that order). However, I was never a very good programmer. After graduating, I bought an intro to Python book and read it cover to cover and did the examples. I then started writing scripts and it all kind of clicked. I found it really simple to build stuff. There's lists, tuples, dictionaries, functions, iterating, easy branching...etc. There wasn't a whole lot to remember. I did find it confusing at first why some things used function syntax min(a) and other things used object oriented notation like mylist.sort(), but it wasn't too hard to remember all the stuff I actually needed. I was able to start using classes when I felt I was ready.
Since then I've read books on and played around with Clojure, Common Lisp, Haskell, APL, Forth, Prolog, Ada, Powershell, Perl, Bash, Awk, Julia, C#, SQL...etc. During that time, I've found that Python holds it's ground in being very expressive, while easy for others to understand and performant enough for most uses with libraries like Numpy. Each time I try to move to a "grown-up" language like Java, I'm shocked at how verbose and clunky everything is. Sure it's great for production systems, but it's awful at just getting stuff done.
Python is used almost exclusively by all engineers at my company (some C#) in hundreds of automation scripts. Some programs are 10,000+ lines of code and are still maintained and easily read by others. We're mostly electrical engineers too, so everyone picked up coding on their own and we find most codebases are still pretty consistent.
I think Ada is a neat language for high reliability systems, but I don't think I'd choose it as a teaching language due to a lot of the friction with getting simple programs to work and wrestling with types. I've had very few issues with types in Python. I generally just use string, int, and float conversions when needed.
I expected a small, simple beginner-friendly language, optimized for gluing stuff together, but I've found a huge number of overlapping features and idioms, more ways to do the same thing than in Scala, half-broken libraries with poor inline documentation, lot of "stringly" typed code everywhere where programmers don't seem to know other types than strings, ints and dicts, no ADTs / pattern matching, package version conflicts and all that with no help from the type system and unreliable help from IDE autocomplete.
Type annotations help a bit, but they are still far behind what's available in modern statically typed languages.
I've also run into a few things that were weirdly complex to do compared to other languages - e.g sending rest requests in parallel. Something I'd expect a glue language shine at.
It just feels like a major step backwards at least vs Scala and Rust which I used most recently.
So maybe it is a matter of earlier experience, familiarity and expectations?
I suppose it depends on your intent. If you're writing a bunch of glue code, I seriously doubt Rust will be anywhere near as easy to reason about. Based on my own experience with the language, I can't imagine any coworkers ever grokking the language. Python on the other hand is picked up pretty quick. The beauty of Python is you don't need all that boilerplate nonsense like even having yo know what ADTs are or pattern matching or factory factory class. In Python, you just write code. A lot of people just have a few globals and a bunch of little functions and others use classes.
Maybe your domain is different? I might hate Python as much as you if I had to build a bunch of large backend code bases.
I admit Rust is much harder to learn initially, but once learned properly, the amount of code in both languages is very similar (as long as we're not comparing one program calling out to a library and another one doing everything from scratch). Both can be very high level.
Here is a study where they've found Python to be not much less verbose than Java actually, despite Java being generally considered a verbose language:
https://blog.wolfram.com/2012/11/14/code-length-measured-in-...
> I seriously doubt Rust will be anywhere near as easy to reason about.
I find Rust easier to reason about because there are certain constraints on sharing mutable state forcing developers to keep to very simple data flows (complex data flows / dependencies are getting hard very quickly and the compiler will fight that a lot with you). Python has none of these, so reasoning about data structures that can be freely shared and mutated anywhere can be hard. Python needs a lot of self-discipline to not end up with unmaintainable code.
I'm sure Rust makes lots of sense when it comes to concurrency and systems programming, but that's not where Python shines or is meant to be used. In scripting, task automation, data science...etc it is really hard to beat. So maybe we're arguing over the usage of an axe and a sword on the battlefield and not about different swords lol.
So the differences you observed might be not because of a language itself, but the complexity of projects these languages are applied to and cultural differences of the teams. So far I haven't worked on Python projects as big (in terms of functionality) as Java projects I've seen.
As for data science, so far I haven't stumbled upon any Python code that wouldn't look very similar translated to Java, Scala, R or Rust, assuming same libraries existed. Most of the code is very simple really: load data into some vector/matrix, apply some library code on it, get a different vector/matrix back, etc. The only thing that holds me to Python really are libraries.
As for concurrency - gluing systems together sometimes needs concurrency to cut the latency down. And in data science parallelism also means performance, and often it is needed. I'm not that convinced Python is a clear winner here.
Ada is really quite good here, the Task is something such that I would say that if your application is inherently going to be dealing with concurrent processes you should seriously consider Ada. -- The 2020 standard is adding a Parallel keyword/block so that it should be (in theory) as easy to use e.g. CUDA parallelization as simply as compiling your code with a CUDA-aware compiler.
Ada has a pretty nice set of numerics (Ada.Numerics.*), but the "lack of libraries" is almost a non-issue when the foreign-function interface is as simple as:
Function Example_1(Item : Some_Matrix) result Some_Matrix
with Import, Convention => Fortran,
External_Name => "EX1";
> or any REPL functionalityThere are a few people coming in from data-science who lament the lack of REPL, while I might do one, it's rather low on my list, though I think HAC is trying for REPL or something like it. (I haven't used HAC yet.)
> Language ecosystems are the thing that matters. I'd rather write Avionics or high speed trading systems in Ada, but data science? Maybe for some very niche problems.
I agree that the ecosystems are what matters, and this alone would be enough to fuel my general hatred of C: the amount of time, effort, and money spent on C, whether "making a better C" or crippling tools (eg text-diff vs real semantic diff) or making "it 'mostly' works" accepted is simply astronomical.
“Design Patterns” in software (particularly when talking about what you see in code) are used largely to refer to things that are not just design patterns, but a combination of design pattern plus workaround for limitations in the facilities for reusable abstraction in the target language that prevent implementing the design pattern via reusable abstraction.
In that use, there aren't really “OOP design patterns”.
I remember doing a bit of comparison of the syntax between languages, and Ada's lack of anything like a switch/case statement stood out, but the instructor did talk up the idea that if you could get it to compile it would run in Ada, assuming you didn't hit a compiler bug. Apparently getting the compilers working properly was a problem at the time.
case X is
when 1 => ...;
when 2 => ...;
when 3 | 4 | 100 => ...
when others => ...
end case;They do this because of intense negative feedback from students who don't like having to learn languages for which there is no job market.
I think it was more that we didn't have enough professors who were bought-in to Ada to handle the volume of students in those initial courses. The professor who used to teach all the intro courses (and was the biggest advocate for Ada there) became the department chair and let other people take over those courses so he could focus what time he had on some of the upper level courses. Pretty sure they still use Ada in their concurrent programming course too.
Edit: Got curious and I think this is all the languages I used as part of an undergrad CS degree in approximately the order I first used them:
Ada, Racket, C, Make, x86 ASM, Java, Bash, batch script, PHP, JavaScript, MySql, Python 2, Inform 7, Blender Game Engine visual scripting, ICON, Lua, Erlang, and ROBOTC
I'm honestly surprised how much variety there was despite the only academic language being Racket.
At the same time colleges are morphing into expensive trade schools, their advertising is full of phrases like "preparing you for the real world".
Polytechnic, with 3 years duration.
Do you want to learn to learn, and be prepared for any kind of challenge, with a professional title?
University, with 5 years duration
Now with Bologna, the rules changed a bit, however the universities sell the fact that now with 5 years the degree includes the master title, which used to require up to 3 years more on top of the 5.
As far as I am aware, other European countries have similar approaches, and in most of them in what concerns state universities, the biggest expenses are lodging, food and getting the required teaching materials.
I liked Ada a lot, but Java gave me employment after school.
This is a well known pattern in services now too, lots of platforms are buying user market share by offering free/cheap service for college students and reaping the revenue when the students move on / generate invites.
I wouldn't call this bribes but you can make an argument that it's buying mindshare that you wouldn't get on your own merits.
Quite to the contrary. If anything, Java was helping sales of future Sun boxes, rather than the other way around. Sun worked hard to make sure Solaris ran Java efficiently and stably, so if you were a Java developer you might consider Sun boxen professionally.
Sun didn't need to bride our teachers, doing the same in C and C++ was such a pain with 1996 compilers and POSIX, that they didn't thought twice about changing into an interpreted language.
> Its foreign function interface seems particularly poorly implemented. The official Rust documentation suggests the use of the external third-party libc library (called a 'crate' in Rust parlance) to provide the type definitions necessary to interface with C programs. As of the time of writing, this crate has had 95 releases. Contrast this with Ada’s Interfaces.C package, which was added the language in Ada 95 and hasn’t needed to change in any fundamental way since.
Rust's libc crate isn't third-party, it's first-party, developed by the Rust project itself: https://github.com/rust-lang/libc/ . It's also not just for type definitions necessary to interface with C programs; here's the first heading and first paragraph of its README:
"libc - Raw FFI bindings to platforms' system libraries"
"libc provides all of the definitions necessary to easily interoperate with C code (or "C-like" code) on each of the platforms that Rust supports. This includes type definitions (e.g. c_int), constants (e.g. EINVAL) as well as function headers (e.g. malloc)."
The fact that this library contains low-level type definitions for every platform that Rust supports explains why it's had more than one release: new platforms get added, platforms add new interfaces, and platforms change the definitions of existing interfaces (possibly incompatibly, which explains why this isn't in the standard library).
> It lacks basic features necessary for the task, like bitfields, and data structure packing.
The latter is achieved via the built-in `repr(packed)` attribute (https://doc.rust-lang.org/nomicon/other-reprs.html#reprpacke...) and the former is provided by the bitflags crate: https://crates.io/crates/bitflags (while unlike libc this does not live under the rust-lang org on Github, it does live under its own org which appears to be populated exclusively by Rust project team members).
Regarding bitfields: At the risk of sounding a little old-fashioned, I don't like the idea of having to import external packages to provide these kinds of fundamental features. The article hints as much. It might be a bit of a culture clash however I feel that learning the different styles and interfaces of a bunch of external packages is an extra, undesirable cognitive burden imposed on the developer. Plus, "A macro to generate structures which behave like bitflags" (The crate's official description) doesn't sound very robust to me. It sounds like precisely the kind of hack that a future release could break.
In fairness, it should be mentioned that the C standard does not guarantee the layout and order of individual bitfields either (refer to section 6.7.2.1 paragraph 11 of the C1X standard). Even though the usage of bitfields is common in C, it's not without its issues.
This is referring to the fact that you have to be careful when accessing the fields of a packed struct that everything is aligned correctly. Normally anything where "you have to be careful" in order to uphold memory safety requires use of the `unsafe` keyword, but due to an oversight Rust doesn't currently require it in this instance. So, in typical Rust fashion, this isn't referring to anything sinister like a compiler miscompilation, it's just an error that Rust isn't being as paranoid as it strives to be. :P
The patch to require this `unsafe` annotation was actually just filed last week; it will become a warning first, then might become a hard error for users who opt-in to the upcoming 2021 edition: https://github.com/rust-lang/rust/pull/82525
No, there's a huge difference between UNDEFINED BEHAVIOR and UNSAFE BEHAVIOR (in the sense of "be careful here").
Take Ada's "Unchecked_Conversion" function, it operates essentially the same as C++'s bitwise-cast, and thus is unsafe ("be careful" sense) but is not undefined.
https://github.com/rust-lang/rust/issues/27060#issuecomment-...
Basically, Intel goofed back when SSE was first introduced, and some compilers (including both gcc and llvm) got tripped up. Intel made two instructions for loading SSE registers, a normal one and one that would take alignment faults. There was the suggestion that the one with alignment faults would perform better, so people used it. In later processors, the tiny difference went away. So now you have a useless instruction supported by the hardware, and it is getting emitted by LLVM.
All the other instructions that could be emitted by llvm, and all the instructions that should be emitted by llvm, do not take alignment faults.
The linked crate appears to implement an unrelated feature. c2rust-bitfields [0] or rust-bitfield [1] might be better examples.
Rust's working on adding native support for bitfields, but it seems that that might be a while out at this time [2, 3]
[0]: https://crates.io/crates/c2rust-bitfields
[1]: https://github.com/dzamlo/rust-bitfield
Having a background in C, I went back and forth with Ada for years, without really jumping all in. In the last couple years in particular, with the growing popularity of Rust, I started to renew my interest.
I'm reminded of a popular reddit thread on r/Ada-- someone called Rust a "toy language", which prompted the valid response that Rust is being used in a lot of commercial products lately. The response[0] kind of brings home the caliber that Ada is capable of, starting with: > Rust being used in commercial products isn’t really the same ballpark as what I’m talking about. It’s not even the same game.
It seems like the easiest way to trend on HN is to make a post such as "<old software> rewritten in Rust", but each time I see more and more people advertising Rust, I just wonder why Ada didn't get the credit it deserved as being absolutely bulletproof. Eventually, I came across an "Ada Manifesto" of sorts [1] that finally pushed me to "put my money where my mouth is" and start going all in with the language. (the same author of that "Manifesto" maintained a "Should have Used Ada"[2] series for a while that points out just how using Ada could have stopped certain security vulnerabilities from ever being a problem in the first place)
Ada is anything but dead and there's a lot of interesting things coming out for the 202x specification. I hope to see community enthusiasm grow as people begin to shift their interest more and more to safe languages.
[0]: https://old.reddit.com/r/ada/comments/js6edd/regarding_this_... [1]: https://old.reddit.com/r/ada/comments/7p12n3/going_allin_wit... [2]: https://annexi-strayline.com/blog/
It's not. All languages make trade-offs in the performance-convenience-safety-etc space, and Ada's choice is not "100% safety". It lacks memory safety and has holes in its type system: https://www.enyo.de/fw/notes/ada-type-safety.html
Aside from simply making manual memory management less frequent (with things like variably sized arrays), it has memory pools and subpools to handle more large-scale memory safety issues. Essentially you can define the scope for all allocations of a type.
It's an interesting tradeoff, though unfortunately I haven't seen much discussion about it.
So I can write guards like
if(stack_allocated_ptr(thingie))
{
exit_critical_error( "oopies");
}And even then, I don't think Rust's mechanisms even can be 100% as they are now.
Ada's pools are probably the one that is most related to Moore's Law and actually capable of 100%, or so I'd expect.
type OperatingTemp is range 33 .. 90;
And then declare variables of that type and they will be range checked - an exception will be thrown if the variable goes out of that range. Wish more languages had this feature. type Character_Histogram is Array (range 'A'..'Z') of Integer;
-- syntax may be off, not enough practice to do this cold right now type
TPerson = record
name: string;
age: integer;
end;
TThrteenPersons = array[10..22] of TPerson;
procedure Check(const aPerson: TPerson);
var
vPerson: TThrteenPersons;
begin
vPerson[11] := aPerson; // this is ok
vPerson[1] := aPerson; // this one gives compile error
end;
alternatively if runtime range checking is on then vPerson[i] = aPerson will raise the exception if i is out of range Histogram : Character_Histogram;
...
Histogram['A'] := Histogram['A'] + 1;
(Ok, a bit more work because I didn't initialize the histogram to 0.)This modern pascal is very feature rich and has plenty of fancy bells and whistles.
So you can have arrays where the index runs from -10 to +20 or from +7 to +26 for instance. Booleans are also ordinal types so you can have an array where the index is boolean.
type Colors is (Red, Green, Blue, Black, Gray, White);
It has to be a consecutive group of them, taken in order, as the specification of even this enumeration has an ordering to it as far as Ada is concerned. So I can make an array using all of Colors as the index, or some subset but it has to be, say, Red..Blue and not Red|Blue skipping over Green.And finally VHDL is strongly typed. Verilog is pretty much C-typed meaning it's pretty weak. About a dozen years ago I worked at an EDA company and one of my tasks was to run our generated HDL code through a popular industry linting tool. There were hundreds linting problems with the Verilog code that needed to be fixed. In the VHDL code there were only a couple of things that needed to be addressed - this was mostly due to VHDL's strong type system preventing many of the problems that showed up on the Verilog side.
That idea was neatly generalized 30 years ago. So expect it to start showing up as the hot new thing in another 20.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...
In Java too, don’t know about other languages though...
The benefit of this in Ada is that it is universal, and it requires the programmer to do nothing special later on except to not turn off the runtime checks. Using that above example, this would be a runtime error:
Reading : Integer := Get_A_Reading(...); -- In some run Reading gets 0 for some reason
Temperature : OperatingTemp;
...
Temperature := OperatingTemp'Value(Reading); -- runtime error in Ada if Reading is invalid
And because OperatingTemp is a distinct type, you never even have the option to do: Temperature := Reading; -- doesn't compile
Also, in most OO languages where you'd make temperature an internal, private variable you can prevent external users of the class from interacting with it incorrectly, but you can't control internal users. Consider this Java-ish example: class Thermostat {
private int temperature; // should only be in [33,90]
public void SetTemperature(int value) { // check range and set }
public void AnotherMethod(int value) {
// good discipline would be:
SetTemperature(f(value));
// but it's not strictly required so this is also allowed
temperature = f(value);
}
}If a civil engineer had access to concrete but he chose to build with mashed potato instead he would be considered insane. Yet programmers with access to Ada choose JavaScript and it's considered perfectly normal.
The so-called "Ada comb" structure that is used for packages, subprograms, and even declare blocks makes it easy to find what you are looking for because it makes the source code more regular.
The "Ada comb" is formed by the shape of the source code with the subprogram header / declare, begin, exception, and end forming the "teeth" of the comb and the rest of the source code indented between the "teeth":
function Square_Root (Arg : Float) return Float is
-- local variables declared here
begin -- program work here
exception -- exception handling here
end Square_Root;ShortName is New ReallyLongAndAwkardName;
Ah, well.
> Unfortunately, the designers did not bring over > ShortName is New ReallyLongAndAwkardName; Do you mean renames? Package Text renames Ada.Strings.Fixed;
IIRC, RENAME is in the keywords list for PL/SQL.
- type BOOLEAN is (FALSE, TRUE);
- type VARCHAR2 is NEW CHAR_BASE;
- generic collections
- varargs
Unfortunately, they are only available for the PL/SQL team itself."Declare" blocks are a PITA. I don't mean the declaration part in the subprogram example you quoted (that's fine), but having to use explicit "declare" blocks to create new variables in the scope of a loop or a conditional. You can't just declare variables after the introducing keyword (then, loop, ...), possibly between it and a "begin" that would be fused to be come a part of the loop/conditional syntax. You have to use a separate "declare" block, with its "begin" and its own "end;", and then you still have to have the "end loop;"/"end if;" of the loop/conditional itself.
Illustration.
A normal conditional:
---------
if A = 5 then
B := 7;
C := A + 1;
end if;---------
What you'd think you could at least do (it would already be a bit heavy, but by Ada standard verbosity it would be a good and smooth fit):
---------
if A = 5 then
declare
D : natural;
begin B := 7;
D := 1;
C := A + D;
end if;---------
What you actually need to do:
---------
if A = 5 then
declare
D : natural;
begin
B := 7;
D := 1;
C := A + D;
end;
end if;---------
So you waste one indentation level more each time you add one such declare block. Talking about combs, this get hairy quickly even when you have simply a few nested loops/conditionals.
Also, the "end;" of the "declare" block is not an "end declare;" or "end block;" like you have "end if;" and "end loop;". So that make it a bit harder again to know where you are when you are closing your blocks/loops/conditional.
Then you can add a label, but that make it even heavier (and good luck finding a clever name for the block label each time). Not sure there exists a good solution to place the (opening) label. And the closing label comes at the same place a closing keyword, which may be considered a bit confusing.
---------
if A = 5 then
myblock:
declare
D : natural;
begin
B := 7;
D := 1;
C := A + D;
end myblock;
end if;---------
or
---------
if A = 5 then
myblock: declare
D : natural;
begin
B := 7;
D := 1;
C := A + D;
end myblock;
end if;---------
or
---------
if A = 5 then
myblock:
declare
D : natural;
begin
B := 7;
D := 1;
C := A + D;
end myblock;
end if;---------
The last one, which wastes not only 1, but 2 indentations levels(!), and even worse, creates an indentation gap in the end, happens to be the recommended style...
In Ada as in almost every language you are well advised to have small functions with limited scope and then build on top of them. This would mean if you would declare your variables as normal part of a function the variables would have the lifespan of the function, and the function being small and to the point would also not be that long and you can avoid all the additional effort with `declare`.
I am not saying that there is no use for `declare` but it’s not needed that often or a big hassle in my view.
Ada is a bit more verbose than other languages and has one of the main aims to be readable on the basis that you write it once and read it many times.
Ada was sabotaged early on because it was 'mandated' by the DOD for new programs. That meant that all the usual suspects, like Lockheed, GD , TI (I don't remember exactly which ones) came up with Ada compilers and runtimes that cost on the order of $10K per seat. The typical military contractor ripoff. So it was impossible for individuals or small companies to use Ada on their own dime. It was only feasible if the cost was rolled into a larger (bloated) defense contract. So it couldn't get a following. Of course much later on free versions became available but it was too late.
That said, Ada was absolutely no fun to program with. It was awkward and verbose. I hated it from the get go, compared to the alternatives. If it is so super why is rarely used.
I always wondered why I've never seen it used outside government work. Your explanation seems incredibly obvious after reading it.
I have done a lot of embedded programming in C over the years and while the reality is most embedded programmers know C best, I am starting to think we would be better served to try a new language with better features such as Ada or Rust. C++ is nice as well, but has it's own set of problems when used for embedded programming.
Ada also seems to have a weirdly negative rep in many circles it seems. I recall looking around for an Ada compiler for a moderately popular platform and came across and old thread where people didn’t give any options but instead just joking about how the OP was interested in such a terrible language. Maybe it’s the Pascal/Algol type syntax?
I think the attitude is mostly a historical artifact and momentum. The language was soundly rejected in the 80s and 90s by many people in favor of C, for numerous reasons. Some valid, others invalid. It's carried a reputation since then (much like the author's take on Fortran, many quick takes here on PHP and Perl) that reduces the potential for adoption today even though the language is actually rather pleasant (IMHO, and also now, may not have been 20 years ago) to work with.
But other languages are better advocated for, and have (mostly) better tooling these days. Alire is helping on the package manager front, but it's still pretty new. When people think "safety" in software they tend to jump straight to Rust and Haskell, mentally, even though Ada also fits within that conceptual space.
Jack Ganssle has advocated for Ada for a long time. http://www.ganssle.com/rants/ada.htm
You can say this is still 20 years ago, but you have to count from the other side. If you have two solutions to a problem the earlier one wins more (except if the later ones is order of magnitude better on a metric that counts). Yes, rust is also late to the party, but support of Mozilla is a big advantage.
Skimming my local jobs list(SE England) I see new listings from both Airbus and BAE Systems looking for Ada developers in the past week.
However, you'd be correct to say there are an awful lot of multi-decade projects in both the companies I mentioned(and the aerospace/defence in general). For example, last year my main project was work stemming from a design that began in 1997.
Oh, my github must be ancient then!
> Ada also seems to have a weirdly negative rep in many circles it seems. I recall looking around for an Ada compiler for a moderately popular platform and came across and old thread where people didn’t give any options but instead just joking about how the OP was interested in such a terrible language. Maybe it’s the Pascal/Algol type syntax?
That stems from the hatred from the people working at the DoD at the time who'd never even seen the language.
Off topic, but have you ever heard of CHILL? It’s a language from the ITU designed for telephone switches (like Erlang) but is supposedly very similar to older Ada standards.
https://psc.informatik.uni-jena.de/publ/1999-T-REC-Z.200-199...
https://www.itu.int/rec/dologin_pub.asp?lang=e&id=T-REC-Z.20...
I can see why it's no longer used, with all those modes! But another language with ranges. Also has module inheritance.
Yes, two different locations for freely getting the spec.
> I can see why it's no longer used, with all those modes! But another language with ranges. Also has module inheritance.
IIRC, "mode" used to mean (in some cases, historically, in CS) the equivalent of what we now call a "type" — I seem to recall Algol using the terminology, but may be misremembering.
Thanks. Yes, you can tell because it doesn't show as a fork on my gh and it should show on the wiki who wrote it.
> Off topic, but have you ever heard of CHILL? It’s a language from the ITU designed for telephone switches (like Erlang) but is supposedly very similar to older Ada standards.
Yeah, heard of it, that is all though.
"Admittedly, I had pictured Ada’s syntax resembling the uncompromising verbosity and rigid construction of COBOL, or perhaps the Lovecraftian hieroglyphics of Fortran’s various eldritch incarnations."
"Lovecraftian hieroglyphics of Fortran" <-- the author never programmed in Perl, I presume.
The author might want to stay away from MUMPS for sanity's sake.
Or in Fortran, it's a relatively verbose language from what I remember of it (that said, I learned Fortran 90/95, so maybe the older variants were worse)
OK, this has no relation to the actual content of the article, but I have to point out that that is not what the phrase "the writing is on the wall" means.
"Mene: God has numbered the days of your reign and brought it to an end."
"Tekel: You have been weighed on the scales and found wanting."
"Peres: Your kingdom is divided and given to the Medes and Persians.”
So you're right, it doesn't connote that something will endure, but that something will end.
As a professional embedded developer who uses bitfields to access registers every day, this doesn't really make a practical difference. On any bare-metal or embedded project you will rely on the behaviour of your compiler, and portability is largely irrelevant if you're accessing memory-mapped registers. Probably, the manufacturer has already provided register maps using bitfields anyway.
(I always expand that to the American Dental Association)
Many of those rewrites are benign, even good, many others are stupid and infuriating.
Unfortunately there is no way to have yourself declared to be a "well-known submitter with a history of not editorializing or outrage-optimizing submission titles" and get this thing switched off.
In this case there have been so many discussions and submissions about the American with Disabilities Act that it sounds plausible to be such an automatic rewrite.
Also, modern Fortran is not that bad of a language much like the modern parts of C++.
The community is becoming more open (AdaCore has been very helpful here), I think, but you still have a fair amount of vocal gatekeepers that are going to continue to keep people out (deliberately or not).
I guess there is more to like then, since the keywords are case insensitive. The OP doesn’t even use all caps.
> Reserved words differing only in the use of corresponding upper and lower case letters are considered as the same (see 2.3).
Interestingly, the manual itself doesn't even use upper case:
> For readability of this manual, the reserved words appear in lower case boldface.
So even the Ada standard recognizes the upper case is less readable and undesired.
Case insensitivity also applies to identifiers (variable names, type names etc.) (sec 2.3) and that is a definitely an archaic feature, but the GNAT compiler has the helpful -gnatya style check which enforces the Pascal_Case style: https://gcc.gnu.org/onlinedocs/gcc-4.6.4/gnat_ugn_unw/Style-...
---
Another common misconception is that function/procedure names (designators) must appear after the `end`. That is optional:
> If a designator appears at the end of a subprogram body, it must repeat the designator of the subprogram specification. http://archive.adaic.com/standards/83lrm/html/lrm-06-03.html...
Yeah, it's horrible to see and worse to type. I took Oberon-2's grammar and changed it to a more Ada like one for a project I'm working on.
This is one of the best lines I've read in awhile, gave me a good chuckle. Thanks for that.
Any enthusiasm for this language is inevitably quashed upon encountering the $$,$$$ per-seat price of the compilers for the absolute bare-bones x86 version. It's more if you want to target non x86. I pester AdaCore for info every few years and while they've dropped a little bit, they're still out of reach for companies not in the Fortune 500. I'd love to use SPARK, but I don't see that happening any time soon.
And I don’t get what you mean with “pestering for info”. I’ve once discussed buying their compiler and I was by no means in a F500 (decided against buying btw).
I take it the Author hasn't seen any APL code.
You can decide for yourself if this is an endorsement or a warning. Or both.
*Exceptions obviously exist for very different niches or paradigms.
I think the best I've actually seen in this regard is actually Haxe, with some decent macros for automatically getting pass-through functions. It doesn't even make you list them all out explicitly (as I've seen others do), IIRC.
Yes, and that's why I am suggesting to wait ~a month until it is deployed as per the road map before taking a hard stance.