Blub Paradox (2014)
wiki.c2.com
wiki.c2.com
The original essay is one of the few texts that stuck with me. I read it not as an affirmation that some languages are strictly superior to others (in particular lisp) but as a push to try new paradigms and ideas even if I cannot see, from my current point of view, how they could improve on my current practice. It is one of the reasons that had me trying Rust this year.
I think the killer use problem is more an explanation then anything else. Java also showed a good marketing, well funded campaign does wonders.
Look at Javascript, it is not a good enough language to have developed without Netscape putting it into the browser. It had the killer application that you were basically forced to use. I love prototype-based inheritance but even NewtonScript was a better language.
1) Steve Job's videos for NeXT explaining objects were pretty amazing
It could have dragged iOS popularity with it.
Pushing, promoting or forcing something is not enough for it to become popular and widely used.
Ask Sun about Java and their promotional campaign. When a teacher at a community college in North Dakota gets a call and sent a book to help him decide to teach their language, you can be very effective at pushing.
But the people most likely to develop iOS apps at the start were likely already familiar with Objective-C, I think, due to it being the favoured app platform on OSX, no?
On a side note, I really do believe the people behind Swift hate Objective-C and a lot of the old complaints are "solved" by Swift.
Probably. I wonder if we wouldn't have ended up with more Xamarins instead.
This one sentence to me is an indication that the author has completely missed the point. Templates and macros do the "same thing" only in the sense that all languages do the "same thing" at some level of abstraction, that is, model Turing machines. What matters is not what languages "do" when they are compiled, but in how they bridge the "impedance mismatch" between TM's and human brains. And in that regard, templates and macros are about as different as two language constructs can possibly be.
If you want to actually say something, don't do it like this.
Anyway, I don't think it's right mental picture, and thus context for paradox. Language power, I believe, cannot be defined with one dimension. What is more powerful, Java or SQL? Will SQL benefit from encapsulation or will Java benefit from window functions?
PG: - It is more efficient to write software in Lisp. Others: - Nah, it doesn't matter.
But no empirical evidence has ever been presented...
Start there, some more links from there to details on the study and follow-ups.
https://web.archive.org/web/2017/https://www.plat-forms.org/
It is hard enough to get study participants for the common programming languages, I deem it nigh impossible for the obscure ones.
>And if Lisp is so great, why doesn't everyone use it?
Suggesting programmers in a blub language don't realise it's power. I get the impression in the 18 or so years subsequently that the main reason for the low uptake of Lisp is it's power enables people to write clever code that other programmers have quite a job to understand while languages like Python let you do much the same stuff while being easier to read.
So it's not that people don't realise the power so much as they don't want it.
Python and Ruby (and Perl before them) got big not just as “easy” languages, but as languages that get the job done quickly thanks to the massive amount of libraries.
And while Java, C, and C++ may not be the most elegant of languages, the amount of tools to inspect the code, find bugs, prove properties about the code, safely refactor the code, and test it from the unit level to the level of an ecosystem is what keeps some programmers interested in those languages.
Lisp looks like complete gobbledygook to me. I'd love to be able to understand it and write it, but the syntax doesn't look like it's meant to be human readable. It's almost as much work as reading assembly.
I should probably give it another try, though.
I'm honestly not sure that that's true. I would bet money that someone who primarily works in Lisp would still have an easier time sitting down and reading Python code than someone who works in Python sitting down and reading Lisp, because I think it might truly be inherently easier to understand.
There is intrinsic meaning in:
x = 5
let x = 5
etc., insofar as these translate character for character, word for word, into human language, whereas this simply doesn't: (let (x 5))"No programming language is more or less natural than Lisp. It is merely a matter of familiarity and the way most programmers were taught."
One obvious problem is that familiarity does matter. Even ignoring all other programming languages, there is a barrier to climb for the vast numbers of beginner programmers who know English and common math notation like f(x) instead of (f x) and x < y instead of (< x y). I've also seen (- x y) be confusing as one incorrect way to read it as English in your head is "subtract x from y".
The other is that for some things, it really does seem like infix notation is inherently preferred by humans. Is there a natural language where conjunctions don't go between terms, i.e. one says "und x y z" instead of "x und y und z"?
But I do want to read some functions like conjunctions, and in Lisp, I am forced to call `and`, `or`, `<`, `/`, and pals the same way as log, print, etc. I just don't think I'll ever find:
(and (not (is-blocked door))
(< (height player) (height door))
(or (is-unlocked door)
(has-key player door)))
as readable as: not is_blocked(door)
and height(player) < height(door)
and (is_unlocked(door) or has_key(player, door))
Edit: to expand, I don't think it's a fundamental flaw of Lisp. There could easily be some syntax sugar that lets you specify precedence rules for functions and write them infix, just like Haskell lets you write either `(+) 1 2` or `1 + 2`. It would de-sugar to the same sexpr.I know this can be done as a macro, in fact I did it for elisp several years ago ( https://github.com/rspeele/infix.el ) but it is really just a toy for personal use. If an infix operator system came out of the box and its use was encouraged for certain common operators, I think that would be a net positive, but most Lispers disagree with me there (if I had to guess).
and --.-- not (is-blocked door)
:-- < (height player) (height door)
`-- or --.---(is-unlocked door)
`--(has-key player door)
The other one looks like a bit of a lorem ipsum paragraph to me.Naively I would guess that I would do best on infix exprs and you would do best on s-exprs.
But I could imagine other results, like s-exprs giving me a speed disadvantage but an accuracy advantage (either because I had to read them more carefully or because they eliminate reliance on precedence rules). Or there may be other quirks, like maybe S-exprs read better for larger, multiline conditions while infix reads better on short one-liners with <= 3 operations.
It reeks of effort so I'm not going to make such a site, but I do wonder what the results would be.
For me I find that s-expressions are the easiest thing to comprehend. Every other language is loaded with syntax and with lisp it's just mainly just (function args).
I wanted to test your argument and I have never looked at common lisp before, I just skimmed some source code from this project https://github.com/stumpwm/stumpwm and it's extremely easy for me to parse.
Comparing this to new variants of Javasscript that seem to come out biannually with some new syntax, I have no idea what's going on and have to look up references on syntax to begin to understand anything.
I guess at the end it's subjective, but lisps have ruined other languages for me.
No, there isn't. That just happens to be closer to the notation you were taught in elementary school, so you're more familiar with it. If you were taught s-expressions in elementary school (and this would actually be a really good idea) you would say the exact opposite. In fact, once you understand s-expressions, all other notations seem awkward and arbitrary. That's actually the reason that Lisp persists, because it's a kind of natural "local maximum" in notational idea space. It just happens to be one that very few people ever reach because of the damage done to them in their primary school education.
I think Python code looks closer to this pseudocode than Lisp does. Once you become an experienced Lisp programmer, it probably takes little effort to convert that mental pseudocode into Lisp, but I believe it does take intrinsically less effort in Python.
In sum, using pseudocode is just temporarily liberating yourself from pesky syntax adherence in prder to better quickly iterate over an idea, but in that discussion you always have the target in mind from the onset.
Might that be Clojure, rather than Lisp, that you're looking at, without an extensive functional programming background?
Some classic Lisp code, too, will look like gobbledygook if you've never had a tutorial, due to unfamiliar symbols. If you don't know what is a "cons cell" or the functions car and cdr, for instance.
Surely, you can guess what this is doing:
(let ((x 3) (y 4))
(print (+ x y)))Only if you got far enough in your background/education to understand prefix notation. Most people aren't there.
f(x,y) is a prefix notation and so are command languages: "command arg ...". Statements like "while (x) { y }" or "return z" are also a prefix notation, as well as declarations like "struct foo" or "int x, y".
Those short, cryptic names are definitely part of it. It's unfamiliar syntax, and overdose of brackets for added confusion, and in the middle of it all, words that mean nothing to me.
Stuff like cout, puts or the printf syntax also take some getting used to if they're new to you, but you get more context with them.
And yes, I can guess what your let and print do, but still, the excessive brackets, unusual assignments and prefix notation, give the whole thing a lot of noise.
The good news is that parentheses disambiguate everything; if we remove some of them, we have to introduce hidden rules that determine what is a child or sibling of what.
let can have fewer parentheses, and some dialects (like Arc, in which HN is written) have tried that:
(let (x 1 y 2) ...)
I've seen variations on a let1 operator which just binds one variable: (let1 x 1 ...)
In Common Lisp, the inner parentheses are there because the initializing values are optional: (let (x y (z 3) w) ...) gives us four variables. x, y and w are initialized to nil; z to 3.Parentheses are needed around the variables so that they form a single argument position in the let syntax. Without that, we don't know where the body of the let begins. So that is to say, if we make it (let (x 1) (y 2) (print x)), how do we know (y 2) isn't supposed to be an invocation of function y on argument 2 rather than another variable binding.
The parentheses are great for code layout; there are formatting rules to make all complex expressions look consistently good. The parentheses also facilitate good editor support.
In my own dialect of Lisp, TXR Lisp, to demonstrate/test the foreign function capabilities, I took a C code sample from MSDN (a minimal Win32 program to create a Window) and translated it, almost expression for expression:
(deffi-cb wndproc-fn LRESULT (HWND UINT LPARAM WPARAM))
(defun WindowProc (hwnd uMsg wParam lParam)
(caseql* uMsg
(WM_DESTROY
(PostQuitMessage 0)
0)
(WM_PAINT
(let* ((ps (new PAINTSTRUCT))
(hdc (BeginPaint hwnd ps)))
(FillRect hdc ps.rcPaint (cptr-int (succ COLOR_WINDOW) 'HBRUSH))
(EndPaint hwnd ps)
0))
(t (DefWindowProc hwnd uMsg wParam lParam))))
(let* ((hInstance (GetModuleHandle nil))
(wc (new WNDCLASS
lpfnWndProc [wndproc-fn WindowProc]
hInstance hInstance
lpszClassName "Sample Window Class")))
(RegisterClass wc)
(let ((hwnd (CreateWindowEx 0 wc.lpszClassName "Learn to Program Windows"
WS_OVERLAPPEDWINDOW
CW_USEDEFAULT CW_USEDEFAULT
CW_USEDEFAULT CW_USEDEFAULT
NULL NULL hInstance NULL)))
(unless (equal hwnd NULL)
(ShowWindow hwnd SW_SHOWDEFAULT)
(let ((msg (new MSG)))
(while (GetMessage msg NULL 0 0)
(TranslateMessage msg)
(DispatchMessage msg))))))
TXR Lisp benefits from featuring the referencing dot operator a.b.c.d for accessing structure fields (which is a syntactic sugar for a (qref a b c d) form). This is much more important than having infix math, because the notation supports the expressivity of object-oriented programming, which strikes at the heart of larger scale program organization.The full program with all the boilerplate to define Win32 types and bind to the foreign functions is here: http://nongnu.org/txr/rosetta-solutions-main.html#Window%20c...
The original C is here: https://docs.microsoft.com/en-us/windows/desktop/learnwin32/...
Suppose you wanted a really efficient numerical library written in Common Lisp, you could do it. But you wouldn't want to use map/reduce/remove/etc which are all linear time on the sequences they operate on (which can be lists or vectors). So clean looking code turns out to have poor performance because you have multiple O(n) operations, introducing large constants or if they're nested turns them into quadratic or worse operations. For instance, if we wanted to compute dot product we could, in CL, do:
(defun dot-product (v1 v2)
(reduce #'+ (mapcar #'* v1 v2)))
That's O(n) which is the "best" we can do for dot product. But it actually contains two iterations. In C, you'd have: double dotproduct = 0.0;
for(int i = 0; i < vector_length; i++) {
dotproduct += v1[i]*v2[i];
}
While for this small example the difference in performance is minor, in the case of a library making many calls to these functions that double loop would add up. And for more complex operations you're introducing higher constants (or worse) by using the clean looking Common Lisp code.Enter something like Series
Series: https://www.cs.cmu.edu/Groups/AI/html/cltl/clm/node347.html
See here for a demo of how it tranforms functional code to imperative: https://malisper.me/loops-in-lisp-part-4-series/
So you still (as the user) get the illusion of working in a functional language, but under-the-hood it happily transforms itself to an imperative form. And if you wanted to make something like Series, you could present that functional form to your users and hide the imperative, optimized form.
Main takeaway: Common Lisp has high-level functional aspects, OO aspects, but also low-level systems-oriented and imperative aspects. You get to pick and choose which level you want to operate on based on your domain. And with the macro language you can present clean, functional interfaces to the end-user while using the lower-level features to optimize things for them.
Steve Losh has a good [0] starting article on Common Lisp as well, which might be a better starting point than the books I mentioned, and could lead to them.
[0] http://stevelosh.com/blog/2018/08/a-road-to-common-lisp/
Effective lisp organizations will generally use a small handful of general-purpose macros throughout their codebases - but at that point you might as well standardise that handful of macros as a language in its own right and you'd have better tool support, better interoperability between libraries (since you don't end up with different libraries choosing slightly different macros for the same thing) and so on.
Also lisp is a blub language compared to a language with a modern static type system. It's not that people don't want lisp-like expressiveness, but it turns out other languages can offer more without compromising on that.
Not convinced; many modern statically-typed languages offer macros or equivalents (and also offer alternative ways to achieve most or all of the headline use cases). Metaprogramming in those languages is substantially more code-level work than in lisp, certainly, but even lisp users tend to treat macros as something expensive (because even though custom macros are cheap in terms of code cost, they're expensive in terms of reader (and tool) comprehension): the standard advice is not to write a macro unless there is no alternative, and using lots of specific custom macros in each code area is regarded as poor style. So in practice developers in modern static languages use macros in much the same way as lisp users.
> And Common Lisp's type system is much less of a blub in that regard.
Disagree; if you don't have types that are reliably accurate and enforced at compile-time then you gain very few of the advantages, counterintuitive as it is.
And (compile-time) macros are one thing, but what happens when you need metaprogramming at runtime (for example dynamic code generation)? With the Common Lisp compiler being always present one can easily generate and compile arbitrary code at any point in time. Here's simple real world example (shameless plug):
I'd agree that stage polymorphism is rarely used or appreciated in languages that don't have it (though I see no reason it should be incompatible with typing; as far as I can see your example is much the same as e.g. Scala's LMS; as with macros of course it's substantially more cumbersome to do this in a language with much more syntax than lisp[1]). I'm not sure that it qualifies as a "blub paradox" since to my mind it's a performance optimization rather than something that fundamentally changes language expressiveness - having AST-like datastructures that are gradually transformed/interpreted at runtime is very much a standard technique in ML-family languages, wider adoption of stage polymorphism would mostly lead to programming the same way and having it run faster, I think.
[1] I'm not convinced that it would be impossible to make a language with ML-style types but a very lightweight syntax that made metaprogramming easier. Personally even looking at e.g. Haskell I find myself wishing for a more visible syntax more often than I'm wishing for better interpreter tower performance, shrug.
Golang was designed around the philosophy of blub.
But in another problem domain, Lisp is terrible. I wouldn't want to program an embedded system with memory-mapped I/O in Lisp. I wouldn't want to do high-performance computing, where I had to get the memory alignment right to get the best performance.
Lisp might be like a Formula 1 racer. If you ever drive one, it will probably revolutionize your idea of what a car can be. Just don't try to drive it on a four-wheel drive road - a Jeep will absolutely dominate it there. For that matter, a Formula 1 car is not even a very good car for commuting to work - not street legal, lousy gas mileage, too high maintenance.
And this is what's wrong with the Blub Paradox idea. Power? Power for what? A language has a certain power for a particular kind of problem, and a different power for a different kind of problem. Pick the language that is the most powerful for your problem, not the language that is "the most powerful".