Your abstraction isn’t wrong, it’s just really bad

Link post

Epistemtic status: pretty sure

For a programming language to qualify as such, the only thing it needs is Turing Completeness. That means, at least in principle, it can be used to solve any (solvable) computational problem.

There are features besides Turing Completeness which make it desirable, but if you tried hard enough, all of those features could be implemented as part of any Turing complete language.

Let’s take a maximally perverse theory of abstraction-making that says:

An abstraction is sufficiently good if, in principle, it can yield answers to all questions that it’s designed to solve.

Under this definition, the assembly language (ASM) of virtually any computer is sufficient abstraction for writing code.

The only “problem” with ASM is that writing something simple like quicksort ends up being monstrously large and hard to read (and this implementation is written for clarity, not speed).

Compare that to a quicksort implementation written in, C. It’s only 13 lines of code and about as equally efficient as the ASM one above. Even better, it would work on virtually all machines, while the above ASM wouldn’t and would have to be rewritten for a bunch of architectures.

C might not be the best abstraction one can use for low-level programming, e.g. writing sorting algorithms, but it’s better than ASM.

However the ASM implementation is far from being the maximally perverse version one can get under our naive abstraction-choosing constraints. The prize for that would currently go to something like the brainfuck implementation:

>>+>>>>>,[>+>>,]>+[--[+<<<-]<[<+>-]<[<[->[<<<+>>>>+<-]<<[>>+>[->]<<[<] <-]>]>>>+<[[-]<[>+<-]<]>[[>>>]+<<<-<[<<[<<<]>>+>[>>>]<-]<<[<<<]>[>>[>> >]<+<<[<<<]>-]]+<<<]+[->>>]>>]>>[.>>>] 

Now that, that is what I call seriously fucked up.

But brainfuck is Turing complete, so under the above definition, it is no worse than ASM or C.

How we avoid bad languages?

So C, C++, Javascript, Python, Julia, any given ASM, Fortran, Lua, Elm, Clojure, CLISP, Rust, Forth, Elixir, Erlang, Go, Nim, Scala, Ada, Cobolt, Brainfuck, and, sadly enough, even Java, are “good enough to be an abstraction for controlling ANY computing systems” under the perverse requirements above.

In reality, it’s fairly easy to agree that some languages are better than others in various situations. I think most criteria for choosing a language are at least somewhat subjective. Things such as:

  • Architectures that the compiler will target

  • Memory safety features.

  • Built-in parallelism and concurrency mechanisms.

  • Functionality of standard library.

  • Available package manager.

  • The companies/​projects/​communities that already use it.

  • Ease of learning.

  • Ease of reading.

  • Ease of debugging.

  • Speed of compiler[s].

  • Efficiency of memory usage (e.g. via avoiding spurious pointers and having move semantics).

  • Performance on various benchmarks.

  • What I want to use the language for (which is probably the most important).

But even subjective criteria allows me to ask a question like: “What do I need in order to create a language that is better than C and C++ for systems programming?”. To which the answer will be something like:

  • Be Turing complete

  • Support all or most targets that C/​C++ support

  • Have memory safety feature (e.g. borrow checking, automatic deallocation) that C and C++ don’t have.

  • Have the same concurrency abstractions as C and C++ (Threads mapping to kernel threads)) and maybe some extra ones (e.g. futures and async-io semantics for more efficient non-blocking IO).

  • Have a standard library that includes all glibc and std-lib functionality plus some extra things that people want.

  • Have a package manager.

  • Try to make nice companies and welcoming communities that are viewed well by outsiders use your language.

  • Have better, more centralized documentation than C/​C++ and community that is nicer and more helpful to newbies.

  • Try to be about as easy to read as C and C++, ideally more so by getting rid of BC syntax and outdated operators.

  • Have a good debugger and nice error logs, or at least nicer than C and C++.

  • {Try} to have a fast compiler.

  • Be about as efficient as C and C++ when working with memory, potentially making useful abstractions (e.g. moving, deallocation) implicit instead of explicit whenever possible. Perhaps by using a better default allocator.

  • Perform equally~ish well to C and C++ on various benchmarks that people trust

  • Be useable in the same situations C and C++ are, maybe more, if possible. This is partially a function of all of the above plus the external library ecosystem, which the language designer can nudge but has no direct control of.

