Sunday, 24 January 2016

Ada's influence on my programming style


"A language that doesn't affect the way you think about programming, is not worth knowing."

This is one of Alan Perlis' Epigrams on Programming. On the surface, Ada doesn't seem like the sort of language that might affect your thinking in any sort of positive way. Structurally it is like C, but with more constraints on what you can do, and with quite verbose syntax. Where is the thoughtfulness in a language that's like C, but is more difficult to use?

The answer is that the difficulties of writing Ada are mostly avoidable through better programming practice. This language punishes you for writing "bad" code, by requiring more elaborate approaches and more verbose syntax: basically, forcing more typing.

There are things that can be done easily in C, which are also possible in Ada, but require a lot more typing. Conversions between types are an obvious example. In C, type conversion is often implicit, perhaps provoking a warning if the conversion involves a dangerous assumption (e.g. "pointers are 32 bit words"). In Ada, type conversion is explicit. You spend more time stating exactly what should happen.

The whole point is, I think, to make you consider: "Am I doing this wrong?" Maybe you can avoid type conversions by creating more types, or by explicitly defining what certain operators do when applied to particular combinations of types. This will be better, because you're being clear about what you want. You're providing more information to the compiler, which can be checked for consistency, turning potential bugs into compile-time errors. But it's outside of the C mindset, where type names tend to be mere aliases, implicitly converted to/from "int" as necessary. As an Ada programmer I make heavy use of arrays and abstract data types, because they're easier to use than pointers. In C/C++ I would use pointers.

This aspect of the design of Ada goes back to the beginnings of the language in the early 1980s, and is a clearly intentional attempt to improve on C. The issues with C were well-known back then! And Ada is a sort of compromise. It doesn't stop you writing C-like code. It just makes it more difficult to write C-like code, so you'll think "how can I do this better?"

Another apparently thoughtful aspect of the design of Ada probably wasn't intentional. In 1983, object-oriented programming (OOP) was still a few years from mass popularity. Perhaps that's why the 1983 edition of Ada only supports it in the same way that C does: that is, it isn't a first-class feature of the language.

Most commonly, OOP is used to hide implementation details behind an interface, and associate a data type with some closely-related functionality. But OOP is not the only way to do that. A modular language - like Ada - provides an alternative. Implementation details can also be hidden inside a "package", which also associates data types and functionality. The main difference is that you can't extend the functionality by inheritance. There's no dynamic dispatching.

Later on (for Ada 2005), dynamic dispatching was added. Now you can do all of the same things that C++/Java can do, without needing to introduce your own "vtable" of function pointers. But the syntax is weird and verbose. I usually have to refer to the textbook in order to use it.

That forces me to consider: "Is OOP really what I want?" And the answer is very often "no". Usually all I really want is to encapsulate an implementation. I don't want a class hierarchy and inheritance. Supporting those things would not make the code better. It would make it less clear.

I think it is quite by accident that Ada ends up challenging OOP, providing an alternative that is simpler and therefore preferable in many cases.

My preferred design uses procedures to define the operations that are unique to this program, and packages to provide "library" functionality that is reusable between programs or shared between parts of the program. OOP is a last resort, used only when a library needs to be extensible in a way that is only conveniently arranged by inheritance.

Even something as "obviously OOP" as a database interface is much better expressed as a package. Classes only represent one type. That covers the database interface, but not associated types, such as identifiers for objects within the database. Packages, however, can contain any number of related types. So the database interface in the prototype tool I've been writing at Rapita has a different type for the identifier of each of the kinds of object it can deal with inside the database. I can't mix up the identifiers by mistake, because the program won't compile if I do.

There are many other examples I could mention here. I also like the Ada mechanism for supporting threads and locks, and the mechanism for templates ("generics") which is not as powerful as C++'s template system, but nevertheless keeps the parts that actually matter.

Ada has had a positive influence on my programming style. When used badly, it's a hassle to write, because you're forced to type more. When used well, the result is better code.