Hacker News new | past | comments | ask | show | jobs | submit login
Lisp as the Maxwell’s Equations of Software (2012) (michaelnielsen.org)
117 points by newswasboring on June 30, 2020 | hide | past | favorite | 46 comments



I've said this before, but I've come to think of Lisp and Forth as the left-handed scissors of software: for 10% of the population they're significantly easier to use, to such an extent that it can feel like a revelation. The remaining 90% of the population tries it, finds it harder to use, and doesn't get why others are raving about it.

This would be down to very fundamental differences in thinking and conceptualisation that are difficult or impossible to "just" teach over. It requires those arguing over "ease of use" to recognise that it's not a property of the tool itself, but a function of both the tool and the user and how well the tool fits the particular, individual, user.


If you've spent years compiling your code and running it or changing it and pressing f5 or changing it and hoping hot code reload does the right thing it won't be apparent why a REPL in process would be helpful

If you've spent years editing your code character by character it won't be apparent why being able to use shortcuts or indentation to edit your code as forms would be a new editing default

I'd argue it's possible to teach everyone, but not motivate everyone

I've also seen a few smart people learn the mechanical parts of Clojure and then dismiss it, not understanding the hype because they were just using it like a very imperative language that just happened to be immutable

As a learner you should definitely go through it's rational and Rich Hickey talks first before learning it: https://www.youtube.com/watch?v=rI8tNMsozo0


Things like lisp, forth and also lambda calculus are conceptually clean. In theory one can write anything in them in a conceptually clean way. However, doing so one has also puts programming and/or computers in a straight jacket which one is going to notice as soon as one wants optimal performance. This is simply not possible anymore. The language that exposes the machine as cleanly as possible is clearly C. But this is not conceptually clean anymore because now one can store information in global variables, on the stack and on the heap. I.e., one has three ways to store values. This is not conceptually clean anymore but it is how one is required to think of computers to do much with them in a performant way. Note that the 'simple' languages actually only expose one of these three concepts to the programmer. In Lisp and the lambda calculus only the heap is exposed and in forth only the stack. In practice, though, implementations of these languages often expose more than what they do in theory. At that point they are no longer clean anymore and I kind of fail to see the point in that case.


> The language that exposes the machine as cleanly as possible is clearly C.

It exposes the PDP-11 most cleanly. For modern machines the mismatch between the C abstract machine and the actual hardware has been steadily increasing for decades. It's still close to the hardware compared to almost all other languages, but it is by no means "as cleanly as possible". It's not portable assembly like in the days of yore.


Common lisp exposes two of these three to the programmer, (stack and heap) and some implementations offer globals as an extension.

A lot of myths about lisp being conceptually pure come from poorly taught college classes. If there were one active lisp user for every time I've heard "Lisp? The only thing I remember about that is that it doesn't have loops," then the lisp community would be a large thriving community.


> The language that exposes the machine as cleanly as possible is clearly C

Well .. sort of. I agree with the comment about C being effectively a PDP-11 VM; there are all sorts of features which it doesn't and can't expose. If you look at the Intel instruction set of the present day with all its extensions, how much of that can the compiler really emit? Multithreading is also something that's been very awkwardly retrofitted to C, with all sorts of memory ordering issues that aren't easy to fix. Then there's all the "undefined behaviour" wrangling.

By raw FLOPS the most powerful part of the average computer will be the graphics card, which really isn't suitable for C (or Lisp!) programming at all.


I'm not sure about 10%. Only a very small fraction of programmers has even ever tried Lisp. That 10% may very well be 50% or even higher. Not that I can judge because I have never tried lisp.


I would be one of your 10%. I have been using Lisp languages since the late 1970s and Common Lisp since about 1982 so I think it is just familiarity.

Another reason could be developers who like to compose programs from the bottom up would naturally like repl based development, but there are many languages with great repl experiences.


Many languages have command prompts, but the ones you could actually call a REPL (full featured) are few.


What are the fundamental differences that are "difficult or impossible to 'just' teach over"? Looking at a language like Racket, you have classes and objects, (first class) functions, recursion, "for" iteration, threads, mutability, etc. Aren't these features found in plenty of languages?

Anecdotally, there seems to be a dislike of Lisp just because of the parens-based syntax. Do you think the different syntax counts as being such a fundamental difference for 90% of developers? Is it really so bad to have to write:

(define x 1)

instead of

let x = 1;

That difference makes it "left-handed scissors"?


Racket is not a good example. People learn it as non-interactive. The project now also switches to a conventional syntax without s-expressions.

The big 'problems' are a) interactive use (much of Lisp usage is 'interactivity first'), b) the code as data thing and lack of static feedback (type checks, ...).

