Matt's Blog

Consider Ada for LLM assisted programming

There's a lot of talk these days about how the landscape of programming languages is going to pan out in relation to LLM assisted development. It seems that the primary language everyone is talking about these days is Rust. There's certainly a good case to be made for using Rust, and it is a great language. But I wanted to point out that there's another language that's been around for a long time, which also seems to have have quite good standing for the future, and that is Ada.

I was prompted to write this after reading All Code Will Converge on Rust. The piece explores a bunch of topics which have been common patter in the Ada world for a long time, such as the cost of maintaining software over time versus the upfront cost of creating it. And the theory of mind behind a codebase. The piece mentions:

The reason for that is that in other languages, lots of little details and invariants aren’t explicitly encoded in a codebase but are only implicitly inherent to it, so teams have to rely on shared knowledge to work with the codebase.

Whilst I would say this is true in a sense, Rust code doesn't necessarily capture the "right" kind of invariants that are really important to long term development. In my mind, they are a bit more low-level (e.g. concerned with the mechanics of memory safety, etc.)

Features of Ada

More than anything else, the primary reason that I think Ada is well suited to working with LLMs is that it was specifically designed with readability in mind.

When they were writing the specification of Ada, it was noted that there was a strong need for a language that lasted better during the maintenance window of software than in the initial writing of it. What this means for us today is important because the "physical writing" aspect of software when working with LLMs has become much easier. And so the emphasis has been shifted onto maintenance and readability. And in a lot of cases already, people are using LLMs to write the entirety of their code. What this means is that the burden has completely shifted onto reading. To me, in this scenario, it stands to reason that you would want to use a language that had been specifically designed for readability.

I do find that the code written in Ada does read very naturally, almost like English. Another language where people say that is Python, but I feel like there is a qualitative difference between the two because Python's nature as a scripting language tends it towards more concise code, which can be less readable (in an external sense).

Another big win for readability is the pattern of having separate spec and body files. This is similar to C headers, but it feels different in a positive sense. It feels like in C, the purpose of a header is primarily for forward declaration, which is basically an implementation detail. In Ada, the purpose of a spec file is to define the interface of a module independently from its implementation. What this means is that if you want to get a high-level overview of your codebase, it's much quicker to go through and scan the spec files as opposed to having to go through implementations instead. In a similar way, this pattern manifests itself at the implementation level of code by having blocks declare variables up front outside of the body of a block. I always felt like this was a nice feature.

Building off the point on Python, I would also just say that the language design doesn't feel structurally capable of handling the readability aspects required when working with LLMs. And this is mostly related to static typing. That's not to say I think that dynamic languages are bad with LLMs. Just that Python is uniquely bad. For example, Clojure offers really great destructuring capabilities and data management through maps. None of us ever felt particularly aligned in Python.

On top of the readability aspect, there's also a bunch of other features which Ada provides, such as compilation to native code leading to high performance, powerful and expressive type system and decades of proven use in critical systems. So it has a good pedigree too.

Ada always felt particularly bad to me in the past for two reasons. The first was that its verbosity made it a chore to write. When you're working on personal projects and you sit down in the evening to do some chill coding, all of that typing genuinely did feel like a pain.

The other one is the ecosystem. This has developed a little bit in recent times because the Ada ecosystem has gained a package manager (Alire) and before that it was always a little ropey. But also with LLMs available, writing bindings for languages has become completely trivial. And so any library you come across that has a C ABI is trivial to port. Not to mention, you can just write the C ABI for a library yourself too. This makes it feel like there is a much larger array of opportunities available to Ada these days.

LLM Alignment of Ada

When we're talking about the type system and where it differs from Rust, its that it feels more expressive in the sense of alignment to the domain.

Instead of spending time on things like borrow checking constraints and lifetimes (which are useful, don't get me wrong), Ada allows you to express aspects of the software beyond the low level mechanical details. For example, it has built-in range and delta types, newtype, derived types and subtypes (and more). There's many features related to enums including using those as indices of arrays, built-in parallelism through tasking (though, as far as I know, lacks support for massive concurrency via M:N threads) and fine-grained memory management via storage pools. It provides excellent support for design by contract too.

I've been working on a personal project written in Ada for a few weeks, and one thing I've noticed is that the robustness of the software is excellent. This is just anecdotal, but the project is somewhere on the order of 100,000+ lines of code now. During the process of development, I experienced three crashes, and each one of those crashes was a pre or post condition exception. Meaning that the crash was actually accounted for in the design of the software, which is something that you'd want to catch during development time. In this sense, it feels like the LLMs understand very well how the Ada code works and what's going on. Reading pre and post conditions (and invariants) make for pretty good documentation on the constraints of your system too.

Workflow

Another anecdote is that I feel like I've seen a very small amount of rework on my project. So in this sense, Ada may have a negative reputation because people at first blush say "but it's very verbose, think of the token costs". The fact of the matter is, you do end up with a lot of code. In that way, if we go by the old maxim of "more code = more bugs", it's bad. But thinking in an economic perspective, when you consider the amount of code generated in a holistic sense (including churning on bugs), it may not actually be as expensive as you would think.

SPARK

There is also a lot of discussion at the moment surrounding the formal verification of software. People are talking a lot about lean, but I don't see anyone talking about Spark, which is interesting.

Spark is very well integrated into Ada itself. You can have individual modules of your codebase which have formal verification through it, whilst also being production runtime code. Furthermore, some of these formal verification checks that Spark uses, like pre- and post-conditions, also pass over into runtime. So the coalescing of these features is very well integrated and beneficial to the robustness of your software.

Ecosystem

I mentioned it a little bit before, but I'll also mention it again. The ability to bind to external libraries these days is completely game-changing. For this personal project I've been working on, I've got libraries in Rust, Swift, C++ and in C. Beforehand, I don't think I would have ever bothered to even consider some of these libraries. But now, even if it's not going to work, it's so little effort to attempt to create the binding that it doesn't make any sense not to try.

Additionally, through the package ecosystem of Alire, it's also trivial to just vendor them and make tweaks if required. Obviously this situation is developing over time because on the one hand it feels kind of toxic to modify a library and not send it upstream, but on the other hand, making these modifications is so easy that you don't want to overwhelm package maintainers. It's a little tricky in that sense.

Conclusion

All this to say that I think Ada's a pretty great language, and it's been a great language for a very long time. If you look at its list of features, quite a lot of programming language development in the last 20 or 30 years shows that Ada many of these features already in the '80s.

If you're looking for a very robust language with a great set of features, one that works very well with LLMs and an ecosystem that's more accessible than it has ever been, I think it's worth checking out Ada for your next project, though I will say that it's not well represented in web development.

#llms #programming languages