Software simplicity
You’re welcome to disagree with me, YMMV. My take on software simplicity is…
- most simple: UNIX shell pipelines, Sector Lisp
-
least simple: anything from GNU, emacs
- I saw a lot of “simple” software in the 1970’s (early games, early IDEs, )
- Sector Lisp
- purer than pure FP, mutation not possible (no heap, either)
- 2 types - Atoms and Lists
- Sector Lisp is virtually useless on its own
- although Woodrush has done some impressive things with it Woodrush (like LambdaLisp)
- I haven’t checked out Tunney’s other work, like BLC, Redbean, Cosmopolitan, Ape, but, I wouldn’t be surprised if these were “simple”, too
- Sector Lisp is small because Tunney is doing something fundamentally right, not because it uses assembler tricks
- UNIX pipelines
- wrap bits of software, snap them into pipelines like LEGO® blocks
- the other, more unfortunate, choice is: hack and add options
- define: “simplicity”
- simple for who?
- end-user?
- developer?
- language designer?
- mathematicians?
- academics?
- simple for who?
- code libraries are NOT simple
- but, programmers believe that code libraries are a good approximation to LEGO® blocks
- complexity is due to hidden dependencies
- “0D” is my way of decoupling code libraries, at a very low level
- a CPU has a single, shared stack, so we have to play complex games with it to run more than one thread, which complexifies code libraries
- code, as we know it, is based on LIFO principles, as inspired by Assembler
- Lisp is a programming language with a non-sequential textual syntax
- Prolog and other relational languages and HTML (sans JavaScript) are examples of declarative textual syntaxes
- pipelines are based on FIFO principles (i.e. not LIFO, not stacks, not CALL/RETURN)
- the model for using “programmable electronic machines” has changed drastically since 1950
- in 1950, CPUs and Memory were VERY expensive,
- so, it made sense to waste developers’ time to invent …
- timesharing
- Garbage Collection
- Operating Systems
- so, it made sense to waste developers’ time to invent …
- in 2023, CPUs and Memory are VERY cheap and abundant
- the model of “concurrency” can revert back to the original model, i.e. one CPU plus private memory for each thread
- time-sharing not needed
- shared memory need not be supported by default
- “thread safety”, et al, just dissolves
- “thread safety”, et al, is just accidental complexity due to the misuse of the idea of sharing memory, thinking that this is necessary
- “thread safety”, et al, just dissolves
- Garbage Collection, except the Biblical Flood method, not needed
- Operating Systems not needed
- in 1950, hardware could only easily support non-overlapping grids of small bitmaps (aka “characters”)
- hence, in 1950, it made sense to develop text-only programming languages
- text-only programming languages are “caveman IDEs” for programming
- IBM channels, Apple Postscript printers supported “smart devices” with embedded, asynchronous electronic machines, but, …
- we went on a deep dive with single, time-shared CPUs and shared memory
- this caused lots and lots of gotchas (a very public example was the Mars Pathfinder disaster)
- in 2023, hardware can support vector graphics
- cells can overlap
- cells can be resized
- sequential thinking is no longer needed
- mice and keyboards and other input devices can be treated as asynchronous input devices and can be allocated one CPU chip + private memory for each kind of task
- Functions in mathematics are not like subroutines in prEMs (aka “computers”)
- functions in math are more like “macros” - instantly swappable (no time penalties involved)
- subroutines in prEMs are sequenced in time by CPU chips (which are just really fast interpreters)
- this is not the same thing as “functions” in mathematical notation
- hence, lots and lots of gotchas trying to work around the mismatches
- subroutines are scripts, functions are macros - these are not the same concepts
- forcing scripts and macros to be treated in the same way leads to accidental complexity, workarounds, complexity
- software is always buggy in the field
- hence, we invented support for bugginess, e.g. CI/CD and rapid updating
- the measure for repos is backwards
- lots of activity is bad
- means that something needs to be fixed
- if not the software itself, then the design
- if the design is off, then it’s a bug, regardless of whether the code implementing the buggy design is type-safe or not
- means that something needs to be fixed
- lots of activity is bad
- hardware is often not buggy in the field (zero defects in the field)
- chance of lawsuit for selling buggy products
- chance of jail time for selling buggy products
- Moore’s Law for hardware
- N.B. hardware is asynchronous by default
- no Moore’s Law for software
- N.B. software is synchronous by default
- my conclusions:
- don’t stretch paradigms
- if you use FP, remove all semblance of mutation
- no assignment
- no heap
- if this leaves you with a mostly useless notation, so be it
- if this makes it impossible to fully solve a problem with a single notation, then, include another notation(s)
- don’t hack on the notation, just use/invent another notation
- I’ve reached the conclusion that bloatware is due to our hacking on the FP paradigm, to add mutation and heaps to the paradigm
- don’t hack on the notation, just use/invent another notation
- include other notations in a project if a given notation / programming language doesn’t fit or is too restricted to solve the whole problem
- solve problems in little bits (aka “divide and conquer”), using different languages
- e.g Prolog supports exhaustive search, but was stretched to support formatting of output, resulting in accidental complexities and difficulty in writing simple code for formatted output, like, say, in JavaScript
- e.g. JavaScript supports web page description, but was stretched to allow multitasking (“callbacks”, promises, futures, …), resulting in accidental complexities
- if you use FP, remove all semblance of mutation
- “one language to rule them all” is a bad idea and can only lead to complexity
- figure out how to plug different languages together
- strong typing is good inside a software component, but bad for inter-component communication plumbing
- comms needs to be dumbed down and use only a single type which is very low-level (e.g.
void *
)
- comms needs to be dumbed down and use only a single type which is very low-level (e.g.
- build software in layers, with software LEGO® blocks (i.e. not code repos based on CALL/RETURN)
- FIFO for comms, not LIFO for comms
- eschew options
- use wrappers and pipelines instead
- eschew Operating Systems
- eschew text-only programming languages
- eschew synchronous-only programming
- in many cases, you don’t need synchronization, so, don’t start out with a synchronous mindset
- build progressive type pipelines instead of strongly typed languages
- programs became non-simple when a whole program was no longer visible in one eye-full
- the notation was stretched to allow “bigger” programs, instead of snapping little bits together in small, understandable layers
- don’t share memory by default
- optimize only if necessary
- optimization is the antithesis of scalability
- most existing programming languages - Haskell, Python, JavaScript, Rust, etc. - force programmers to deal with optimization too early (like, right off the bat)
- dynamic type checking should be the default, use static type checking sparingly only when optimization is proven to be necessary (e.g. by profiling)
- prEMs (“computers” - programmable Electronic Machines) can do more than just act as automation for mathematics
- projects that use current-day programming languages are essentially doomed to abandonment or complexification
- every time you add a CALL to code, you are adding a hidden dependency, which, over time, will result in complexity
- using CALL is the only way provided by current languages to bolt to other libraries and to add functionality
- other techniques are possible, but, using CALL is so strongly encouraged, that programmers skip over other ways of bolting software together
- regardless if the CALL is made indirect via “dependency injection” techniques
- using CALL is the only way provided by current languages to bolt to other libraries and to add functionality
- don’t stretch paradigms
- in 1950, CPUs and Memory were VERY expensive,
Appendix - See Also
References
https://guitarvydas.github.io/2021/12/15/References.html
Blogs
obsidian blogs (see blogs that begin with a date 202x-xx-xx-)
Videos
videos - programming simplicity playlist
Books
leanpub’ed (disclaimer: leanpub encourages publishing books before they are finalized - these books are WIPs)
Programming Simplicity Takeaways, and, Programming Simplicity Broad Brush
Discord
Programming Simplicity all welcome, I invite more discussion of these topics, esp. regarding Drawware and 0D
@paul_tarvydas
Mastodon
(tbd, advice needed re. most appropriate server(s))