The 'code as data' thing is a real hurdle: what is code, what is data, what is transformed code, what are code transformers, ... One needs a mental model to work with code which is making heavy use of meta-linguistic-programming.


> Racket is not a good example. People learn it as non-interactive. The project now also switches to a conventional syntax without s-expressions.

I can understand that people that have achieved "lisp enlightenment" will view Racket this way. With respect to replacing s-expressions, I am hopeful that the original s-expression-based Racket retains prominence even when the Rhombus language is fleshed out. I know I will continue to use s-expressions.

> a) interactive use (much of Lisp usage is 'interactivity first')

To me, having used Clojure (which hopefully counts as an interactive Lisp) and worked in environments like A+ (in the family of APL/J/K), this is an amazing feature and I struggle to see this being such a "fundamental difference ... that is difficult or impossible to 'teach'" (paraphrasing OP).

> b) the code as data thing and lack of static feedback (type checks, ...).

With many people programming in Javascript, Python, and Ruby, the lack of static feedback seems less compelling to me. With "code as data" thing, as a Racket programmer, I rarely if ever think about macros or readers or code transformers. I am sure that can be understood as missing the whole point of lisp, but I have been productive in an environment whose underlying bits can make use of all of that and I can just write code as code and treat data as data in a manner similar to many other languages. Maybe I'm weird, but programming in a manner similar to Java but having code written with s-expressions feels better to me.


> which hopefully counts as an interactive Lisp

Not that much in my view. I don't see Clojure as a particular interactive Lisp, I actually see it only as 'derived from Lisp and others'. 'Lisp' is usually much more interactive, with stateful images (running copies of 'object seas'), mix of interpretation and compilation, interactive error handling, all-layers Lisp (where most of the layers can be inspected/manipulated/changed at runtime, incl. its own implementation) and internal development environments (not externally attached).

For an explanation see: https://news.ycombinator.com/item?id=22326853


For the type checking, Typed Racket attempts to provide a static-typed-lisp.


The syntax criticism is a red herring in my opinion.

At least speaking personally from my opinion I see a lot of people claiming that lisp simple syntax and homoiconicity make it a simple language and in my opinion this misses completely the point of simplicity in a language.

I am not claiming that lisp is complex, just that that argument is faulty.


Would you say that simplicity shouldn't be confused with familiarity?


That for sure, but what I mean is that syntactic simplicity does not translate to simplicity of meaning.

In the case of lisp and its rich macros system the only thing I can see is that anything could cause any kind of effect; I see this as a significant source of complexity in practice. I am sure that library/implementation authors made a fantastic job in refining wonderful abstraction so that this is not a problem in practice.

Julia offers also has rich macros but there the messaging there is different, macros are intended to be used in library code to allow for greater ergonomics. Users of the language to be able to write an idiomatic library as that involve language features that are not necessary in normal scripting.

(I just now realize that I never heard of an Obfuscated Lisp Contest, things could get quite crazy there)


Have you written lisp code? As a Racket programmer, I almost never think about macros, readers, code transformers, or whatever could interfere with what I think should happen in the same way that I would write Java code. To give an example, the only time I thought macros might be useful was when defining class types (contracts) and default values.

In https://github.com/evdubs/interactive-brokers-api/blob/maste..., there is plenty of:

  (define/contract some-class%
    (class/c (init-field [class-member-a integer?])))

  (class* some-class%
    (init-field [class-member-a 0]))
Maybe it would have been nice to have:

  (define/contract/class* some-class%
    (init-field [class-member-a integer? 0]))
All this would be doing is defining a class with both a type (contract) and a default value without having to first define/contract for types (contracts) and later defining class member defaults.

It's great that Julia has messaging that macros should be reserved for libraries. I can't say that I've seen Racket messaging be the same, but I also don't see a proliferation of macros in libraries that I choose to use for projects, either. Especially in such a manner that makes me concerned that, "will this completely screw up my intention?" If you have a different experience, I am curious to hear it.


> As a Racket programmer, I almost never think about macros, readers, code transformers, or whatever could interfere with what I think should happen in the same way that I would write Java code.

This is exactly what I mean, Racket is not a nice language because it has a minimal syntax, it is a nice language because it is powerful, well thought, and its features work well together. Homoiconicity and macros are tools that Racket uses internally to define itself and they are essential to how many of the language features work, but often they are not directly relevant to the (non-library-writing) user.


Looking at the language is the wrong direction; look at the programmers and how they think, and what people self-report about the ease of use of particular features.

Both left and right handed scissors have blades, a pivot, and handles. The only difference is the arrangement. But remember that both this and the analogy with Maxwell's equations are analogies.