What I describe above is, of course, Rust. A language that, with certain caveats, has managed to become better than C and C++[citation needed] for systems programming (and many other things).

Granted, the world hasn’t been rewritten in Rust yet. (Though I hear the R.E.S.F has managed to secure an audience with God and given his buy-in, you’d be only one level removed from converting von Neuman.)

But it does have wide adoption and I would be deeply surprised if in 10-20 years from now most server kernels and common low-level libraries (e.g. BLAS, CUDNN) won’t be written in it.

For a more post factum example, you can take something like Python, due to a good package manager and easy to understand syntax, it managed to become the dominant scripting language for… everything.

For another example, take Nodejs, which used people’s familiarity with javascript and it’s build-in asyncio capabilities to become the language of choice for many low-load http servers.

Or take something simpler, like TypeScript, which just took Javascript, added a feature people really, really wanted (types), and swiftly became a fairly dominant language in the frontend world.

Our model for avoiding bad languages is based on continuous improvement and competition. You look at what existing language are used for and what people want to do with them, you look at their syntax and their performance. Then you build something that fits the basic requirements (is Turing complete, runs on x86) and tries to be better than then them in the pain points previously identified. If your new language is good enough, it will sweep everyone away and 10 years from now we’ll all be using it.

A similar but less complex set of criteria can be defined for libraries and open-source software in general. A similar but more complex set of criteria can be defined for processor and computer architectures.

The languages, processors, and libraries of the 80s are all “kinda bad” by modern standards. The ones of the 40s are basically at the brainfuck levels of horribleness. But this is not to say that the people of the 80s or 40s were bad programmers, it’s just that they didn’t have the knowledge and tools that we have right now.

Indeed, I’d wager that the pioneers of computer science were probably smarter than the people doing it now, successful pioneers tend to be that way, but seeing mistakes in hindsight is much easier than predicting them.

The requirements for avoiding bad abstraction

So, I think the requirements for avoiding bad abstraction, at least in terms of programming languages and programming abstractions in general, can be boiled down to:

  1. Minimal and rather specific core standards (Turing completeness, ability to run on a popular~ish CPU).

  2. Ability to name areas where improvements can be made such that the language could be considered “better” than current ones.

  3. A general willingness of the community to adopt the language. Or at least a mechanism by which the community can be forced to adopt it (see market-driven competition).

Granted, these requirements are far from being fulfilled perfectly. The adage still holds mostly true:

Science progresses one funeral at a time

Programming languages are not quite like scientific paradigms, partially because the field they are used in is far more competitive, partially because they are much easier to design than a scientific theory.

An average programmer might switch between using 2 or 3 significantly different languages in one area over their lifetime. An especially good one might bring that number to 10, 20, or 50 (most of which are going to be failed experiments).

There’s no programming God that can just say “X is obviously better than Y so switch all Y code to X and start using X”. There is however something close to an efficient market that says something like:

If I can design a piece of software in 1 hour using my language and you can design it in 100 hours using yours, I will soon build a 3 person startup that will eat your mid-sized corporation for lunch.

However, I don’t want to downplay the role that community plays here.

Imagine designing a new language and trying to improve and popularize it, you’ll need validation and feedback. In general, the reactions you will get will probably be neutral to positive.

Being already popular and respected helps (see Mozzila and Rust), but so does having a language that seems very obvious useful by filling a niche (see frontend devs wanting to work full-stack and Node) or just having an amazing syntax (see Python way back when). However, the worst-case scenario is that you end up getting some “oh that’s nice BUT …” style reactions and are left with a good learning experience as the basis for your next attempt.

