This post comes from an email conversation going on related to programming languages vs. libraries. The story goes that these days, the major productivity gains come not from new languages but from the existence of libraries that already do almost everything for you. That is unquestionable. These days people don’t choose programming languages as much as they choose libraries and frameworks that already do most of the work for them, and that happen to be written in some programming language or another. One can argue that these powerful libraries and frameworks stand in the shoulders of a lot of work in programming languages that became invisible by design. But that work has already been done, to the point that these powerful libraries are already out there.
So where does this leave programming languages? Are we done yet?
During the discussion, Mark Miller said something really interesting. He said: “Libraries *cannot* provide new inabilities.” What he meant was that certain concepts we include in programming are actually inabilities, or constraints, over what we can do in, say, assembly. Think, for example, automatic garbage collection that comes in the heels of disallowing programmers from allocating memory manually. For inabilities, libraries are fairly useless. Yes, we can have libraries that use additional constraints from those of the language, but those constraints can’t be enforced. If we want enforcement, constraints need to be included in the design of the language itself. (The whole concept of constraints is central in my book)
Indeed, the whole history of programming languages can be summarized as a yin-yang between abstraction affordances and constraints over what can be done in assembly. We tend to cover the affordances really well. But we don’t cover the constraints that well, because, well, once they are in place, they become invisible.
So, by means of focusing on constraints, here are a few things that were disallowed along the way, in some language or another:
- The ability to refer to registers (all ‘high-level’ languages)
- The ability to refer to memory addresses (all languages, except the C family)
- The ability to jump to arbitrary lines of the program (Java, Python, functional langs)
- The ability to define named functions (Smalltalk, Java, C#)
- The ability to search objects in the heap (Java, C#)
- The ability to define co-routines or even generators (C, Java)
- The ability to define variables whose values change over time (Haskell, ML)
- The ability to share state among concurrent threads (Erlang)
- The ability to bind the same name to different types of values (statically typed langs)
- … (name more in the comments)
Programming languages are usually presented in terms of what they do enable, rather than what they don’t let us do. But, really, language design is also an exercise in deciding what not to let people do — everything else that comes wrapped in a language can usually be done with libraries in modern languages that have good support for all sorts of composition. Which, if you think about it, it’s a funny business to be in: who wants to present themselves as being the bringer of constraints?
Constraints sound bad and authoritarian, but they are really necessary to tame complexity. The real question is which ones are beneficial, in general and for specific purposes.
For fun, here are a few possibilities of future constraints that I can already imagine:
- The ability to program, at all. Let the program learn from data.
OK, this took an unexpected turn. I’ll stop here 🙂
I hate leaving comments.
You should know that Mark Miller, in particular, worked on E, which disallows quite a bit of operational functionality in the name of object-capability security. In your style:
* The ability to create a reference to any object which was not already referenceable (E, Monte)
Disallowed: the ability to have more than one “owner” for an object. (Rust)
I’m the author of the article “Stop Designing Languages. Write Libraries Instead.” I’ve been one of your readers for a while now and it’s flattering to have you link to my essay. Your blog post entitled “Research in Programming Languages” is one that I have read many times throughout the course of my own Ph.D. while I was in the middle of designing Stanza.
In response to the very first assertion in your post, “But that work has already been done, to the point that these powerful libraries are already out there.” my opinion would be that there is, of course, always room for more innovation, particularly in areas that we don’t yet foresee. With each new generation of languages, we see an equal corresponding leap in the capabilities of the libraries that exploit them.
So as far as the work that’s already been done, we’ve only just written the libraries that we can imagine at this moment. And our imaginations are limited. Once we start playing with the next generation of languages, I’m sure we’ll come up with further tricks to play. There has, historically, always been a lag between the introduction of new language features and the emergence of interesting libraries that exploit them.
I liked your point about constraints. Half of what a programming language offers is what it *doesn’t* allow you to do, along with what it enables you to do. Though I would argue that the ultimate benefit are not the constraints themselves but rather the additional capabilities we can provide when such constraints are in place. Erlang’s constraints allow its optimizer to parallelize and distribute its programs automatically and easily. In an ideal world, we would just write an optimizer that automatically parallelizes and distributes our existing programs, but that’s been difficult. So specific niches are willing to *tolerate* writing in a constrainted language to take advantage of such an parallelizer. My own perspective on type systems is similarly pragmatic. In an ideal world, bug finders would automatically find all the errors in my program, but none exist yet. So I’m willing to tolerate writing in a restricted language to take advantage of a bug finder with good error messages and a reasonable low percentage of false positives.
My opinion is that we should take care not to forget that the constraints are not the end goal. Some languages impose a set of rigid constraints upon the user whose purpose is not clear. Does it allow the compiler to better optimize the program? Does it allow us to write an automatic parallelizer? Or detect more bugs? Does it allow for better composition properties? The answer should not be that it allows for a smaller core calculus.
A long time fan.
“The ability to refer to memory addresses (all languages, except the C family)”
I believe that Pascal got pointers as of the Borland Turbo Pascal 5.5.
“Around the same time a number of concepts were imported from C to let Pascal programmers use the C-based API of Microsoft Windows directly. These extensions included null-terminated strings, pointer arithmetic, function pointers, an address-of operator and unsafe typecasts.”
> I believe that Pascal got pointers as of the Borland Turbo Pascal 5.5.
No, 3.0 had already got it:
and I guess that was even earlier.
5.5 was the point of adding _objects_ (class type and its instance) to Pascal (in a manner similar to Apple Object Pascal, but less verbose). Very soon, in 6.0, this was expanded with the famous TurboVision library.
Apollo used Pascal as the systems programming language for its operating systems beginning in the early 1980s. It had the univ_ptr type to compare pointers to different types.
The ability to abstract object representation, i.e. encapsulation (C++, etc)
There has been (and there is) some research aiming exactly at complementing libraries with “constraints”, even on the base language that they are extending.
Stroustrup aimed in that direction with SEL (semantically enhanced library, http://www.stroustrup.com/SELLrationale.pdf) but as far as I know it didn’t really take off.
Microsoft is more pragmatically moving there through Roslyn and “Code-Aware Libraries” that come with compile-time analyzers which runs during compilation (and even inside the IDE as you type) and are normally used to restrict usage of libraries in ways that are not possible using the underlying language facilities. A simple example is discussed in this talk: https://www.youtube.com/watch?v=Ip6wrpYFHhE (Analyzers and the Rise of Code-Aware Libraries”). In practice, those analyzers are not restricted to constrain your own library, and can apply constrain on any part of the language. Of course, the compiler architecture itself has to be adapted to allow plug-ins, which is also an interesting idea from a language hacking perspective 🙂
> But, really, language design is also an exercise in deciding what not to let people do
Yep, that’s the same I’ve been saying for a few years 🙂 If a language includes both high level and low level features, it’s not a high-level one, it’s mixed. That’s happening to C++ and a flock of similar languages.
The other side is that such mixed-level languages are generally the most claimed now. Unless totally different running domains are presented, people tend to exploit a single language on all levels, to avoid sandwich-like design which is good for qualified programmers and architects but roughly confuse the main mass which prefer concentrating on language skills, not target area skills, giving beasts as a totally ignorant “Java programmer”, “C# programmer”, etc.
Languages with removed low-level languages are really DSLs for a universal high-level and safe programming.
Comments are closed.