The One True Programming Language
2024-10-08
There is only one programming language for computers - assembler.
All other languages, like C, Haskell, Python, Lisp, Prolog, etc., are just tools that help programmers generate assembler.
All of these other programming languages make assumptions about target paradigms and rely on engines and code libraries to enable them to generate assembler for paradigms of certain flavours.
Prolog relies on a backtracking engine to help it work on bare hardware. The backtracking engine is often written in assembler. The backtracking engine accompanies the Prolog code when both sit on bare hardware. More recent versions of Prolog and backtracking engines sit on top of function-based engines which sit on top of bare hardware.
Lisp 1.5 worked on bare hardware and relied on various libraries to help it do its work. Lisp 1.5 used code libraries to enact mark-and-sweep garbage collection and to implement code subroutines for non-trivial list operations.
C, Haskell, and most other popular programming languages rely on engines that implement the function-based paradigm. The idea of functions is not inherent to the original concept of CPU hardware, hence, engines and code libraries are used to accompany the assembler code generated by these languages and their compilers. We use the name “operating system” to denote such engines and code libraries. There are two main capabilities provided by such engines:
Context-switching, which was developed to support the old-fashioned concept of time-sharing. Context-switching support has been baked into modern CPU chips, in the form of MMUs (memory management units) and protection rings and the like. Fiddling with all of this extra MMU hardware requires the use of extra software running in a privileged mode.
Code-sharing of code written in the function-based paradigm.
Most CPUs implement CALL and RETurn opcodes. These instructions were originally meant for sharing common bits of code, but, tend to be used to implement program functions. Program functions look like mathematical functions, but, don’t work the same, because:
Program functions take a finite amount of time to compute a value, whereas mathematical functions work instantaneously. Mathematical functions can be instantly replaced by other mathematical functions, regardless of size, whereas this kind of thing (“referential transparency”) is more difficult when time affects the replacements, and, causes unexpected gotchas.
Program functions mutate a globally shared stack and globally shared registers. CALL/RET instructions cause memory cells and registers to change. The fact that these items are shared causes accidental complexities and causes effort to be wasted on devising work-arounds.
etc.
Treating programs as if they are pure functions is possible, but, only by hiding the aspects of time and mutation. This technique - function-based programming - is good for only one kind of programming - programming computations, i.e. using the hardware as glorified calculators. Other uses of function-based programming cause accidental complexity by pushing the function-based paradigm out of its sweet spot. In addition, programmers tend to “forget” that they are working in only a narrow paradigm and tend to “forget” that this paradigm makes it difficult to use bare hardware as anything but glorified calculators.
Most of the interesting problems of today are well out of the sweet spot for a function-based, computational approach. This includes problems like internet, GUIs, robotics, IoT, blockchain, etc. These problems share a common trait - time (sequencing, ordering) - which must be taken into account when solving the problems. Using a paradigm which ignores and hides time is counter-productive. Given that CPUs are inherently time-based, and, mutation-based, it is obvious that our hardware is capable of being used for these problems, but, that we need to invent and explore different paradigms to program in.
What is called “programming” today is not programming of bare CPUs, but, is only a function-based variant of programming that requires function-based engines and libraries. This is what we have. We’re not going to go back to square one and start all over. We have to build on what we have. It behooves us, though, to remember that function-based programming is just a single island in a sea of possibilities, and, to recognize where the function-based paradigm is being pushed beyond its sweet spot1.
Computers are just reprogrammable, electronic machines. They can be more than just glorified calculators, if we let them.
See Also
Twitter: @paul_tarvydas
For example, asynchronous concurrency cannot - by definition - be expressed using a (synchronous) function-based paradigm. We can use a synchronous function-based paradigm to express synchronous concurrency, but, that ain’t what many modern problems are made of. With such a limitation, the best that we can do is to use a function-based paradigm to express low-level (assembler-like) pieces of asynchronous concurrency, then bolt the pieces together using duct tape. Obviously, we are making do with this sort of approach - we have the internet after all. But,,, we could do better.


"Most CPUs implement CALL and RETURN opcodes. These instructions were originally meant for sharing common bits of code"
Dear Paul, are you referring to GOSUB/RETURN statements in the BASIC language? These instructions permit to call a piece of code multiple times during the execution of the program. The called section is enclosed between a label and the RETURN instruction, but it has access to all the variables declared in the main section of the program. The code section called by GOSUB behaves like a subroutine without any input parameters, neither local variables. The GOSUB/RETURN couple is a very primitive version of CALL/RETURN paradigm.
Nice post. I think this one does the clearest job of pointing out that what most programmers think of as the fundamental building blocks of software -- functions -- are in fact not fundamental at all.
> What is called “programming” today is not programming of bare CPUs, but, is only a function-based variant of programming that requires function-based engines and libraries. This is what we have. We’re not going to go back to square one and start all over. We have to build on what we have.
Speak for yourself :-D . In 3 years we'll be using LLMs to write entire operating systems with exactly the features we say we want, and no more. Those OSes will be written in whatever programming language we tell it to use, including momentarily-imaginary ones we prompt into existence.
The time has come to #RewriteTheStack .