I don’t think anyone ever tried to build a programming language (or a library or a CPU arch for that matter) and was met with:

Oh, you stupid quack, you think you’re better than the thousands of language designers and dozens of millions of developers building and using a programming language. You really think you’ve managed to out-smart so many people and come up with a better language/​library !?

I think that the lack of this reaction is good. However, try to challenge and redesign a core abstraction of any other field (internal medicine, high-energy physics, astronomy, metabolic biology… etc) and that’s roughly the reaction you will get. Unless you are a very respected scientist, in which case you will usually get indifference instead and maybe in 60 years, after the death of a few generations, your abstractions will start being adopted. But this is a very subjective impression.

On the other hand, good CS students are routinely challenged to design their language and compiler. Maybe 9991,000 times it sucks, but that’s ok because they get a better understanding of why our current languages are good and of the principles behind them. Even better, maybe 11,000 times you get the seeds of something like Scala.

I have it on good authority that no physics students were ever tasked with trying to re-design the mathematical apparatus used to interpret the results of high energy collisions into taxonomical facts.

We make fun of tools, but in general, we never make fun of tool-creation. Creating tools for programming (e.g. language and libraries) is as close as you can get to a sacrament in the programming community. It’s something that’s subconsciously seen as so desirable that nobody has anything but praise for the attempt (though, again, the results are fair game for harsh critique).

Note: Of course, outliers exist everywhere, including here.

Is your abstraction bad?

So the reasons we know our programming abstractions (e.g. languages and libraries) are kind of good is that we have a market mechanism to promote them, a culture that encourages and helps their creation, and both explicit and implicit criteria that we can judge them by.

We don’t know how good they are in an absolute sense, we can be almost certain they are not the best, but we can be 100% sure that they are not the worst, because we know they aren’t Brainfuck.

But take away any piece of the puzzle and the whole edifice crumbles, or at least is badly damaged. If you replace the free-market determination with a council of elders or a regulatory board, then Node and Python don’t exist. If you replace the community with one that is trying to minimize the amount of new things they have to learn, then Rust doesn’t. If the quality criteria were much fuzzier than we might have just stopped at ASM instructions and never designed any language.

This is the fear I have about a lot of other abstractions.

Take for example taxonomies in fields like biology and medicine.

There’s a taxonomy that clusters things that can approximately replicate the contents of DNA and RNA molecules encased within them into: Realm > Kingdom > .... > Genus > Specie
.

There are taxonomies that cluster symptoms together into diseases to better target them with drugs, investigate the underlying cause, and predict their progress.

But the way these taxonomies are designed does not seem immediately obvious, nor does it seem like one of the fundamental questions that their respective fields struggle with.

If I inquire “why is life classified the way it is ?”, I think the best steelman of a biologist my mind can come up with will answer something like:


Well, because it evolved (no pun intended) all the way from Aristotle to us. There are some good Schelling points such as “things that fly are different from things that walk, are different from things that crawl, are different from things that swim, are different from things which are immutable and green”. Rough outlines were built around those and some ideas (e.g. Kingdom, Species) kinda stuck because they seem obvious. Once we understood evolution and the fossil records we realized that things are a bit more complex and we came up with this idea of phylogenetics, but we decided to work around the existing rank in the hierarchy to calcify their meaning a bit better, then added more ranks once they seemed necessary.
Yes, maybe even viewing it as a hierarchy is rather stupid, since that whole view works for e.g. multicellular eukaryotes that tend to keep slowly adding to their DNA and leave a fossil record but seems kinda silly for bacteria which swap, add and discard DNA like crazy and leave none but the vaguest trace of their existence a few seconds after their cell wall breaks. But it kinda works even for viruses and bacteria if you make a few adjustments.
Yes, there are arbitrary rules we apply, for example, the more foreign a lifeform is to us the more likely we are to use genetics to classify it, and the more often we encounter it the more likely we are to use phenotype.
Yes, maybe forcing kids to memorize these things is school is pointless and arbitrary, and yes I see no particular reason to put this taxonomy on a golden pedestal, but it’s the one that everyone uses so you might as well get used to it.

