To the vast majority of programmers, syntax matters. C-style with brackets, or python whitespace, or Ruby do/end, these fit better the brains of the majority of programmers. Perhaps not the majority of HN readers but the majority of corporate devs.
Another example of this is Erlang and Elixir. Elixir adds a couple of features over Erlang, macros and protocols, but Erlang does everything else. What made Elixir take off where Erlang didn't, after decades, is that Elixir has a syntax that people are comfortable with. Erlang has a syntax that will summon Cthulu.
Common Lisp has left brackets like {} and [] to the user (aka developer). It supports "reader macros", where the user can extend/supersede the syntax of s-expressions.
So, specialized tools/libraries/applications can introduce these brackets for their own use. Examples are embedded SQL expressions, notations for Frames (special objects, in kind of a mix of OOP and Logics), grammar terms, etc.
Thus it explicitly supports the idea of "people will introduce new brackets with new meanings".
some time ago I tried Racket, and just no. recently I tried Scala ZIO HTTP, and yes.
Maybe it's the types? Maybe it's the parens. But probably both. I cannot really recall my experience, just that manipulating code was ridiculously clunky. My assumption was that the IDE will manage the parens for me and when I'm moving something somewhere it'll figure out if I messed up the parens.. and ... no, nothing. I had to balance them with hand.
What has happened in reality is that C became really popular and then all the people designing languages they wanted to be popular, rather than to be experimental, or to push boundaries, etc obviously chose a syntax which was familiar with most programmers, ie a syntax like C’s.
Further, one can disprove that the syntax is particularly important by simply pointing to Python which became immensely popular despite a lack of curly braces and even worse with significant white space simply because colleges and bootcamps decided it would be a good language to teach programming to beginners.
I would argue the important part are the blocks in the former two, which sort of gets lost in the homogeny of lisps. Whether a block is marked with curly braces or indents doesn’t matter much - they being dissimilar to a regular expression does. Of course well-formatted lisp code tries to indent as well, but still there is a lot of visual noise there making it harder to visually inspect the code, I would guess.
Of course familiarity with a given way is significantly more important. We pretty much learnt the non-intuitive writing of math, to Chinese people their writing system is the intuitive one, etc.
There are lisp dialects that are very imperative, for example elisp, but they still use S-expressions. Historically they might have been considered “functional” because they have first-class functions and higher-order functions like mapcar, but nowadays practically every modern programming language (except go!) has these.
The thing all lisp dialects have in common is not where they land on the imperative vs. functional spectrum, but rather the fact that the syntax is trivial and so it’s easy to write powerful macros.
Code is communication, and communication needs redundancy for error correction. You can see it in natural languages, and it makes sense to have it in programming languages as well. Using different kinds of syntax for expressing different ideas is an easy way to increase redundancy without making the code more verbose.
Then the AI Winter killed it and people avoided it like the plague.
Today's cruft like you ... Python, JS, whatever ... would not stand a chance in the world of the 1980s on that hardware.
It's amazing how far they were able to bloat up Lisp while continuing to peddle it commercially.
Leaner Lisps running on small systems existed all along, but they would rescue Lisp from the associations brought about by big Lisp.
This is what fascinates me about Unix, they created an OS which works with text and processes, as opposed to binary structures and function calls when computers were hundreds of times slower. Even today the overhead of process creation and serialization for pipes is not negligible, how the hell did they manage to do it in 1970s?
It s weird people prefer reading implicit text.
static char _getch() {
char buf;
if (read(0, &buf, 1)) return buf;
return '\0';
}
would become: (define _getchar ()
(declare static)
(return-type 'char)
(let ((buf (char)))
(if (read 0 (& buf) 1)
buf
"\0")))It also responds to a few parents up "almost all of the innovations in lisp [...] have been absorbed into more popular languages" - pervasive interactivity hasn't even been taken up by some "Lisps", let alone has it been absorbed outside Lisp.
It says nothing about what makes a language easy to learn.
Maybe innate, maybe it's an offshoot of teaching math in an infix style, 1 + 2 vs. + 1 2.
I have no trouble with lisp's parens, i like them. What I never liked though, is that the first item in the list was an operator, a verb lets say, and the rest were the nouns; whereas, you could also have nested lists say of numbers where there were no operators. Never felt right (not that I can think of a better way, not worth adding more parens)
Many things are socially constructed, but not everything.
WRITE(6,28)
READ(5,31) LIMIT
ALIM = LIMIT
5 SUM=0.0
DO 35 ICNT=1,LIMIT
READ(5,32) X
35 SUM = SUM + X
AMEAN = SUM/ALIM
WRITE(6,33) AMEAN
GO TO 5
28 FORMAT(1H1)
31 FORMAT(I3)
32 FORMAT(F5.2)
33 FORMAT(8H MEAN = .F8.2)
END
Most modern programming languages seem to take inspiration from C, which took inspiration from BCPL, and that from Algol. Others took inspiration from Algol directly, like Ada, or Lua. And Python has indentation-based block structure, rather than having blocks of statements delimited by braces or or an "end" keyword.I'd argue a lot of programming language evolution is influenced by the capabilities of our IDEs. When you code in a text editor, the terse syntax of C is great and brings advantages over the verbosity of Pascal, Basic or god forbid Cobol. Once your editor does auto-indentation the braces seem redundant and you get Python. Smart completions from IntelliSense are essential to efficiently writing C#, and now that LSP has brought that to every IDE or smart text editor we have the explosion of popularity of more explicit and more powerful type systems (Typescript, typed Python, Rust). Programming languages are shaped by their environment, but the successful ones far outlive the environment that shaped them.
Backus also shifted away from imperative inspired languages to design FP/FL language (I thought they were contemporaries of BCPL but came 10 years later, later than APL), even though he contributed to FORTRAN directly.
I remember learning JavaScript as a kid (for some class) and trying to get used to the mutable variables, having to mutter to myself "Okay, here, let x be 4. After this line, x is x + 1, which is 5, a new value." From there, eventually thinking things like: "After every loop, x changes to be itself plus 1. So after the loop, x will be its value before the loop plus however many times the loop ran." Things like that. Basically informal Hoare logic without realizing it.
I had almost forgotten, because I then went years before I programmed again, and the language I learned was C, which was probably easier because I was already familiar with while loops and mutable variables.
Maybe it would have been equally intuitive to learn a functional language first. It's probably no more intuitive to mutter that under your breath versus stuff about the type system and equational reasoning.
On the other hand, it seems easier to get beginners interested in programming with an imperative approach. In our assignments in that class using JavaScript, we used libraries to make little games, which imperative programming seems better-suited for.
There are lots of great parts in FP, and for the last ~10-15 years imperative programming languages have made a lot of effort to add them to their syntax. You just need to leave out the more dogmatic parts that make FP popular in academia.
I agree with you otherwise though.
When 6.001 (the introductory class for which SICP was written) was launched, most of the students who took it had never used a computer before. Yes, MIT students. This was around ~1980. And in the first hour of their first class they were already doing symbolic differentiation in scheme.
I think your view of what’s “natural” is a just so story.
People heavily trained in maths can take quickly to languages designed to make programming look like maths, that's hardly a surprise.
I wouldn't base my assumptions about what most people find natural on the experience of MIT students taking 6.001 in 1980.
(Not to mention, 'doing' is doing a lot of heavy lifting in that sentence. I could show you a intricate sentence in French in the first hour of your first French class, but unless you came up with it yourself, are you demonstrating much learning just yet?)
I would certainly be interested in the results of a study that put a simpler interpreter / compiler and a language reference in front of motivated non-programmers, but I strongly suspect that the amount of elegant tail recursion we'll see will be limited (and I'd very much expect there to be a correlation between that and a training in mathematics).
Imho, data comes from experiments, but experiments come from hypotheses, and hypotheses come from experience.
Lisp was once a very popular introductory programming language and students learned it just as easily or easier than any other language.
A better name for "non-OOP" programming is procedural programming, where you organize code in long blocks that go straight down, code duplication is accepted vs jumping all over the place, etc. Honestly underrated. It can be quite easy to understand.
Strictly-evaluated FP is also imperative. The only really different languages are the ones with different evaluation systems or that can do things besides evaluate - people like to say Haskell is the best here but I think it's actually unification languages like Mercury. Maybe even SQL with transactions.
Why do you believe this is anything more than an historical accident?
For example, it wasn't what Alonzo Church gravitated to when he invented the lambda calculus in the 1930s, before any programming languages or indeed general-purpose computers existed.
> 99 Bottles of Beer implemented with a loop is intrinsically going to be easier to read than an implementation with tail recursion
First, you don't need to use explicit tail recursion. See e.g. https://99-bottles-of-beer.net/language-haskell-1070.html
Second, this sounds like unfamiliarity, not anything inherent. Why is it "intrinsically easier to read"? For a tail recursive version, the main tail recursive function would look like this in Haskell:
_99bottles 0 = printVerse 0
_99bottles n = do
printVerse n
_99bottles (n - 1)
In fact, with a bit of experience you might write this as: _99bottles 0 = printVerse 0
_99bottles n = printVerse n >> _99bottles (n - 1)
It's only less easy to read if you're completely unfamiliar with the concepts of pattern matching and recursion. But the same is true of any programming language.Given the above, what's a "for loop" and why would you need one? Sounds complicated and unnatural.
Why is tail recursion better generally? I'm not familiar with FP very much, but it feels like loops more closely resemble the way computers execute them than tail recursion.
Loops, while not bad per se, do have a lot of foot-guns. Loops tend to be used to make all sorts of non-trivial changes to outside state (it's all still in scope), and it can be nightmarish to debug errors that this may produce. Let's say you're looping over chickens in your upcoming Hen Simulator 2024, and you call a function from inside your chicken loop to update the henhouse temperature, which has a check to see if the temperature has gotten too high, which might result in a chicken overheating and passing on into the great farm in the sky, which changes the amount of chickens remaining, but wait, isn't that what you're looping over? Uh oh, your innocuous temperature update has caused a buffer overflow and hard crash. In a rare and possibly hard to reproduce case. Have fun debugging!
Generally, functional programming prefers encapsulated solutions - arguments go in, results come out, nothing else happens - which makes it easier to reason about your code. The most common replacement for loops is something like map, which just applies a lambda to each member of a list. This should make it somewhat harder to achieve the mess above (the other chickens shouldn't be in scope at all, so your temperature update function should complain at compile time).
With tail recursion, you could make a function that takes a list of chickens to update. You pop the first chicken, update it, and recur on a list of the remainder of the chickens. Because this needs to be a function (so you can recur), you have control of the arguments, and can determine what exactly is passed to the next iteration. You can't overflow the buffer, because you're passing a new 'remaining' list every time. This is also where you can get a little clever - you can safely change the list at will. You can remove upcoming chickens, you can reorder them, you can push a new chicken into the list, etc. If a hen lays an egg mid-loop, it can be updated as part of the same loop. Plus you have the same scope safety as you do with map - you can't do anything too messy to the outside state, unless you specifically bring it in as an argument to the function (which is a red flag and your warning that you're doing something messy with state).
What's closer to innate is the Algorithmic Language, Algol for short, the common ancestor of the vast majority of languages in common use (but not, notably, Lisps).
Algol was designed based on observational data of how programmers, who had to somehow turn their ideas into the assembler to run on machines, would write out those ideas. Before it was code, it was pseudocode, and the origins predate electronic computers: pseudocode was used to express algorithms to computers, when that was a profession rather than an object.
That pseudocode could have been anything, because it was just a way of working out what you then had to persuade the machine to do. But it gravitated toward a common vocabulary of control structures, assignment expressions, arithmetic as expressed in PEBCAK style, subroutine calls written like functions, indexing with squared brackets on both sides of an assignment, and so on. I revert to pseudocode frequently when I'm stuck on something, and get a lot of benefit from the practice.
So I do think that what's common in imperative languages captures something which is somewhat innate to the way programmers think about programs. Lisp was also a notation! And it fits the way some people think very well. But not the majority. I have some thoughts about why, which you can deduce an accurate sketch of from what I chose to highlight in the previous paragraph.
I believe you, but do you have a source for this? I can't find papers on how they chose to develop the syntax of Algol in the beginning.
You can see that when observing novices programming (without stack overflow or similar help). They often assumes that it will get done (magically) as soon as they call that function. And their code organization reflects the ad hoc thinking instead of a planned endeavor.
So it’s probably just what people learn first + lack of ‘marketing’ or negative PR (there are no libraries or ecosystem! The thing that least bothered me about CL but people with npm leftpad experience seem bothered by it).
It’s interesting as I worked I almost everything in production; c/c++ (including the MS 90s flavour), Delphi, VB, Perl, PHP, Java, C#, Haskell, F#, Common Lisp, Erlang, TS/JS, Python, Ruby, asm (z80, arm, x86) and I simply have not had a better overal experience than CL. The others are better at some things but a an overal experience, CL just is a pleasure.
All that to say, I completely emphatically agree with the original comment. The world would have been so much better off with Scheme as the language of the web.
Considering the state of the web I do not think this is making the argument you intend.
Though, slightly off topic, but worth mentioning, that both Erlang and Elixir communities support each other very well. For example, now not only elixir is built on top of Erlang, but also Erlang adopts some things from elixir, such as monadic expression `with` from elixir inspired `maybe` in Erlang, or starting OTP27 Erlang is using ExDoc introduced by Elixir to generate documentations.
I'm reasonably confident that all the anecdotes you hear about 10x improvements from switching to Lisp are just programmers learning about functional programming and good design patterns for the first time. But those aren't contingent on using a Lisp, and I'd argue using Lisp brings an enormous amount of cruft and baggage that makes FP seem far more alien and difficult than it needs to be.
I don't think FP by itself is that massive of a win despite what some dubious studies or zealots say, but it's certainly better than enterprise Java. I've read my fair share of horror stories of Haskell in production too.
There's nothing stopping you from writing a massively-scaling e-commerce site in Verilog and running it on an FPGA, but it - uh - probably isn't the soundest course of action.
With all due respect, he is ironically probably the biggest blub programmer.
You have no idea whether this is actually true, or whether people have just fit their brains to what is out there.
The idea that programming language syntax fits people's brains rings untrue for anyone who has watched beginners struggle with it, or remembers being one.
1. Many people try programming.
2. The vast majority of the people who try programming are subject to external forces that guide them to whatever they learn and use.
3. Out of these, a certain fractions stick with it and are found programming in the long run, even working in it.
We could easily conclude (probably quite wrongly) that the popular languages turn people away from programming, except for a few weirdos for whom they click.
Millions have learned javascript because it is the technology of the web. Are they better off?
So many people have been introduced to programming, computer science, and programs through the abstractions provided by javascript, and that sucks
As much as everyone poops on js it is a very forgiving language for embedding.
And yet, when you tell them the reasons, why some other syntax than their Java/PHP/Python syntax would be better, they usually counter with "It's just syntax." or "Every language can achieve the same result, why care so much about syntax." or similar.
> C-style with brackets, or python whitespace, or Ruby do/end, these fit better the brains of the majority of programmers. Perhaps not the majority of HN readers but the majority of corporate devs.
I would need a source for that.
I think most programmer's brains have simply not been exposed to other syntaxes much or much too late, when their brain already calcified the C-style syntax. Or they don't actually care.
It's like the Lisp crowd can't be honest with itself and not realise the serious shortcomings of lisp projects are: maintainability.
Every single lisp project out there, hell this also applies to Haskell, is so radically different because everybody's too busy reinventing its own abstractions and macros (and language extensions in case of Haskell) that you just throw your hands up in the air in dismay.
It's like the Lisp crowd, that can barely attract like-minded people to collaborate on some simple open source projects out there so you get 20 broken JSON parsers, cannot see the link between that issue and industrial lisps.
I love lisps, they are fun, they have their place in the industry when you leverage them to their core strengths.
Try to ask yourself: why does PHP has more quality killer-software than all of lisps combined?
Isn't that the problem with Javascript too, though? Javascript isn't good enough, let's all use jQuery. No, jQuery isn't good enough, let's all use Vue. No, Vue isn't good enough, let's all use Angular. No, Angular isn't good enough, let's all use React. No, React isn't good enough, let's...
Vanilla JS was replaced by jQuery after about a decade. A decade later, React/Vue/Angular (React came first, not last) replaced jQuery
There was a paradigm shift in the kind of websites that people wanted to build. For better or worse, we went from HTML + CSS with some JavaScript (jQuery) to SPAs. It's easier for a team of junior developers to build a SPA with React compared to jQuery.
The abstractions that you're describing changed because the goal changed.
---
There has been plenty of churn around build tools, libraries on top of React/Vue, the paradigms within React/Vue (Hooks, Vue 3 composition API), state management libraries, and the adoption of TypeScript.
Again, this isn't really reinventing abstractions as much as dealing with an evolving language that wasn't designed for the applications we're building today (though it is clearly quite capable!)
Given the circumstances, it's amazing javascript isn't way worse.
Chromium: we’re not doing proper tail calls