Showing posts with label abstraction. Show all posts
Showing posts with label abstraction. Show all posts

Tuesday, March 24, 2026

Programming languages and paradigms

Steps for creating a programming language:

  1. Think about who will want to use your new language and for what.
  2. Conjecture a unified mental model of how a programmer should think about your language. Make it fit how your users think and their expected use cases.
  3. Translate your mental model into machine code.
  4. Announce your new language to the world, making sure to report how users should think about your language.

Steps for creating a modern general-purpose programming language:

  1. Define your target audience as "everyone" and their expected use cases as "anything ever".
  2. Make a unified mental model of how everyone should approach doing anything ever. Model it after how everyone currently does everything.
  3. ???
  4. Be a large corporation. Make everyone use your language to interact with your products. Eventually, people will start using it for other tasks out of familiarity.

That was probably the fastest I have yet derailed a blog post. I want to talk about how to make a proper mental model.

Switch-statements

C# requires a control flow statement at the end of every case in a switch-statement. I'm sure they wished to add an implicit `break` after every (non-empty) case, but that would have shocked developers coming from C, where cases fall through by default. So they resigned themselves to making the user choose control-flow behavior explicitly, presumably reasoning that any default would surprise someone.

I know why C chose default fall-through and C# didn't.

In my oversimplified understanding, control flow constructs compile to jump-instructions--the equivalent of using `goto` everywhere. Switch-statements compile to jump tables, which figure out how far forward in the code to jump before you start executing again.

Notably, a jump table says nothing about where to jump after that. It doesn't know what a switch case is. So a `break` statement at the end of the switch case is another jump instruction--extra effort for the runtime. Fall-through is the default in C because it is the default for the implementation. C probably expects its programmers to know already basically what the assembler will do and to write programs based on that knowledge.

C# doesn't do that. C# would consider that an implementation detail. Why would you need to know assembly concepts to program in C#? In C#, fall-through requires the use of `goto`, the most damning marker of unidiomaticity that I can imagine. The creators of the C# language did not want people using switch-statements with fall-through. (Stephen Toub does, though, if I remember correctly.)

(Actually, switch-statements don't always compile to jump tables in C#. The probably do if you're switching on int (or char or bool), but they can also do pattern matching using the same syntax. In C#, the code you write may have a completely different implementation based on context.)

The point

Why does every modern language try to be opaque?

Richard Gabriel says that simplicity of implementation is at least as important as simplicity of interface. What sense does this make unless the user is supposed to understand the implementation? And Joel Spolsky argues that the user will need a correct mental model of the implementation anyway. These are big figures in software, worthy of our attention.

We all agree that programs should be simple. Is there any plausible measurement of software simplicity other than how easy it is for the computer to run? ("Quick for the programmer to write" is a separate question; a throwaway script may rely on brute force.)

To write good code, you must first have the right mental model. The right mental model is that which most correctly describes the problem. In programming, every problem is flipping some bits somewhere into the proper state. So should we not be thinking in those terms? Should we not use technologies that we are allowed to understand?

Friday, May 3, 2024

If "worse is better", then why monoliths?

Richard Gabriel, in his essay The Rise of "Worse Is Better", argues that systems should be written first for functionality and only later for correctness--that is, for consistency and completeness.

His reasons:

  1. Systems that focus on practical use are less computationally expensive to use, and thus receive widespread adoption on underpowered machines.
  2. Because of the assumption of inconsistency, users will become accustomed to needing to know something about the implementation of the system. (This also ameliorates leaky abstractions.)
  3. Components will be kept small and reused ("because a New Jersey language and system are not really powerful enough to build complex monolithic software").

It took a while, but I have finally begun to agree with this way of thinking. "Make it work, make it right, make it fast" seems to demand first making a pile of utilities and then conglomerating it into a language or framework or IDE or whatnot when that becomes useful. The world is still under development, and I can help, so I should build instead of complaining. Systems are not complete because they are never complete; if they were, they would have nothing for me to add.

But now, after rereading the conclusion of the article, I have realized that I mostly use and work on kitchen-sink "workflow" programs that have received none of the stated benefits of "worse is better":

  1. They are computationally expensive and supported only on certain architectures.
  2. Their users have no idea how they work or why certain interactions are more likely to produce bugs.
  3. They feature primarily GUI interfaces, thus not being integrated easily, nor do they apparently integrate other programs, since each one features yet another awful, flashy, underpowered text editor.

So why do we have monolithic programs? Do users really desire all those details to be hidden and thus inaccessible? What do they do when the program fails to work as intended, anyway? Have Oracle and Microsoft pushed us into thinking in terms of monoliths against our better interests so that they can sell us products related to their platforms? Or do we have good reasons to build programs that each do fifteen different things?

And if we have indeed written monolithic programs, then have we also perhaps written needlessly bloated languages? Should we reconsider our usual implicit assumption that everything should be done in the same language, which implies that each language must cover all use cases?

My programming languages of choice: 06-11-2026

Shell If the real work can be done with existing utilities. Bash: My default Included with Git for Windows PowerShell: Included with Windows...