My background is scientific R&D with a few interludes working as a software engineer. Lots of computer vision, machine learning, statistical analysis, data modeling, and system design. Functional programming and denotational thinking has probably been the most useful tool for me over the years.
Last year I was handed a greenfield R&D project to develop a firm real-time system for processing data streams from a variety of different sensors. Low latency, mathematical integrity, and correctness were very important, as the outputs of the system (especially the precise times of detected phenomena) carry critical scientific implications.
Reliability was very important. During an experiment, the system has only one chance to get it right. Having to re-run an experiment because the software didn’t work or crashed would be wasteful in terms of time and money, and would make the system unfit for use.
The hardware for the system had to be physically small and able to run on a sensible amount of power due to installation environment constraints. The system had to be capable of using sensor APIs and numerical analysis libraries written in C/C++. Timely and safe concurrency was critical.
Evolvability and maintainability was really important. In an R&D project like this, it’s essential to be able to incrementally construct a system and safely refactor it to accommodate large changes as the requirements evolve.
I decided to give Ada a try for this project. What a great decision that was! Ada and its excellent tools check all of the boxes for the project requirements. Using Ada made it possible for me to design and develop a working system with all of the high risk functionality implemented in a matter of just a few months. Way ahead of schedule, as the Ada language, runtime, and tooling circumnavigated so many of the usual pitfalls. Much of the usual defensive programming and difficult reasoning about system invariants, concurrency, and timeliness disappeared completely.
Highlights of the great things about Ada:
- C/C++ interop: Amazing. Smoothest experience I’ve ever had. Wow!
- Documentation: Excellent. Thank you AdaCore et al., it’s great. Really good, professionally written books are available. The forum is a great source of information. Abundant open source examples to learn best practices.
- Tasking: Best in class. Absolutely amazing work. Restricted profiles (Ravenscar/Jorvik) is such a great idea. Ada tasking has eliminated almost all of the potential pitfalls of my project by removing all of the complexity of highly concurrent time constrained computing.
- Type system: Fantastic. Things break down a bit when we have to think about when a particular constraint or type predicate is applied because it cannot be determined statically. Unpleasant surprises at runtime are icky. Tools like SPARK help here, effectively endowing Ada with liquid types and much more.
- Expressiveness: Very good. It is phenomenal how easy it is to write extremely low level code, while still expressing fairly abstract concepts. The abstraction bit has a ceiling, as usual - I can say some things in Haskell that I can’t say in Ada, but the converse is true as well. The gamut of expressiveness is incredible.
- Tooling: Great, and continually improving. I was so grateful to be able to use Alire to build and manage my projects, and not have to dive into complicated polyglot tooling. It just works. This alone saved a huge amount of time and effort.
- Safety: Excellent. Memory management, very good static analysis, and the whole language is designed to ensure that a system is free of many classes of common errors. I sleep well knowing that my system will work as expected.
As someone who takes full advantage of powerful type systems such as those in Haskell, Scala, Lean, etc., Ada has been great. The basics are very easy to learn, and the remaining learning curve is manageable.
There are a small number of pain points from my perspective. Not show stoppers by any means. Some aspect of these are probably because Ada is new to me, and I am trying to bend it into something that it is not:
- Generics: Coming from Haskell/Scala/Lean, having to instantiate a generic package for every combination of concrete types in Ada is a big downer and limits their expressive power and potential elegance. Maybe this will help, but probably won’t go all the way to the ergonomic satisfaction seen in Haskell etc. There are pluses and minuses to Ada’s design of generics, and a long history of real world use cases probably supports those design decisions.
- Source Code Files: In an R&D workflow, it’s very useful to quickly spike out a new idea in a system. Having to bend over backwards to jointly cultivate “.ads” and “.adb” files, and insert stubs to keep the compiler happy slows things down. That said, I understand and respect why things are the way they are. Maybe I need a different workflow using
gnatstub/gnatchop.
- Strings: Would be nice to have out-of-the-box modern string handling.
- JSON: Again, out-of-the-box JSON supporting Unicode with good ergonomics would be nice to have.
- Rapid Prototyping Ergonomics: When spiking something out, I don’t want to have to think too much about concerns such as memory management or whether my files/packages/etc are all ready to be immortalized. Unfortunately, the rigmarole for things like this get in the way of expressing the what that I am trying to do (denotational), and forces me to divert time and brain power into managing the how (operational). Based on massive development time savings elsewhere, this is an ok tradeoff.
- SPARK: Fantastic auto-active verification, but you have to know how to play the game. To play the game, there is a certain amount of metis and tacit knowledge needed. You can’t just read books and fiddle - you need to know what the proof automation is trying to do, how it works under the hood at some level, what it may choke on, whether your config files need to be twiddled, etc. Auto-active verification always has limitations, so easily dropping into an interactive proof mode (Rocq) is a very nice escape hatch, though I haven’t used it yet. If I had a magic wand, I’d make a tool for SPARK to start any compatible interactive theorem prover (ITP) to discharge stubborn VCs, to directly see the proof context, and use the ITP’s ecosystem to its fullest extent to get the job done. Maybe something like Velvet could serve as an example of what is possible in a general sense.
- Non standard terminology: Some of the terminology used to describe the language context seems non-standard, possibly due to historical happenstance. Might be helpful to have a Rosetta Stone somewhere that shows the Ada term for a thing, and the term that’s considered more modern. I should come up with concrete examples to support this claim. For example, what’s a “nonlimited definite type?” I think some of the Ada OOP terminology may be considered idiosyncratic by some people, but I don’t use many OOP features right now.
It is incredible to get an opportunity to use this language and ecosystem. I will certainly use it again, and I actively look for opportunities to apply it.