> The only difference is the arrangement.

Agreed, and to me, this makes the point that the syntax (arrangement) is a key component to an unwillingness to learn. However, I don't see how this establishes lisp as having, "very fundamental differences in thinking and conceptualisation that are difficult or impossible to 'just' teach over". With left-handed scissors, you have the same concept; you just need to train your left hand to use them. Likewise with syntax. Describing that as being a very fundamental difference that is difficult or impossible to teach over seems odd to me.


I don't think you're getting the example of left-handed scissors; try something like adaptation for red-green colourblindness, where you can't really train around it. Although that's physically based, which is going to be a distraction in the analogy; try the reported observation that some dyslexic people find Comic Sans easier to read.

Or trousers. One size does not fit all.

Or the people discussing the intuitive representation of Maxwells' equations themselves: https://news.ycombinator.com/item?id=23700295 - not everybody likes the equations, some people like pictures for a better understanding, but the equations-first people may not understand that.


Interestingly, the 4 equations shown in the article are due to Oliver Heaviside [1]. Maxwell came up with 20 equations, which captured electromagnetism. But it was... rather unwieldy. Heaviside reformulated that into 4 equations. Basically, Maxwell managed the breakthrough and Heaviside cleaned up after him and made it palatable.

Why is that interesting? Because it raises the question: is the analogy made in the article more akin to Maxwell (breakthrough, brilliant but close to unusable), or is it more akin to Heaviside (taking known concepts and making them usable in practice)?

[1] https://en.m.wikipedia.org/wiki/Oliver_Heaviside


A somewhat related idea: the discovery that light is an electromagnetic wave falls right out of Maxwell's equations in a vacuum. Simply rearranging terms yields two wave equations in 3D - one for the electric field, and one for magnetic. The term in both wave equations representing the speed of propagation (i.e. the speed of light) is a constant. It depends only on the physical properties of the vacuum, which never change as far as we know. That means an observer traveling at any constant velocity with respect to light will always measure the speed of light to be the same. Maxwell died in 1879 at the age of 48. It is not only plausible but likely that he would have come up with special relativity had he lived a bit longer, and also likely that it would have been years before Einstein published his 1905 paper on the subject.

So in addition to the Stigler's law [1] situation with Heaviside, the formulation of special relativity may have been more or less inevitable after all of Maxwell's laws were put in the same room together. Not that Einstein wasn't a once-in-a-generation talent, but the reality of how progress is made in science is often glossed over in favor of assigning glory to particular individuals.

Even Maxwell's laws taken individually are named after different people, but together they all belong to Maxwell. I get that this practice is intended to be a convenient way to put a label on an abstract concept rather than a way to write history, but I still think it has an effect on how we think and talk about the history of science.

https://en.wikipedia.org/wiki/Stigler%27s_law_of_eponymy



Yay! A bunch of people that haven't programmed (much?) in Lisp t-ing off with a few that have, dragging out all the old tired arguments (LISP?!)!


Most recently discussed about a month ago in the thread

“Maxwell's equations of software” examined (2008)

https://news.ycombinator.com/item?id=23321955


FYI CL might have more libraries than you think: https://github.com/CodyReichert/awesome-cl just saying.


Maybe it's just me but it seems silly to compare equations to Maxwell's equations just because Maxwell (or his peers) originally used the same number of equations. In the modern mathematical framework of differential geometry and tensor calculus, Maxwell's equations can be reduced to only a single equation. See e.g. [1].

Also, Maxwell's equations have the important property that they are linear. How would that work in the Lisp analogy?

https://en.wikipedia.org/wiki/Maxwell%27s_equations#Alternat...


LISP has an insidious parenthetical notation, and is well known to be a "write only" language, where nobody but the author can understand large programs written in LISP. It is avoided commercially for this reason.


Sorry, but I strongly disagree. It's possible to write unreadable code in any language, and Lisp is readable once you've spent a basic amount of time with the language to get to know the forms and when your code is indented properly - see my blogpost at https://nl.movim.eu/?blog/phoe%40movim.eu/cd3577f6-fb1d-45f5... for that last one.

Also, LISP is a language that's been dead since 1970s. Did you perhaps mean Lisp?


That's why GNU Emacs is written in C and Cobol.

More seriously, I have seen a lot of badly written Lisp/Scheme, as much as in any other language. Lisp DOES scale quite well, if the code is well-designed.


Every language scales well if the code is well-designed, almost by definition. The more salient question is how well large codebases written by mediocre programmers can be maintained by other mediocre programmers, because that's what any long-lived codebase will have to deal with.


