Yes, yes, I know... I am kind of an oddball for programming in Ada. When the matter comes out in conversations I am usually asked to defend this choice of mine. For future reference (and maybe to help others knowing this nice language) I decided to write few articles about what I like in Ada.
History and other stuff
Before diving in the details, let me spend a couple of words about the language. Many never heard about Ada and many of those who did have some prejudices about it.
The language Ada is named after Ada Lovelace daughter of Lord Byron (yes, that Byron) and considered the first programmer in history.
Incidentally, did you notice that I write "Ada" and not "ADA"? This is because it is a name of a person, not an acronym. Use "ADA" on an forum/newsgroup and you will be corrected in no time...
Ada was developed in the '80s following an idea of the Department of Defence. (I am not replicating here the interesting history that is available elsewhere.) Since then it was updated every approximately ten years giving rise to several releases, informally known with the year number (Ada 83, Ada 95, Ada 2005 and Ada 2012, next release will probably be Ada 2020). Ada is a language very alive and modern, with several feature not easily found elsewhere: contracts, type invariants, formal checking, private packages, distributed programming, native multitasking and so on...
Ada is quite flexible: it is used to write million-line code in avionics, but also to control small devices like the STM32. See, for example, this segway implemented with a Lego mindstorm controlled by a multitasking code (with a 16 bit processor and 64K of RAM!) presented at FOSDEM.
Why do you love Ada?
A simple answer? Reduced debugging time: my personal experience is that an Ada code will require maybe 1/10 of debugging time if compared with a C code of similar complexity. The reason is that Ada compiler is much "stricter" than C, so that when the program compiles, it has an internal coherence that prevents the existence of many silly bugs (and most bugs are just silly!) that would have survived in a C code (functions with no return, a missing break
/case
in a switch
, dangling pointers, buffer overflows...). The remaining bugs are easily caught by the many bug traps (I love this term) that the compiler adds to your code. Programming in Ada is like doing pair programming, but with the compiler playing the role of the observer.
Please note that I am not claiming that by using Ada your code will be automagically readable, maintainable and bug free. You can write bad code in Ada, you just need to make an effort... :-)
Seriously, it is often said that the quality of the code depends mainly from the skill of the programmer, rather than from the type of tools. Nevertheless, using the right tool will help you to achieve the same result with less effort.
OK, OK, I understand... But can you make me an example?
Sure, I am here for this. Maybe the simplest characteristic of Ada that helps you in writing robust software is the type system, especially its strong typing nature. Let me explain with an example: if in C you write
typedef int serial_number;
typedef int port;
you can assign with no problem a variable of type port
to a variable of type serial_port
since both are integers; but if you do in Ada
type Serial_Number is new Integer;
type Port is new Integer;
you cannot assign a variable of type Port
to a variable of type Serial_Number
although both "under the hood" are implemented as integers. This is because they actually represents two different things despite being implemented in the same way at the lowest level. Well, it makes sense, doesn't it? Why should you want to use a TCP port as a serial number? Most probably, if you try to do such a thing, there is an error somewhere.
What if you actually need to use a port as a serial number? No problem, you can convert it with
Serial := Serial_Number(Client_Port);
. Note that this conversion has no cost since both are integers; it is just a way to tell to the compiler "Listen, this is not an error, I know what I want, please bear with me and assign this value."Note the use of "camel case with underscores" for names that are not keywords. This style is quite common in modern Ada code. It is not mandatory, of course, but personally, I find it quite readable. Please also note that Ada is case-insensitive, so that you could write, e.g.,
serial_number
. Oh, yes, and the use of ":=" for assignment.
Actually, the code above is not the best choice. How do you know that an Integer
will be large enough to keep a serial number or a port? Well, on current Intel processor (whit 32-bit integers), that is quite reasonable, but what if you have a small micro-controller with 16 bit integers? Well, maybe the best thing is to let the compiler to decide by writing
type Serial_Number is range 0 .. 999_999;
type Port is range 0 .. 2**16-1;
Note the use of '_' as separator in numbers. A simple thing, but really convenient...
Note how we do not say to the compiler which low-level implementation to use, but which characteristics we need and let the compiler to decide how to handle them. On a microcontroller with 16-bit int
s maybe Serial_Number
will be implemented as a long int
, while Port
will be an unsigned int
. But who really cares? Let the compiler take care of this boring stuff...
What if you need
Serial_Number
to have a specific size, say 24 bits (because, for example, you need to write it in a packet)? Just write
type Serial_Number is range 0 .. 999_999 with Size => 24;
I do not want to dig deeper in the Ada type system, but I cannot resist telling you about two types that are not commonly found elsewhere. If you write
type Volt is delta 0.125 range 0.0 .. 255.0;
the variables of typeVolt
will hold a fixed point real ranging in the interval 0 V..255 V with step 0.125 V. Fixed point numbers are usually implemented as integers and are used, for example, in some DSP applications running on small processors without floating point maths. Another uncommon type is the decimal fixed point type defined with something liketype Money is delta 0.01 digits 15;
. I'll let you discover about them. See also the corresponding Reference Manual page.
An example: cleaning user input
Let us make a toy (but not so-toy) example of exploitation of the strict typing of Ada.
Someone could object that there are other ways to handle the problem considered here. Yes, I know, but I just need a simple example to show what you can do with strict typing. I am not claiming that is the only (or best) solution (although, I think it is quite good).
It is well known that in any application using user input care must be taken in using the user input since it could open security holes. The following xkcd comic is a classic
Of course, we can pay all the attention we want to not use user input before sanitizing it, but if the application is very large and everything is a String
(or char*
), something can slip in the cracks... Type strictness can help us here. We just need to define a new type Dirty_String
and have all the user input function return a Dirty_String
rather than a string (this is easier to check). The only way to transform a Dirty_String
in a normal String
will be via a special Sanitize
function.
Let's dig into details. We will define the following package specs
package Dirty_Strings is
type Dirty_String(<>) is private;
function Sanitize(X : Dirty_String) return String;
function Taint(X : String) return Dirty_String;
private
type Dirty_String is new String;
function Taint(X : String) return Dirty_String
is (Dirty_String(X));
end Dirty_Strings;
In Ada a package is divided into two parts: its specs that specifies the "resources" exported by the package and its body with the actual implementation. The specs are further divided into a public part (visible to the rest of the code) and an optional private part.
This package define a type Dirty_String
. In the public part (before the private
) the type is defined as private
, that is, none of your business. Moreover, the package exports two function that can be used to convert from Dirty_String
to normal String
and vice-versa.
However, in the private part we see that a Dirty_String
is just... a String
. Putting its definition in the private part prevents short-cut conversions from Dirty_String
to String
and forces the programmer to go through the function Sanitize
that, we guess, will do stuff like quoting special characters. Instead, the conversion of a normal String
to a Dirty_String
is just a type conversion since there is no need to change it. This allows us to define it as a expression function (see also the RM) that, most probably, will be "inlined" by the compiler.
Run-time constraints
Let me conclude with a feature of Ada 2012 that I find quit cute (and useful). Few years ago, I wrote a small package to write Matlab files from Ada. One day I discovered that the package was writing files that could not be read from Matlab. The reason was that the names of the variables in the Matlab file were not valid Matlab names. After correcting the bug, I decided to add a new bug trap to the code. I defined the type to be used for variable names as
type Matlab_Name is new
String
with Dynamic_Predicate =>
(for all I in Matlab_Name'Range =>
Is_Alphanumeric (Matlab_Name (I)) or Matlab_Name (I) = '_')
and
Is_Letter (Matlab_Name (Matlab_Name'First));
If you have a bit of programming experience, you should be able to understand the code above, even if you do not know Ada. As you can see Matlab_Name
is a String
(but you cannot mix it with other strings, e.g., a filename!), but it also must satisfy a Dynamic_Predicate
(a condition that is checked at run-time, if you ask the compiler to do so). The condition can be split in two, the first part
(for all I in Matlab_Name'Range =>
Is_Alphanumeric (Matlab_Name (I)) or Matlab_Name (I) = '_')
requires that every character in the name must be a letter, a digit or an underscore, while the second part
Is_Letter (Matlab_Name (Matlab_Name'First));
requires that the first character must be a letter. If checks are enabled and at some time your code generates a bad name, an exception will be raised precisely in the point were the bug happened. (This pinpointing of bugs helps a lot in debugging...)
What about efficiency? Yes, I can hear you, efficiency lovers. The idea of having checks done at run-time seems to go against the idea of efficiency. Well, it is true, run-time checks costs in term of efficiency, but in my experience you do not even notice it. Unless you are on a very strict time budget (or unless you have very complex checks) it is usually more convenient to keep them on to catch possible bugs. You should take the decision of turning checks off only after discovering that you cannot afford them and you should turn off only those that are in most computationally intensive parts (possibly after thoroughly testing).
An alternative approach is to code in SPARK (a subset of Ada, nothing to do with Apache) in order to check your code formally. I'll let you discover the joy of SPARK... :-)
Top comments (19)
Thanks for writing this article, Riccardo.
I have so many questions:
What kinds of projects do you write in Ada? Do you always use Ada or is it a special tool for specialized requirements?
Why should someone reading dev.to learn Ada? Most of us are doing some form of webdev. Does Ada have a place on the web?
Is there a good book for experienced devs who want to learn a little Ada?
Where does Ada excel? It seems to be used mostly for large systems that need to work correctly (avionics, medical equipment, air traffic control, etc.) but are there less publicized uses where it's really good? For example, is anyone converting security sensitive C/C++ in operating systems into Ada?
Do you write Ada in an IDE? If so, which one? How about your compiler? I hear the free tools are not so great and the commercial tools are expensive. Thoughts?
I understand if you can't answer all these questions or if you are planning to answer them in future posts.
Cheers.
While you wait for Riccardo to chime in, I think I can answer some of your questions.
Ada is general-purpose but is not that comfortable for quick, dirty code. I find it excels when you have anything that 1) needs tasking (which is built-in and very high-level, but still efficient, mapped to native threads usually), or 2) is medium-size (let's say, once you need to separate sources into modules). I personally use it for anything that I would do otherwise in C++ instead of python/bash; to me is a safer C++ replacement.
Why learn it: since it mostly forces you to do things right, and write proper specifications (headers are mandatory, and not pre-processed but compiled) when coding in other languages you tend to write better, more structured code, and realize issues that Ada detects and fly over other compilers. You start to miss ranged types everywhere.
There are libraries for the web (check AWS, not the Amazon one) but I would consider it overkill and perhaps cumbersome; although Ada can be interfaced easily with C it requires writing bindings and there are not many libraries, so it's tedious and not easy for the novice. Others may disagree here.
Ada excels at early bug detection. The author's point on debug time is spot-on in my experience. Firing the debugger is the exception rather than a usual step. Also at tasking (already said that). Also, if you're formally-minded, the SPARK proving tools might be your cup of tea. No experience there on my part, but I've seen interesting projects (check Tokeneer/Crazyflie+Spark). In general, my feeling is that of a safer C++, so you might say it's good for the same things (developments that need efficiency, modern features), with a caveat: there are not so many libraries, so I would consider it specially when the bulk of the code is going to be newly written (in Ada).
For a "modern" IDE I'd recommend Gnat GPS (it's free with the GPL edition of the compiler). I say "modern" because it's miles behind e.g. CLion or IntelliJ (also much much lighter), but it's serviceable. A lightweight alternative would be multi-language Geany. Vim/Emacs, if that's your way. The compiler uses gcc as backed so tools can understand the generated object code.
About price, it's true that there are two extremes: the GPL version of Gnat is free for GPL projects, but is (I hear) very expensive for closed developments. However, the FSF-maintained version is very well curated in Debian, although it's a bit behind the privately developed/supported Gnat Pro (not by much).
Hope this helps! Cheers.
Thanks for the reply. Those are really great answers.
I've read a bit about Ada and read a whole book on Spark but I've never done a project in either. Do you (or anyone) have an idea how fast you can write a project in Ada vs C/C++?
For example, Let's say I wanted to write a clone of TrueCrypt/VeraCrypt for Debian without looking at the source. Let's further assume that I'm equally proficient in Ada and C/C++. I'm trying to get an idea of how much quality do I need to require from the code to justify the effort of writing in Ada. Does that make sense?
I imagine could quickly pound out a buggy version in C/C++. But the fastest version I could make in Ada would take longer and have fewer bugs than the C/C++ version. And then all the way at the other extreme of quality, I imagine that if I wanted 0.1 defects per KLOC, I could get there faster in Ada than I could in C/C++ (if I could ever get there at all). And at some point, as we move to fewer and fewer defects, those lines cross. Do you (or anyone) have any idea where that might be?
In other words, how much do I need to care about quality to make it worth using Ada? Or is that not true at all? Maybe once you know Ada well, you can write as fast or faster than in C/C++ and lower defect rates are a happy side effect of using the language?
Also, does the equation change if we trade C/C++ with something safer like Java?
Let's see I get the gist of your question.
For an equally proficient Ada/C++ programmer I'd say Ada development speed is the same if not faster. Ada is a bit more verbose, but with a completion editor that's mostly moot, and in any case I feel it would be offset by the gains in debugging time. Ada is not foolproof though, and if you carelessly start to play with pointers you will lose sleep over the same kind of memory corruption errors (I must check Rust for precisely that reason). Dynamic memory management is similar to C++, but using standard containers you can avoid most problems (same as in C++, but still Ada containers have extra checks).
Related: Ada use of the stack is far superior (with its unconstrained/indefinite types), so you can avoid pointers in much more places that would require them in C++. I'd say this is the most jarring part for programmers coming from C/C++, since it has not really an equivalent there (to my knowledge).
If Java enters the picture: you lose somewhat on the efficiency department, but I guess unless doing real-time it won't be a problem. Also, you trade memory corruption for memory leaks in my experience (but I'm not as experienced in Java as in Ada). However, my personal dilemma would mostly be Ada vs Java; I avoid C/C++ if I can (there's too much of it anyway where I cannot choose).
To summarize, if you're equally proficient in Ada/C++/Java:
If a compiled language is a must: Ada unless you need lots of 3rd party libraries that would take a long time to bind/replicate in Ada; in that case C++ with most warnings on. (Gnat has an automatic C binding generator but in my (old) experience it was not yet 100% there).
If interpreted is OK: Java if 3rd party libs needed, Ada if tasking is involved, otherwise down to personal preference (Ada in my case).
Also, another factor against Ada is that the community is (much) smaller, if you want to attract collaborators. On the other hand, there is people very devoted to Ada (but I'd say you lose chances anyway, if only because of numbers).
So, again: Ada if self-contained development, Java if I need many libs, C/C++ if the project is already using it. In your particular example of Truecrypt... if for fun, Ada, but checking the crypto/disk needs first.
Please do, it would be awesome if someone could debunk them and prove Ada to be superior after all their hot air :)
As for dev speed, the only place where you will slow down is designing thick bindings to C and C++ libs. Tedious isn't the word.
Java is pretty bad, IMO, spaghetti code with all the patterns, really hard to follow.
Hehe. Do you think though there is no substance there? From my superficial reads they get rid of a whole class of errors with the "ownership" idea. Although I don't really grasp the implications yet.
Certainly the lack of specifications are a step back from Ada. Interfaces mitigate it to some extent, although then one may get lost in the hierarchies; however the new shorthand notation for lambdas and reference functions for callbacks and single-method singletons goes a looong way in terms of local clarity and boilerplate reduction. I couldn't go back to Java 7. But then, that's also the merit of the tools, besides the language itself.
Anyway, I would consider Java instead of Ada mostly for reasons of libraries/community, rather than the language itself.
I have no idea, I've only really checked on the whole syntax of the language and it's not for me. But they do have this arrogance going on basically saying that everything should be rewritten in Rust and whenever you mention something they say "Rust has that" and then you find that it's in the library, not the language.
This is why we need more people working on libs for Ada, so people pick Ada rather than something else.
As for the tooling/libs on Java, yeah, they have loads, but Sun paid universities and company's millions for them to use the language. Plus, forcing everything into a class is coming back to bite them, this is why I think so many people are jumping on the FP bandwagon, there'll come a time when people think forcing everything into a function is bad.
Regarding the lack of bindings for the Web, look at the excellent project Swagger Ada: blog.vacs.fr/vacs/blogs/post.html?...
As Stéphane Carrez ends his post: "The APIs.guru lists more than 550 API descriptions from various providers such as Amazon, Google, Microsoft and many other online services. They are now available to the Ada community!"
Excellent, thanks! Also, wasn't there something similar to generate SOAP bindings in AWS? Here, I found it: docs.adacore.com/aws-docs/aws/usin...
I did not know of this binding generator, although it's the work of colleagues... thanks for pointing that out!
To keep you out of the debugger more.
You can use Ada for webstuff, just like people have used C and C++ for it. For example:
github.com/stcarrez
github.com/faelys/markup-ada
github.com/faelys/lithium3 - CMS
https://www.youtube.com/watch?v=xxbAiuZydPE&t=14s < Ada on Rails - not open to the public though :/
adaic.org/learn/materials/ - use the Distilled book and grab the extras for 2005 and 2012 from the reference manuals.
Anywhere.
Ada scales well. I've done a lot of small applications without any trouble. Embedded can be small or large, again, it scales well. Embedded is just one area where Ada can be used.
Possibly.
You can use a text editor or an IDE, although the quality of IDE's for Ada vary. The commercial Ada tools have always been expensive, but GNAT from the FSF under GCC is free and you can release stuff closed source commercial if you like.
Thank you for the book recommendation.
Thanks for writing this article!
A comment to the small device part of your article:
About a year ago I 3D-printed a nice Lighthouse (designed by Estlin Haiss) and decided to equip it with rotating light.
The rotating light as can be seen on this short clip is delivered by 14 LEDs controlled by an attiny4313 (AVR) with all code written in Ada (AVR-Ada).
It has no tasking support, but are using Interrupts. The LEDs are modulated with software-implemented PWM, and the code was designed so it could be tested on my PC. Code has not been optimized for neither speed or size.
Resulting code size: 1190 bytes.
Resulting data size: 220 bytes.
I think it is safe to say that Ada can go small as well :-)
My first job from university was (largely) working in SPARK. Nowadays, I'm mostly coding in JS, and am terribly, terribly missing so many of the language features that saved me from those runtime bugs.
Heck, even just the abstraction of package specs and DFA would resolve a large portion of the problems I encounter, let alone RTE freedom proofs!
In college, I studied ADA because our instructor actually wrote the official ADA book and made us purchase it. It's a good language. And sure, you can connect ADA with C, but it's a lot of work that you can actually code in C directly.
Thank the author for sharing. I have gained a lot.
Wow, nice post! I LOVED to program in Ada in the university. Sadly, there is too few opportunities for that to me. I'm glad to remember that days 😊
Some comments may only be visible to logged-in visitors. Sign in to view all comments.