This is a fairly good defense of the taxonomy, all things considered, it’s a very similar defense to the one I’d give if someone asked me why it’s still relevant to know C or C++.

But it’s a defense that leverages the idea that the current taxonomy is fit enough for its purpose, thus there’s no reason to change it. However, I fail to see why we consider it to be so. The role of a taxonomy is to dictate discussion, indexing, and thought patterns, this is a rather complex subject and can only be explored via querying individual preferences and observing how individuals using different taxonomies fare against each other in a competitive environment. But if my only option is to use this taxonomy and doing away with the whole edifice in favor of something new is off-limits, then I think it’s fair to argue that we have exactly 0 datapoints to suggest this is a good taxonomy.

If the whole reason we use the taxonomy is because “It kinda classifies all life, so it’s fit for purpose”, that’s like saying brainfuck is Turing complete, so it’s fit for purpose. An abstraction can be fit for purpose under the most perverse possible definition and still be very bad.

In programming, if I think C is garbage, or, even worst, if I think OO and imperative programming as a whole is rubbish, I can go to one of 1001 university departments, meetups, and companies that use functional languages and patterns.

If I think that even those are rubbish and I specifically want a functional language designed under some niche tenants of a sub-branch of set theory designed by a few mathematicians in the 50s… I can go to Glasgow and find a whole university department dedicated to that one language.

The reason I can point to C and say “this is an ok language” is because I can take i3 and xmonad, look at their code+binary and say “Look, this is a C program compared to a Haskell one, they fulfill the same function and have x,y,z advantage, and disadvantages”. That times 1000, means I have some reason to believe C is good, otherwise C programmers would be out of a job because everyone would be writing superior software in Haskell.

But I’m not aware of any department of biology that said: “Hmh, you know what, this life-classification system we have here could be improved a lot, let’s redesign it and use the new system to communicate and teach our students to use it”. If we had one such department or 100 such departments, we would suddenly have some data points to judge the quality of our current taxonomy.

A dominant abstraction still has a huge home-field advantage. But the reason for experimenting with abstraction is not to get minor improvements (e.g. Python3.7 to Python3.8) but to get paradigm-shifting ones (e.g. C vs Rust). The changes we are looking for should still be visible even in those subpar conditions.

If Firefox used to be kinda slow, and in the last 2 years it’s become the fastest browser on all platforms, that starts to hint at “Huh, maybe this Rust thing is good”. It’s not a certainty, but if we have 100 other such examples than that’s much better than nothing.

Conversely, if the university working with {counterfactual-taxonomy-of-life} is suddenly producing a bunch of small molecules that help people stay thin without significant side effects, although billions of dollars went into researching them and nobody really found one before, maybe that’s a hint that {counterfactual-taxonomy-of-life} is bringing some useful thought patterns to the table. It’s not a certainty, but if we have 100 other such examples than that’s better than nothing.

Granted, I don’t expect fundamental taxonomies to be a good candidate for this approach, they are just an easy and uncontroversial example to give. Everyone agrees they are somewhat arbitrary, everyone agrees that we could create better ones but the coordination problem of getting them adopted is not worth solving.

So fine, let me instead jump ship to the most extreme possible example.

Why do we have to use math?

Generally speaking, I can’t remember myself or anyone else as kids protesting against learning numbers and how to add and subtract them. As in, nobody asked “Why do we have to learn this?” or “Is this useful to adults?”.

Granted, that might be because children learning numbers have just mustered basic speech and bladder control a few birthdays ago, so maybe there’s still a gap to be crossed until they can protests to their teachers with those kinds of questions.