By the way, I'm well aware that there are pieces of GNU Emacs that are mazes of twisty passages, understood by very few people. That's to be expected of a decades-old program that has perhaps not experienced enough refactoring.


Or embraced by those who know how to cook this: it is very easy to spot adequate LISP coder and filter self-serving equilibrists. (I am full Clojure for many years)


People who cannot read Lisp or find it difficult to scan Lisp code with their eyes, or those who try Lisp for the first time, usually approach it the same way as they've probably done many times with other programming languages. They stare at it and try to make sense of the code.

However, that's not how Lispers usually read Lisp code. Lispers would connect to a REPL and start "dissecting" the code evaluating it piece by piece. Lisp is unlike many other PLs where you usually have to follow "write -> save -> lint|compile -> run -> check the results" routine, boils down to "write -> eval -> check the results".

You can even say that coding in Lisp is dealing with a "living," "breathing" program, whereas in other PLs - your program is "dead" and comes to life only after it's compiled and ran.

> It is avoided commercially for this reason.

Lisp is being used today by Apple, Walmart, Cisco, Amazon, Netflix, and even NASA and many other companies. Huge banks and FinTech startups around the world actively using it. Clojure is the most popular programming language in its category of PLs with strong FP emphasis. It is more popular than Haskell, OCaml, Elm, Elixir, Purescript, and recently surpassed Scala.

Not a single guru, CS veteran or renowned programmer ever complained about Lisp being hard to read, because they understand - if they know anything about programming at all, publicly saying shit like that would raise many eyebrows.

But every single time someone mentions Lisp on HN, there will be at least one dyslisplexic, who has never developed enough patience or inner curiosity to learn it.


And That's A Good Thing

If Lisp were mainstream I'd probably see that as a red flag


What about stuff like clojure?


Those are pretty strong words. I wouldn't use Lisp for a project I expect to scale, but I can still appreciate it for its merits, including what it represents.


I agree, however, Greenspun's tenth rule:

Any sufficiently complicated C or Fortran program contains an ad hoc, informally-specified, bug-ridden, slow implementation of half of Common Lisp.

What are these amazing 'commercial' programs?

And why are they so large? And, do they need to be so large?

Does the entire team understand all the inner workings of the commercial program?

The Lisp eval function is easier to read, understand, and modify than to start over and rewrite it from scratch.

( As for those brackets, well ... https://xkcd.com/859/ https://xkcd.com/541/ https://xkcd.com/297/


> Any sufficiently complicated C or Fortran program contains an ad hoc, informally-specified, bug-ridden, slow implementation of half of Common Lisp.

Sure, because C and Fortran are extremely limited languages - no first-class functions, no sum types, pitiful error handling. In a world where language design was extremely-primitive, user-defined macros made sense. Once languages that offered standard ways of doing the constructs that most programs need were invented (i.e. ML in 1973), there were better alternatives.

> The Lisp eval function is easier to read, understand, and modify than to start over and rewrite it from scratch.

That's not actually true though. The definition in the link uses a bunch of obscure lisp-specific terminology and doesn't really gain anything from doing so. It genuinely is easier to reimplement it from scratch (perhaps with the help of a more neutral reference) than to understand the Lisp version.


This keeps popping up, and it is no more persuasive the tenth time than the first.

Universal Computation was demonstrated a long time before Lisp. Any Turing-complete language is universal, Lisp included. Church's lambda calculus is included, and Lisp is a notation for lambda calculus. So, you can write a Lisp interpreter in Lisp, and it's pretty compact because (surprise!) a Lisp Interpreter has a lot of support for the stuff you need to do to Interpret Lisp. There's nothing deeply meaningful about it beyond what you start out with from Church.

It is much more impressive, and subtly meaningful, that a NAND gate is universal. You can build a machine that runs Lisp from nothing but NAND gates, and people have. There have even been commercially successful discrete-transistor mainframes made of practically nothing but NOR gates (plus core memory), which are identically universal.

Transistors are therefore universal. But because their operation is described in analog terms, they don't quite fit the mathematical formalism. The universe doesn't care about that, so it allows us to build up Universal Computation out of analog transistors. The more transistors you put in, the more computation you can do in a second.


You're speaking of functional completeness of boolean operators which I think is quite unrelated to theories of computation. For computation you need some notion of state, and while a circuit made of logic gates may possess some notion of state, a logic gate in itself does not. As for transistors being universal (whatever that means) if this is true then vacuum tubes, relays, pneumatic valves, etcetera are also universal.


Do you understand that you can construct flip-flops from NAND gates? You can construct registers from flip-flops.


Yes.


I think transistors are great for electrical side of computing.

Lambda calculus and lisp for the abstract model.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: