I think that I view programming differently from most modern programmers because I was forced, out of necessity, to write programs in machine code at first.
I see all programming languages and paradigms as ways to help me write machine code with fewer bugs. No language, including function-based ones like C and FP, covers all of the bases.
When you implement other paradigms on top of the FP paradigm, sometimes the FP paradigm becomes a hindrance. I see Forth and Smalltalk and Prolog and FP as paradigms that are implemented on top of sequential synchronous CPUs, instead of being implemented on top of the FP paradigm.
Lisp did foster the Functional Programming paradigm. I understood the inner workings of early Lisp. I could see that in addition to FP, Lisp allowed programmers to build programs using any other paradigm, realizing that not every problem should be solved in a function-based manner.
Early programming was concerned with simple CPUs. Somewhere along the way, CPUs accreted MMUs, VM, preemption, caches, etc. Today’s CPUs are FP machines that strongly encourage the use of the FP paradigm. Lisp machines, OO machines, Forth machines, Smalltalk machines, CSP machines, connection machines, etc. were invented but didn’t catch on as vehemently as did FP machines.
To me, Clojure is not Lisp. It is a language for functional programming with a lisp-y syntax instead of a javascript-y syntax. Lisp is an assembler with a tree-oriented syntax instead of a line-oriented syntax. Lisp does not enforce the use of any particular paradigm, while Clojure strongly encourages the use of a single paradigm (the FP paradigm).
The biggest failing of Lisp is that it emphasizes the idea that subroutines are functions. Which leads to wanting them to be pure functions. When all you’ve got is functions, then every problem looks like it must be solved using functions.
Functional thinking implies
the use of blocking - the caller must suspend operation while waiting for the callee to return a value
a single pattern for routing - the callee must return a value to the caller, while side-effects, like sending values elsewhere, is discouraged
addition of complicated baubles to handle out-of-band concepts like control flows that aren’t sequential, e.g. exceptions
strong coupling - clockwork thinking.
These basic tenets of Functional Programming and function-based programming are at odds with modern problem spaces, like internet distributed programming, robotics, NPCs in games, etc., hence, we run into gotchas like callback hell, priority inversion, increasing complexity, increased memory usage, [TBD], etc.
These basic tenets of Functional Programming, also, require the use of preemptive operating systems. We don’t need preemption, except to act as scaffolding to support FP programming. Preemption is just a technique that converts functions into state machines. Context switching saves the state of a computation and resumes it later. Instead of letting programmers decide when to switch states, preemption makes the decision under the hood in a random manner, based on code that isn’t part of the code being preempted. This kind of implicit, non-local behaviour is OK for building things like calculators where only the result is important, but, makes it harder to reason about things like sequencers where the actual control flow path is important. Sequencers include apps like iMovie, DAWs, machine control, robotics, blockchain, routers, daemons and servers, etc.
An analogy might be: we understand all of cosmology, except for the 95% of the universe which we call dark matter. These other aspects of programming are like the dark matter of programming. They are simply excluded from our 1960s, Gutenberg type-set, equation-based, synchronous, sequential approach to programming.
See Also
Email: ptcomputingsimplicity@gmail.com
References: https://guitarvydas.github.io/2024/01/06/References.html
Blog: guitarvydas.github.io
Videos: https://www.youtube.com/@programmingsimplicity2980
Discord: https://discord.gg/65YZUh6Jpq
Leanpub: [WIP] https://leanpub.com/u/paul-tarvydas
Gumroad: tarvydas.gumroad.com
Twitter: @paul_tarvydas
Substack: paultarvydas.substack.com
And a question — what is the difference between “FP”, “Functional Programming” (uppercase), “function based programming”, “functional programming” (lowercase) ?
And where would Python & JS fit into those categories?
Super thought provoking. Okay… earlier today my son made a comment about checking the time using a phone, and I realized that he’s never really been around anyone who wears a watch. And I was thinking that, in a lot of ways, the bundling of *everything* in an iPhone isn’t a matter of elegance, it’s a matter of economics. Having hardware for dedicated tasks is actually much more elegant than smushing everything together. (I hate modern cars in this regard.) I think it’s because hardware is expensive to develop, but also we’re really bad at pricing distractions. (Just thinking out loud…now that I think about it, it actually makes sense to pay like $5000 for a watch across ten years to save myself 10 looks at my phone every day. Right? I’m just melting my brain looking at this thing.)
But anyway, my thought with your essay was: are we always going to be hamstrung in our way of thinking so long as we use traditional CPUs? And, like the iPhone, do we use one CPU architecture for every task because it’s elegant or because it’s economical? And will there be a day when we can identify that alternative architectures that will bring so much developer productivity that they’re actually worth the investment?