But I doubt it. I think there’s something almost intuitive about numbers, additions, and Euclidean geometry. Maybe one doesn’t come up with them on their own in the state of nature, but they are very intuitive abstractions, once someone points them out to you they seem obvious, natural, true.

This is a famous argument and I think Plato does better justice to it than I could.

Some context, Socrates (So) is arguing with Menos about the existence of “recollection” (I’d rather think of this as abstractions/​ideas that become immediately obvious to anyone once pointed out). He brings out a slave with no knowledge of geometry and draws the following figures:

http://​​cgal-discuss.949826.n4.nabble.com/​​file/​​n2015843/​​image.jpg

  • So: Tell me, boy, do you know that a square is like this?

  • Slave: I do.

  • So: And so a square has these lines, four of them, all equal?

  • Slave: Of course.

  • So: And these ones going through the center are also equal?

  • Slave: Yes.

  • So: And so there would be larger and smaller versions of this area?

  • Slave: Certainly.

  • So: Now, if this side were two feet and this side two feet also, how many feet would the whole be? Look at it like this: if this one were two feet but this one only one foot, wouldn’t the area have to be two feet taken once?

  • Slave: Yes.

  • So: When this one is also two feet, there would be twice two?

  • Slave: There would.

  • So: An area of twice two feet?

  • Slave: Yes.

  • So: How much is twice two feet? Calculate and tell me.

  • Slave: Four, Socrates.

  • So: Couldn’t there be one different from this, doubled, but of the same kind, with all the lines equal, as in that one?

  • Slave: Yes.

  • So: And how many feet in area?

  • Slave: Eight.

  • So: Come then, try to tell me how long each line of this one will be. In that one, it’s two, but what about in that doubled one?

  • Slave: It’s clearly double, Socrates.

  • So: You see, Meno, that I am not teaching anything, but put everything as a question. He now believes he knows what sort of line the eight feet area comes from. Or don’t you think so?

The point at which children start questioning their math teachers seem to coincide with the point where more complex abstraction is introduced.

There’s nothing fundamentally “truer” about euclidian geometry than about analysis. Yes, the idea of breaking down lines into an infinity of infinitely small distances might conflict with the epistemology of a kid, but so might the axioms of Euclidian geometry.

Where the difference between Euclidian geometry and analysis lies is in the obviousness of the abstraction. With something like analysis it seems like the abstractions being thought are kind of arbitrary, not in that they are untrue, but in that they could be differently true.

Maybe it’s not even obvious why “infinitely small distance” is a better choice of abstraction than “100,000,000,000 small distances”.

It’s not obvious why reasoning analytically about the area under a curve is superior to the Greek geometric approach of starting with a few priors and reasoning out equality through similarity. (not to a kid, that is)

It’s not obvious why the ‘trend’ of a certain single-parameter function, represented by another single-parameter function is an important abstraction to have at all.

I think that kids asking their math teachers “Why do we have to learn this?”, want, or at least would be best served with an answer to one of two interpretations:

  1. Why is this abstraction more relevant than any other abstraction I could be learning? The abstractions math previously gave me seemed like obvious things about the world. This analysis thing seems counter-intuitive at first, so even if it’s true, that in itself doesn’t seem like reason enough for me to care.

  2. Why was this abstraction chosen to solve this set of problems? How did people stumble upon those problems and decide they were worth solving? What other abstractions did they try to end up with on that is so complex?

But instead, the answers most teachers give are answers to the question:

Why do we have to learn math?

I think most people have this mental split at some point, between the mathematic that is intuitive and that which isn’t. Maybe for some people, it happens right after they learn to count, maybe for others, it happens when they get to 4d geometry.

I think many people blame this split on something like curiosity, or inborn ability. But I blame this split on whichever qualia dictate which mathematical abstractions are “intuitive” and which aren’t.

