Hell Is Other REPLs
Being Mutable and Avoiding Bad Faith Programming
Kicking off the SAVE-LISP-OR-DIE series is a whimsical presentation of a few thoughts about what makes Common Lisp so snazzy, and why those who get hooked stay hooked.
Embrace the Hopelessness
By a somewhat meandering path the ideas I write about today began with this reddit comment. In the comment, user hyperion2011 is responding to a question about Common Lisp vs Clojure vs Racket. The TL;DR of hyperion2011's reply boils down a tidy excerpted quote:
Common Lisp ... embraced the hopelessness, and accepted mutable state as ... an unavoidable part of computation ... and figured out the tools that humans needed in order to work with such systems.
I loved that line: Common Lisp embraced the hopelessness. It cracked me up. But as I literally laughed out loud at the goofiness of the statement, somewhere deep within I also felt the notion to be profoundly true. Common Lisp is an embrace. It is an almost existentialist embrace of the hoplessness of programming complexity.
More on this existentialist angle shortly, but first...
You see, Common Lisp has a long history and was standardized by a committee. Some people interpret this to mean that Common Lisp made many compromises during its design phase, and that as a consequence, the language turned out not as pure or as simple as it could have been.
While there is something to this critique, and while Common Lisp is not always simple , nor pure, I'm not sure I see it as compromised, if you take my meaning.
I instead choose to interpret Common Lisp as a convergence and intermingling of diverse interests, needs, and computational ambitions. The language exists because a number of pioneering people had their voices heard and their interests included into the standard. If embracing the "hopelessness" of a complex set of needs means that the language includes a few fasicnating asymmetries and "warts", then so be it. Common Lisp more than makes up for it with versatility.
If you want object-oriented thinking, Common Lisp has you covered. If you want functional programming, you can do that too. Heck, even though Common Lisp is about as dynamic as a language can be, you can have your type checking cake and eat it too. Want software transactional memory? There's a library for that. Non-deterministic programming? There's a one for that too. Parallel and concurrent programming? Check. Object serialization? Check. Continuations? Check. And on and on.
There's a famous quote from Kent Pitman:
Please don't assume Lisp is only useful for Animation and Graphics, AI, Bioinformatics, B2B and Ecommerce, Data Mining, EDA/Semiconductor applications, Expert Systems, Finance, Intelligent Agents, Knowledge Management, Mechanical CAD, Modeling and Simulation, Natural Language, Optimization, Research, Risk Analysis, Scheduling, Telecom, and Web Authoring just because these are the only things [people] happen to list.
In order to accommodate such a wide variety of styles, techniques, and domains Common Lisp had to be unopinionated. It became unopinionated exactly because so many opinions were synthesized into single a coherent whole. If Common Lisp is complicated, it is only because diverse individual programmers have diverse individual needs.
The indie game developer (and "icon") Jonathan Blow talks about what he calls "big idea languages". These are languages that try to push everything into a single "revolutionary" concept or idea. Some examples include safety in Rust, functional purity in Haskell, immutable data in Clojure, concurrent processes in Erlang, and so on. These "big idea languages" tend to assert a kind of "programming ideology" over reality, and tend to, in my opinion, distance you from the thing you are trying to do, often, they claim, "for your own good".
There is something vaguely totalitarian about these big idea languages. The Rust or Haskell compilers, for example, insist on policing whatever private understanding you might have about the meaning of your code. They are like the neighbor's kids in a totalitarian regime who will report you to the secret police when they think you're not quite as doctrinaire as you ought to be.
"Side effects outside of the IO monad?", the Haskell compiler might be heard to say, "What are you doing? You are a Haskeller and should know better!"
"You want to give HOW many objects a mutable reference to this value?" complains the Rust compiler. "Are you insane? You are a Rustacean, you don't do that kind of thing!"
But of course, you're not always Haskeller or a Rustacean. Indeed, you're a programmer, a creator, a human, and probably much more.
Existentialist philosophy talks about bad faith , a definition of which the opening paragraph on Wikipedia pretty much nails:
[T]he psychological phenomenon whereby individuals act inauthentically, by yielding to the external pressures of society to adopt false values and disown their innate freedom as sentient human beings.
Big idea languages might be thought of as creating those "external pressures" that nudge individuals to program inauthentically. Sometimes this is just what you want. Rust, for example, is so opinionated because, well, Rust is Right™ about safety most of the time. You probably should avoid sharing mutable references among your objects. Encouraging you to avoid something, however, is quite different from banning it outright.
Common Lisp doesn't do that. The language seems designed to encourage you to be you and to shape the language to suit your individual style, problem domains, or interests.
"But," you might protest, "what if I do everything wrong!? I'm no expert after all".
To that I reply: Don't worry! Common Lisp wants you to make mistakes, and that's a good thing.
What's So Snazzy About Interactive Development?
"What do you mean Common Lisp wants me to make mistakes? What an absurd thing to say!"
Well sure. It is a little absurd. I'm sorry - I had to provoke you. The reason I had to provoke you is I'm going to talk about interactive development and, in my experience, nobody who hears about interactive development for the first time really seems to get it.
"Yeah yeah," this hypothetical skeptic might say, "so there's a REPL. So what? Ruby has that. Python has that. Heck, even Haskell has that."
To this paper-thin critic I rebut: "Ahh, that may be, but none of those languages offer true interactive development."
True interactive development is programming by directly working on your program as it runs. As you do, you find yourself recompiling functions, methods, and whole classes, on the fly. Each change you make is immediately and automatically a part of the live program. When you are programming interactively, you are not so much writing code as sculpting and structuring live computer memory.
True interactive development is also about programming in such a way that your program never crashes. Instead, whenever an unhandled condition is raised, the program starts an interactive debugger. While interacting with the debugger you don't lose any program state, not even the ephemera of the call stack, unless you choose to do so.
Because I desperately want to emphasize the concept of interactive development , bear with me while I repeat the above characterization as a numbered list of winning points.
Again, true interactive development is programming such that
- your program never crashes
- because it enters an interactive debugger
- where you never lose program state
- unless you choose to do so
- after a thorough inspection of state
- including the call stack
- and after you have tinkered, recompiled functions, and edited objects
- and after you have tried restarting the computation
- all while the live program is running, possibly remotely.
Too good to be true? Try it out for yourself!
The industrial designer and computer scientist Bret Victor, while giving a talk about Inventing on Principle, listed one of his principles:
Creators need immediate connection to what they are creating.
When you have an immediate connection to your creations, you begin to feel your ideas flowing more freely. When your ideas flow freely your "mistakes" more often flux and morph into "discoveries". This is what I meant when I claimed that Common Lisp wants you to make mistakes. With interactive development, and a fully interactive debugger, a "mistake" isn't really a mistake. Rather, unforeseen conditions are just part of the process. The process is where the magic happens.
After getting hooked on interactive development you might never look back. I know I haven't. This is why I say, to paraphrase Sartre, that Hell is Other REPLs. Programming in other languages is almost painful.
I hope you have enjoyed this first installment of the SAVE-LISP-OR-DIE series. More Common Lisp advocacy is "soon" to follow.
Interactive debugging is part of the Common Lisp standard, so you can always count on it being there. Interactivity is supercharged, however, by tools like SLIME, SLY, and the wire protocol that gives them life, Swank. It is by using something like SLIME that you gain the "true interactivity" that I celebrate somewhat haughtily above.