Furthermore, I don’t think this split can be easily resolved, I think to truly resolve it you’d need to find a problem impervious to intuitive abstractions, then try out a bunch of abstractions that fail short of solving it, then reason your way to one that does (which is likely going to be the “standard” one).

But it seems that abstraction-finding, whilst most certainly a part of mathematics, is something almost nobody is explicitly taught how to do.

To put it another way, I think that anyone learning to program, if asked “How would you redesign C to make it better?”, could give an answer. Maybe a wrong answer, almost certainly an answer far worst than the “best” answers out there. Most people are asked some variant of this question, or at least ask themselves, a significant percentage even try to implement it… maybe not quite at the level of trying to redesign C, but at least at the level of trying to redesign some tiny library.

On the other hand, someone that’s learned analysis for a few years, if asked how they would improve it, would fail to answer… even a poor answer, even a wrong answer, the question would seem as intractable to them as it would be to their 6-year-old self.

If I proposed to you that in 20 years schools and colleges would be teaching either Rust or Python instead of C and Java you might argue with that idea, but it would certainly seem like something within the realm of real possibilities.

If I proposed to you that in 20 years schools and colleges would have thrown away their analysis books and started teaching a very different paradigm you would ask me what’s the stake and odds I’m willing to bet on that.

Maybe that is because math is perfect, or at least because math is close to perfect. Maybe one can make minute improvements and completions to the old knowledge, but the emphasis in that sentence should be placed on “minute” and “completions”.

One thing that strikes me as interesting about mathematics, under this hypothesis, is that it seems to have gotten it impossibly right the first time around. E.g. the way one best abstracts figuring out the equation for the area under a curve in 17th-century with limited ink and paper, is the same way one best abstracts it when sitting at a desk with a computing machine millions of times faster than our brain.

I’m not comfortable making this point about analysis, I’ve tried thinking about an idea like “What if we assumed continuous variables did not exist and built math from there Pythagora style”, every time I ran into an issue, so I’m fairly sure a good mathematician could poke 1001 holes in this approach.

However, in areas that interest me like statistics, I think it’s fairly easy to see gralingly bad abstractions that have little reason for existing. From people still writing about trends without using any test data, let alone something like k-fold cross-validation, to people assuming reality has an affinity for straight lines and bell shapes until proven otherwise.

Maybe statistics is alone in using outdated abstractions, or maybe there are many areas of math like it. But because mathematics is (fairly) hierarchical in terms of the abstractions it uses, there’s no marketplace for them to fight it out. New abstractions are forced to be born into new niches, or via strangling out a competitor by proving an edge case they couldn’t.

When Python was born nobody ever claimed it does more than C, it is, by definition, impossible to do something with Python (as implemented by CPython) that can’t be done with C. On the other hand, that doesn’t make the Python abstraction inferior, indeed, for the vast majority of jobs, it’s much better.

Is there such an abstraction we are missing out on in math? Some way to teach kids analysis that is as obvious as Euclidian geometry. I don’t know, but I do know that I’d have no incentive to ever figure it out and I don’t think anybody else does either. That fact makes me uncomfortable.

Perhaps mathematics is the worst possible field to reason about abstraction quality, but I feel like there are a lot of other easier picks where even a bit of abstraction competition could greatly improve things.

Alas

I think the way programming handles creating new abstractions is rather unique among any field of intellectual endeavor.

Maybe this is unique to programming for a reason or maybe I’m wrongfully associating the most fluid parts of programming with the most immutable parts of other fields.

But I do think that the idea is worth exploring more, especially light of our knowledge accumulation problem and the severe anti-intellectualism and lack of polymaths in the current world.

Maybe all theory is almost perfect and improving it can only be done incrementally. But maybe, if thousands of people attempted to completely revamp various theories every day, we’d come up with some exponentially better ones. I think the only field that provides evidence regarding this is programming and I think the evidence points towards the latter approach is very promising.