148 comments

[ 0.27 ms ] story [ 74.3 ms ] thread
Relevant. Guy Steele, Growing a Language https://youtu.be/lw6TaiXzHAE
(comment deleted)
The text version: https://www.cs.virginia.edu/~evans/cs655/readings/steele.pdf

I feel though that the article hasn't aged well. The talk was given in the age where Java didn't yet have generic types (he mentions this alongside with operator overloading). As to how Java successfully grew since then, I'm not very sure. Not all faults of Java are his, but like many others, he only seems to have a (very good) idea about the general direction, not the path itself.

> the article hasn't aged well

He mentioned generics* is needed to "grow the language". Then Java added it, making the language more expressive. This talk aged like fine wine if you ask me.

* and operator overloading, and value types. The former I'm happy using in Kotlin. The latter we will have to wait. But for code that is not performance-sensitive, data classes/records work just fine.

As a mild counterpoint, I'm anything but upset with the recent additions to Java. It feels like it's becoming a simpler language, as in many cases, it takes much less effort to tell the compiler what I want to say. The conceptual tools Java provided at its inception just weren't enough to express things well.

In a similar way, even though the lambda calculus, SKI combinator calculus, or and MOV-only assembly are minimal in very real ways, the cognitive burden of expressing yourself in these languages is very, very high!

To pick a friendlier example, I'm a big fan of Lua. Lua is deeply minimalist, and from what I know, its developers try hard to keep its feature set orthogonal (and it has my favorite language feature, coroutines!). It's a great fit for certain domains. Nonetheless, it's not a great fit for most domains; for instance, I never got the sense that it could scale much beyond one-person projects.

General-purpose computing languages are in a bit of a catch-22. People need to do such wildly varying things with them, that in order to be minimalist, they must necessarily put some distance between the features they provide and the use-cases they are applied to. Conversely, to be expressive, they must minimize the amount of encoding required to take ideas in the problem domain and represent them in the language.

A domain-specific language (with high expressivity for the problem domain) invariably tends to take on general-purpose features, as users' needs expand and they push the language to its limits. A general-purpose language (with low expressivity for most problem domains) tends to take on more and more high-level features, to help bridge the gap between the language and the domains it is most often put to use in.

I think a polyglot environment makes a lot of sense. Use high-expressivity DSLs to serve your business logic, and high-generality languages to serve those DSLs. In some ways, that's the ideal of Lisp: your DSLs are indistinguishable from the host language itself, so the experience is seamless. But folks don't seem keen on polyglot environments, Lisp or not. So we're at an impasse.

> It feels like it's becoming a simpler language, as in many cases, it takes much less effort to tell the compiler what I want to say.

I do not think this shows that Java is a simpler language, if anything it shows that it has more features (ie. being more complex as a language) that make it easier to match what you want to do.

Java might be simpler (easier) to use, but that doesn't make it simpler as a language itself.

For a more extreme example take C vs C++ - C is certainly simpler as a language than C++ and nobody would ever claim that C++ has any relation to simplicity as a language whatsoever, but a lot of things are much simpler to do in C++ than they are in C because of all the additional features C++ provides.

So its semantics over the meaning of simple - some mean simple as in not meany features, the other means simple to get things done with. Many people call C a hard language because it's quite hard to get stuff done with it, while others call it simple because the feature set is small and writing compilers for it is relatively easy.
IMHO the reason why it's "hard" to get stuff done in C (which I don't necessarily agree with), is mainly the bare bones stdlib, less the language. Eg C programming is much more library-driven than language driven. It sucks if the used libraries suck (such as most of the C stdlib), but it is great when using libraries with a modern API design (eg the opposite of POSIX).
From the perspective of most high level programming - say web development, C has no advantages whatsoever.
Hmm, every piece of web infrastructure I just thought of (every browser, nginx, FreeBSD, your HL language runtime, nghttp2, curl, SSL) is written in C.

Sure, you may not be touching those during your web development, but that’s because you don’t know how to have fun and outsourced it all.

web infrastructure is not the same as web development...infrastructure is what allows developers and users to build and use the apps. C does make sense for these things. Stuff like Curl I think is written in C more due to historical reasons (Unix is written in C so all of its apps are in C), I don't see any major con if it was written in Python (which is a big high level wrapper over C in the end...) - it would be a tad slower and a bit more memory yeah, but in todays world I don't know how much of a difference it makes.
> I don't see any major con if it was written in Python

> it would be a tad slower

a tad? I'll use C++ as an example, but the last time I profiled a hash map in C++ vs C#, the C++ lookup took 1 nanosecond[0] and the C# version took 1ms (1000x slower) (I don't have these metrics anymore but I believe it was one ms or slower, and Google seems to confirm that).

Now, if the difference between C++, a machine compiled language, and C#, a runtime compiled language, is 1000x for a very common operation, how much slower will an interpreted language that is running at an even higher level be??

The reason 90% of our apps are such slow trash these days is because programmers think that there is no difference in a couple milliseconds when it is a huge difference if that couple milliseconds is being run thousands of times through hundreds of thousands of lines of code! Take a look at how fast the Jai compiler is compared to a modern C++ compiler. Or even a C compiler vs a C++ compiler. It's night and day because of how much cruft slowly gets added on.

So I find your claim that most of the Unix utilities are only written in C because that was mainstream in the day highly dubious. If anything, they would be written in something like Rust today, but definitely not JS or Python.

Edit: I would also like to point out how much of a pain it would be to write low level network code, like what's necessary in curl, in a language like Python. When you're writing low level net code you want as little interference between the bytes and the programming language. Because if your bytes have some hidden padding, or the endianess is swapped, or a data structure is aligned a byte off, it means the difference between between correct transfer over the net, and sending garbage over the net. Python is not conducive at all to understanding how an object will be transformed into q series of bytes, whereas C is almost a direct translation from a struct to a series of bytes.

[0]: https://gist.github.com/ambrosiogabe/66a6e2fdc77e6a600e570f4...

I might be wrong about curl - I use it just as a tool to make http calls but it might be more complicated than that - but why can't you build curl with python's http libraries? I guess there are use cases where you have to manipulate responses on the byte level?
(comment deleted)
> the C++ lookup took 1 nanosecond[0] and the C# version took 1ms (1000x slower)

The ratio between 1 ns and 1 ms is a million.

The link talks about nano- vs micro-seconds, so probably just a typo.
I just re-ran the benchmarks and I was mis-remembering those statistics quite a bit. It looks like C# takes 124.5 nanoseconds on average for a hash set lookup. This was benchmarked using BenchmarkDotNet :)
C# definitely does not take 1ms to look up something in a hash map. Perhaps 1us (microsecond) in a horrible example if the virtual hash code has a slow algorithm and doesn't cache the result.

Regardless, the benefit of not using C++ is and always has been: Not having to use C++.

Yep you are correct, I was mis-remembering quite a bit. I just re-ran the tests using BenchmarkDotNet and C# takes 124 nanoseconds on average, which is still 100x slower and a considerable difference, but not orders of magnitude difference :)

C# benchmarks on my machine. FastFindRecipe just does a hash set lookup, it looks like this:

  private static readonly HashSet<CraftingRecipe> FastList = new HashSet<CraftingRecipe>(new CraftingComparer());
  public static CraftingRecipe? FastFind(CraftingRecipe r) => FastList.TryGetValue(r, out var res) ? res : null;

  |         Method |     Mean |   Error |  StdDev |      Min |      Max |
  |--------------- |---------:|--------:|--------:|---------:|---------:|
  |     FindRecipe | 315.2 ns | 1.17 ns | 1.09 ns | 313.6 ns | 317.4 ns |
  | FastFindRecipe | 124.5 ns | 0.23 ns | 0.21 ns | 124.1 ns | 124.9 ns |
> , which is still 100x slower and a considerable difference, but not orders of magnitude difference :)

Two orders of magnitude.

I see this sort of argument for low level, system languages all the time. What you're not considering is that runtime isn't the constraint that we're hitting anymore, it's developer time. There's no doubt that a good python dev is much more productive than a good c dev, both in terms of raw output, and in terms of writing code with fewer bugs. 'Software is eating the world' is overused, but broadly true, and the reason we devs are so well paid is because the demand so vastly outstrips the supply of productive devs.
My argument here is about developer time though. Python development for low level systems programs would be astronomically slower than using a language like C, C++ or Rust which are built for these things. Use the proper tool for the proper job.

If you have to have multiple structures that need to be precisely laid out in binary, python is the wrong tool for the job. Delivering content using Tcp or Udp packets requires very explicit binary definitions of the data your sending. Sure you can use a wrapper on top of a library that deals with the low level stuff, and that's what Python is all about.

Use the proper tools for the proper jobs. If you're using C to write one off scripts or simple data manipulation like analyzing csv contents or something, then you should probably use a higher level language instead. If though, like curl, you have to be very explicit about the binary description of objects to accurately send and receive those objects between a network, then use a low level language.

> runtime isn't the constraint that we're hitting anymore

If you're sending data over the network you're absolutely hitting this constraint. Reading some data from RAM is on the order of like nanoseconds, sending data over the network is on the order of hundreds of milliseconds depending on the size and location you're sending that data. When it comes to sending data over the network, literally every byte does matter.

My point was more broad. We're not short on compute power in most cases, we're short on dev time. If you're talking low level microcontroller stuff, or bit banging, sure, do it in C if you like. In general though, I'd rather devs using a higher level language that gives them tools to deal with things like memory management and concurrency. The slowest code is the code that was never written, not the code that takes an extra 100ms because it was done in python instead of C.
> I see this sort of argument for low level, system languages all the time.

It seems like you were advocating using a higher level language for low level, systems programs. If that's the case I disagree. My original argument was about Unix utilities. Unix utilities contain everything from "list the files in a directory" to "compile this C program". The original poster was talking about how Curl would probably be written in Python if it was written today. This is just incorrect. From the author of curl[0]:

>> If I would start the project today, would I’ve picked another language? Maybe. Maybe not. If memory safety and related issues was the primary concern I had, then sure. But as I’ve mentioned above there are several others concerns too so it would really depend on my priorities.

> If you're talking low level microcontroller stuff, or bit banging, sure, do it in C if you like.

This is the argument I'm making. Unix utilities are usually dealing with low level bit banging stuff. Unix utilities are also meant to be extremely portable. C is the glue that holds all programming languages together, whether we like that or not. So programming a library in C automatically ensures that the library can be used in practically any higher level language.

[0]: https://daniel.haxx.se/blog/2017/03/27/curl-is-c/

These 'frontend web projects' are all written in C, or a mix of C and C++ though (and I'd wager that doing the same thing in JS would have been more difficult, at least the emulator stuff which is mostly bit twiddling code):

https://floooh.github.io/tiny8bit/

https://floooh.github.io/sokol-html5/

https://floooh.github.io/visual6502remix/

The common pattern is though that both the C and C++ stdlib are mostly useless for this type of project, you need cross-platform libraries where 'cross-platform' also includes peculiarities of the web browser runtime, specific language features are (mostly) irrelevant in this case - except maybe for the bit twiddling thing in the emulators where C is clearly superior to JS (plus you get lean native versions "for free" without requiring a heavy Electron runtime).

> semantics over the meaning of simple

Thankfully there are two different words with very separate meanings that people nonetheless tend to conflate [1]. "Simple" means "not many features", and "easy" means "good to get things done with".

[1] Maybe because we have lots of people speaking English as a second language? In my mother tongue, German, there is no distinction in vocabulary between "simple" and "easy". Both translate as "einfach".

> "Simple" means "not many features", and "easy" means "good to get things done with".

I'm not sure about this. What utility is a language with only simple constructs that is hard to use.

Not quite a counterpoint, but a thought in that direction is:

There can be features that make it easy to implement something which some find hard to understand.

I suppose I question the value of simplicity for it's own sake in a language and would rather defer to how simple and understandable the resulting code is.

I frequently consider the added complexity cost of a more complex compiler inherent complexity because I find the simplicity of use dwarfs compiler simplicity or simplicity "just cuz".

Not much of a difference even among native speakers. If you pose a problem to someone, and they think they have the answer immediately, they're about as likely to say 'that's easy!' as they are to say 'simple!'
I’m not so sure, when I look up the definition of simple it starts with “Easily”. In my native english speaker mind simplicity is a type of easiness.

Though if we want to make the clear distinction, like in the Clojure talk below, for the programming domain that’s fine.

For me, simple is in the amount of concepts.

For example, I consider most multi paradigm languages complex because in them functions, variables, objects, control flow, etc. are fundamentally different concepts.

But something like LISP, smalltalk or Haskell are simple in my eyes, due to almost everything being the same concept on a fundamental level and the other stuff being built on top of that concept.

The problem is when simple vs complex is used as a value judgement, implying that simpler is better. It's not that simple.

Conway's game of life has an incredibly simple definition, and is Turing Complete. The Brainfuck language has a minimal number of language concepts and barely any syntax. And yet, programming in these languages is very hard.

Humans need a certain level of language complexity to be able to express themselves. When you simplify things it's not always clear if you're removing superfluous complexity, or removing tools needed to deal with inherent complexity of the problems you need to tackle.

I don't think there's even any single objectively appropriate level of complexity, because it depends on the complexity of the problem the language tackles. By analogy to human languages: scientific language has its own jargon (sometimes overused!), but it needs an extended vocabulary to precisely describe the things and problems it works with. Without the added complexity it'd be unproductive due to being verbose and sometimes ambiguous, like the amusing example of Up Goer Five: https://xkcd.com/1133

As I am not good with words, I am just gonna take this: https://news.ycombinator.com/item?id=31492953

Specifically what I wanted to express, is that, for me, the turtles all the way down is what I understand as simplicity.

And yeah, simplicity alone can't be used as a value judgement.

> I never got the sense that it could scale much beyond one-person projects.

Adobe Lightroom?

I'll wear that; I'd heard of Lightroom in the context of Lua, but I've never used it and don't know what it's for. Looks like its plugin architecture is a major selling point? Seems like a great fit for Lua.
The UI widget toolkit and the low-level image processing code are in C++ but the people involved have said that the remaining half of the application is written in Lua. Nothing to do with plugins, really.
Cool! I'd be curious how large the codebase is, but it's cool to hear that something like Lightroom exists at all.
Given that it's made by the people who created a printable document format that supports embedded videos, I'm going to assume that it's an alarming large codebase.
The name PDF stands for portable document format (the name itself isn't that relevant, but also the intention).
> Nonetheless, it's not a great fit for most domains; for instance, I never got the sense that it could scale much beyond one-person projects.

I don't think it's really missing features compared to Python or Javascript that would help with this. Tooling could improve a lot though :)

(comment deleted)
> though the lambda calculus, SKI combinator calculus, or and MOV-only assembly are minimal in very real ways, the cognitive burden of expressing yourself in these languages is very, very high!

But much less high with the lambda calculus than with the other two; Haskell is not far from being a layer of syntactic sugar on top of the lambda calculus.

For an example of a non-trivial lambda calculus program, see this self-interpreter [1] for binary lambda calculus [2].

[1] https://github.com/tromp/AIT/blob/master/uni.lam

[2] https://www.ioccc.org/2012/tromp/hint.html

True! I think pattern matching and the high-powered static type system really pull a lot of weight here, though. Haskell's types are a whole other dimension of domain modeling. You can encode algebraic data types as pure lambdas, but it's much less pleasant IMHO. (Laziness also bakes some common streaming patterns into the language core, which I find really nice, but it also makes it harder to totally understand what's going on sometimes, so it's a bit more of a toss-up.)

Point being, nobody wants to model their data structures as pure lambdas; even primitive integers are an addition on top of the lambda calculus.

> For an example of a non-trivial program

I don't think I'm taking the point you wanted me to from this program. It's under 30 lines, yet I'd need to pore over this for twice as many minutes to comprehend what it's doing. I can see the lambda abstractions and applications clear as day, but what they're doing I couldn't say. (The comments clearly say it's a universal binary LC interpreter, but we don't usually judge languages by their comments.)

EDIT: Right, okay, it's a submission to the the International Obfuscated C Code Contest. If it's obfuscated, then indeed, I'm not meant to comprehend it easily. Not sure that sells the lambda calculus.

Spoiler alert, but if you want a tutorial on understanding Tromp's calculus, then here's the readable version of his IOCCC entry with cryptograms and everything deciphered. https://justine.lol/lambda/
> pure lambdas

Also, in lambda calcalus there is no recursion built-in. You have to build it yourself with the Y combinator.

Right; you just add definitions you need like

let Y = \f. (\x. x x) (\x. f (x x)) in program-using-Y

which is the slightly more legible version of

(\Y. program-using-Y) (\f. (\x. x x) (\x. f (x x))

> You can encode algebraic data types as pure lambdas, but it's much less pleasant IMHO

Not that much less, since you can manually translate back your Haskell code to lambda calculus. E.g. your

    List a = Nil | Cons a (List a)
rewrites to

    let
      Nil = \n\c. n
      Cons = \x\y. \n\c. c x y
    in ...
Of course the type system is what helps you write correct Haskell code in the first place, so the untyped lambda calculus will be painful for debugging wrongly typed code.
Lua 5.1 is the most amazing language design I've ever come across; you can do almost anything with this language and it's as minimal as it gets. It is true that dynamic typing sets limits in terms of project size.
Have you ever built anything complex in it though? One of the pains of professional lua development is how much lay users admire its design. And it is true that its design is elegant and emerges from interesting and admirable constraints.

In practice though it doesn't give you any of the tools you need to do anything except make tools to do those things. Which appeals to yak-shavers like us for sure. But if you need to sit down with it for months at a time and just get work out the door it fucking sucks.

Also 5.1's GC is goddamn tragic. In practice you're usually at the mercy of whatever was implemented by the embedding runtime, but if you're ever left alone with just the raw 5.1 GC you'll wish you weren't. Later versions improved that dramatically.

Can you elaborate on why the 5.1 GC is so bad?
Honestly I'm not an expert on that part of the lua internals so I can't confidently say why. I don't think it's a fundamental problem since later versions use the same approach afaik, so I assume it's just implementation nuance. 5.0 used, IIRC, mark-and-sweep which is not great either but it's predictable: it's easy to develop intuition for how much you're allocating and what the collection cost will feel like.

The main practical issues I've run into with 5.1:

  - per-object overhead in time and space both increased significantly, making many small allocations much more expensive than fewer big ones. further pushing you into the "mutate a few huge global tables" pattern that is already a problem in lua conventions.

  - some of lua's more lauded and genuinely powerful features, esp metatables, don't work smoothly or predictably with the GC. it leads to people trying to manually tune it with __gc metamethods, which is hard to do correctly and causes memory leaks if done incorrectly.

  - weak tables idk,. esp with strings for some reason. similar to the __gc, there is a solution there but in practice it's very hard to be sure you've got it right and it's worse to get it wrong than to not try.

I'm sure a PL expert can explain and justify all this but I am just a normal person who codes for money. Lua is admired for being simple and accessible, but especially in the older versions you get into the low level weeds real fast over this stuff. Kind of a good example of minimal implementation exposing you directly to irrelevant complexity, really.
This is nonsense; where do you have that from?
I'm using it since 20 years (e.g. for http://cara.nmr.ch/doku.php, or e.g. this one https://github.com/rochus-keller/Smalltalk), also used it as backend for programming languages and developed tools on it (e.g. https://github.com/rochus-keller/LjTools/). Is this complex enough? Never had an issue with the GC.

> you're usually at the mercy of whatever was implemented by the embedding runtime

Are we talking about the same Lua? The engines I know (PUC Rio and LuaJIT) are not at the mercy of embedding runtimes, but haver their own, sufficiently good memory management.

We might not be talking about the same lua! Like I kind of hinted, I think it's likely very good for expert users to build tools on.

I'm not an expert user of anything though, or building tools. My professional experience of it has been using ancient versions that are embedded into C or C++ systems to build simple guis and automation within those systems. I don't think it's well suited to this, but this is a common (I believe even it's original?) use case!

I've never used luajit, or any version where I had full control over the runtime at all. I understand people love this language and I'm not saying they're wrong to. But for the things I've needed to use it for it has not been enjoyable at all, particularly 5.1 which is the one I have the most experience with by far.

I've used a lot of different embedded scripting languages with C and C++ and Lua is by far the most efficient and easiest to integrate one. Especially the 5.1 version is excellent and I still use it today. In what notable project have you used Lua?

> I'm not an expert user of anything though

But apparently confident enough to make negative comments about the technology.

Yeah pretty much, sorry. I do this for money and generally more or less enjoy it as far as that goes. But I've always had a miserable time working in lua, more than any other language I've touched professionally.

I might be wrong about the technical reasons why, and I'm genuinely sorry if I've unfairly maligned your baby here. But it has always been unpleasant for me and I think I'm entitled to my confidence about my own experiences in it.

It's not my "baby", just one of many programming languages I'm using or design myself; but is't a great example for how good you can get with a minimal language design. Personally I prefer strictly/statically typed languages.
> Have you ever built anything complex in it though?

I have, yes, it's great. No complaints, would choose LuaJIT again.

I do regret that the quad-color collector will likely never be implemented, I'd content myself with a back-port of the new generational collector, but the allocation sinking in LuaJIT has kept me out of trouble for the most part since it can put 'tables' on the stack if it can prove they don't escape.

> I never got the sense that [Lua] could scale much beyond one-person projects.

I had a misfortune to deal with more than 200K lines of Lua code back in time [1]. It seems that my experience doesn't necessarily match those of other big Lua users though; it does seem to work when everyone is willing to invest in self-contained environments. In the other words successful Lua users doesn't exactly use Lua, they use their own programming systems based on Lua. (I should note that such systems are not guaranteed to be successful, as evident from my case.)

[1] https://news.ycombinator.com/item?id=18351788

> I never got the sense that it could scale much beyond one-person projects

It can be done, but it requires a lot of discipline.

You might enjoy Teal, a sort of "Typescript for Lua" https://github.com/teal-language/tl

Checkout Oberon (the successor of Pascal and Modula). The whole language guide for Oberon is just 17 pages because it follows this philosophy ("Make it as simple as possible, but not simpler" - Einstein) - https://people.inf.ethz.ch/wirth/Oberon/Oberon07.Report.pdf .

Mostly Oberon - a series of blog posts documenting exploration of the Oberon Language - https://rsdoiel.github.io/blog/2020/04/11/Mostly-Oberon.html

Oberon to C Compiler - https://github.com/GunterMueller/OBNC

Oberon+ - Oberon parser, code model & browser, compiler and IDE with debugger - https://github.com/rochus-keller/Oberon

Niklaus Wirth, the creator of Oberon (and Pascal and Modula-2) has been in the small programming camp all his professional life. I also agree with his views.

One of the advantages of small languages is that you can actually learn or master the whole language. However, small doesn't always mean simple. An example is the frequent discussion over Go's supposed simplicity. Other new languages fall in the medium-to-large size: Julia, Nim, Rust, Swift, Kotlin, Crystal.

Back to Niklaus Wirth and his thoughts on Oberon:

> There was obviously no way [for Oberon] to play an influence next to Java and C# and C++...But yes, I thought, as I did in the Pascal times, the foundations have been laid when a person learns his first programming language. That’s the key. In order to do a good experience, you have to have a clean language with a good structure concentrating on the essential concepts and not being snowed in.

> That is my primary objection to the commercial languages – they’re simply too huge, and they’re so huge that nobody can understand them in their entirety. And it’s not necessary that they’re so huge. But people don’t want to see that. Particularly industry doesn’t. I think the industry thinks it’s a big asset to have a complex thing that increases their reputation of being sophisticated.

(From a 2018 interview with Niklaus Wirth: https://www.youtube.com/watch?v=SUgrS_KbSI8)

I think Pascal itself serves as Wirth's own counterexample. When I first learned Pascal it was not the original version of Pascal. It was a Borland's highly extended version of Pascal, then later Delphi. Wirth still has a point as they are considerably smaller than many contemporary languages, but it is clear to me that a bare Pascal wasn't large enough.
From my point of view this also applies to Oberon.
You're both missing the point maybe? Oberon the language was plenty good enough to create Oberon the OS which ran ETH Zurich for a decade or two. The University used the Lilith and Ceres workstations as their main system (as far as I know.) It wasn't a toy, it was the bleeding edge in it's day.

The point is that the functionality of current systems hasn't progressed much beyond what you could manage three or four decades ago despite Moore's law because complexity has eaten the gains.

I don't think so. Actually Wirth himself made additions to the original Oberon proposal, not by coincidence. Personally I prefer Oberon+ over original Oberon. See https://www.quora.com/Why-isnt-the-Oberon-programming-langua..., https://oberon-lang.github.io/2021/07/15/motivation-for-a-ne..., and https://oberon-lang.github.io/2021/07/16/comparing-oberon+-w....
That looks like a pretty cool language.

To me it sounds like you and the other person were saying that Pascal and Oberon weren't "large enough". I'm just pointing out that they were large enough that people can and did do serious work with them, and that, in many ways, our modern systems aren't that much better than what they achieved with those relatively simple and primitive tools. They weren't perfect, both languages grew and became more elaborate, that's true, but the original systems were solid.

Thanks. The argument was, that Wirth's original language designs were less successful than the extenden versions designed by others, e.g. Boarland. The original designs are undoubtedly sufficiently expressive to build things of decent size, but apparently too little attractive for wide adoption. Not surprisingly, variants of all Wirth languages have quickly emerged; in the case of Oberon, for example, these are Oberon-2 by Mössenböck or Component Pascal by an ETH spin-off.
> Thanks.

You're welcome. I don't look at usernames on HN so it didn't occur to me until now that it's your project. It really does seem like a well-considered extension of Oberon, I'm going to check it out in more depth later.

> The argument was, that Wirth's original language designs were less successful than the extended versions designed by others, e.g. Boarland.

In a sense, the extensions of his work are the success, eh?

> The original designs are undoubtedly sufficiently expressive to build things of decent size, but apparently too little attractive for wide adoption.

In the quote from the interview with Wirth he says "I think the industry thinks it’s a big asset to have a complex thing that increases their reputation of being sophisticated." So rather than a curing a lack of expressivity, complexity instead provides a marketing edge, in his opinion.

> Not surprisingly, variants of all Wirth languages have quickly emerged

Again, to me that seems like a kind of success. The alternatives are that Wirth birthed perfect languages, like Athena springing from the head of Zeus, or that they were all barren and died on the vine.

> So rather than a curing a lack of expressivity, complexity instead provides a marketing edge, in his opinion.

His focus was educating students at ETH; the languages he created with this focus were fit for purpose; it is true that there are developers in industry who prefer complex languages for sophistication reasons; but there are also pragmatic people; even they apparently prefer Delphi over Pascal; I don't consider Delphi complex e.g. in comparison with C++11.

> to me that seems like a kind of success

Agree; the original languages are great and a robust basis; and e.g. Go is a very successful offspring of Oberon-2.

> Personally I prefer Oberon+ over original Oberon.

I don't like that Oberon+ removed uppercase requirement for the keywords. While I too found the uppercase requirement to be weird initially, I have now come to appreciate the sentiment behind it - it does make it easy to read and review the Oberon code. Just like Python uses indentation to increase readability, the uppercase keywords were a good defining feature of the original Oberon, and shouldn't have been removed. After all, now that modern editors can automatically make the keywords uppercase, programmers don't really have any reason to complain about it. It's a simple usability trick that doesn't rely on colours or font size or weight to increase readability. (One can also argue that if you want to remove that as a language feature, why stop there - why not also remove BEGIN and END and replace that with brackets or something else?).

Upper case keywords are not removed. You can continue to use the old syntax, so the code looks like it came straight out of the sixties, see https://oberon-lang.github.io/2021/07/15/examples-with-tradi.... Each valid Oberon, Oberon-07 or Oberon-2 program is a valid Oberon+ program. The lexer checks whether module is written in upper or lower case and then switches the syntax. Only a few people still prefer the old syntax. What do you dislike about begin/end?
Thanks for the clarification. It would also be helpful if the editor also included a setting to automatically capitalise the keywords and make the developer's job easier. And no, I don't dislike begin / end as they too improve readability (I was just pointing out that they are also a language design feature to increase readability). Modern editors though should anyway be offering to auto-complete it so that typing these doesn't become tedious.
> There was obviously no way [for Oberon] to play an influence next to Java and C# and C++...But yes, I thought, as I did in the Pascal times, the foundations have been laid when a person learns his first programming language.

Eww. Pascal is the worst first language ever. A first language should show you and familiarize you the concepts that are the best we know, and that you are likely to encounter.

It should be as multiparadigm and as easy to access the tools as possible.

Javascript beginners soon will know the concepts of oop, functional paradigm, async, etc, will recognize every pattern when they see them, also will use the best approaches to problems.

While pascal beginners after years still won't understand shit (mapping over elements of an array? what sorcery is that?), and will write you procedural code to every problem that clearly just don't work.

Please don't force newbies to grind to write procedural code, it's way worse than nothing.

Javascript is a good choice for a first language in that a learner can do exciting things with it and development tools are widely accessible. I disagree with the idea of teaching languages being separate from business languages.

That said I think there is value in the idea of a minimal language, so I'll try being the devil's advocate. As far as I can tell Wirth does not think that students should be picking up Pascal these days. He was referring to his thinking back in the 60s when he was discussing the same ideas.

I agree that a first language should show you the concepts that are the best we know, and likely to encounter.

Oberon has numbers, identifiers, expressions, statements, etc. It has subroutines, references to locations in memory, structured control flow, and both simple and complex data types. Its type system is what provides the support for object-oriented programming. This seems like a reasonable set of things that a starting student will encounter.

Can maps be done in this language? Yes. You could in a course introduce the concept of maps by having the students implement them on their own. This reduces the amount of what feels like magic involved in the language, which is something Wirth seems concerned with the most.

Are other elements of functional paradigms likely to be encountered? Really depends. Some languages do not have support for closures even while calling themself functional. They may not guarantee that tail-recursion does not expand the stack. At the same time, Lisp had none of these things in its first implementation, and was still functional-enough to be a educational and research tool. I am not sure it is something that you are likely to encounter in any one form, but the concepts should be taught. You could teach what a closure is by implementing a restricted one inside Oberon, and can do recursive functions as long as the stack allows.

Async has a lot of variation, even if you are likely to encounter it. IMO parallelism is better taught as a concept and then applied using games. It is difficult to build an intuition of it at a programming language level.

This article definitely strokes a chord in me, although in my case for for Swift, not Rust. After I retired a couple of years ago, I decided to develop a Mac and iOS app in Swift using SwiftUI. In most of my career, I used Java, C or Python.

Swift, like Rust, is a very complex language. Even six months after I started, I keep learning new language features. In some cases, I had to watch WWDC talks and read the language spec to understand why some features were included in Swift. And even then it left me wondering why they could not tolerate a slightly more ugly syntax, in favor of readability.

I should mention that I have been programming for forty years and and I have written several compilers and language run time systems in my career.

> And even then [Swift] left me wondering why they could not tolerate a slightly more ugly syntax, in favor of readability.

Well said.

I've also been coding for a couple of decades (mostly in C++) and I also found advanced, real-world Swift unbearably... "irritating", for the lack of a better term. Surprisingly, I do not have that problem with Rust, even though Rust can be quite complex in order to be able to express ideas that Swift cannot handle at all (lifetime, ownership, immutability of reference types, mutable reference exclusivity, dynamic trait types, etc.) Somehow my brain is happy to consume complex Rust, while it's often "irritated" about consuming complex Swift.

I think the reason why Rust is so overwhelmingly better than Swift (for me, at least) is the fact that a lot of (ingenious) effort must have been put by the Rust Gods into Rust language >>consistency<< and >>predictability<<. My proverbial "sh*t, I like Rust..." moment came after I noticed a few things: 1.) Rust did not allow me to force-implement the Copy trait for a type that was not actually simply bitwise-clonable, 2.) Rust was expecting the "await" keyword after an expression, like a function call, yet uniquely without parenthesis, and 3.) Rust closures turned out to be based on the very easily noticeable double-pipe syntax.

My plea to the Rust Gods That Be: do not ever allow Rust to lose that unique "explicitness" and "predictability". It is one of the language's greatest assets.

I agree with you on swift. The evolution process seems to favor adding more "features" instead of unifying, simplifying concepts. For instance, we have special syntax for optionals, exceptions, asynchrony, coroutines (mostly hidden), and any other effects they decide to add in the future. Whereas in haskell, all you get is `do` syntax for describing these things. The trade off is that in haskell it's harder to learn the first thing, but then easy to pick up the rest. In swift, you have to learn how all these special effects interact.
I think it depends on what you want to achieve. If the goal is to quickly create solutions for complex problems, then it helps if the language (as a means to an end) already provides ready-made "features" that you can fall back on. Of course, not everyone will need all of them, but only a subset for his or her domain. I'm thinking of the scientific community, where C++ is very common and almost more like a DSL. Here the finished program is used as a tool for a task, which itself is the final product. So the coding itself is "only" a (sometimes annoying) preliminary stage. You don't want to build important language constructs yourself first from minimalist elementary building blocks.

It may be different for professional programmers, whose programs often have a different purpose, are created by multiple developers, and have much more effort put into reusable, clean code. Here, the code base is more the product than the finished program.

A recent observation about the advantage of syntactic minimalism:

As a newcomer to Rust, a thing I noticed is the syntactic explicitness around generics: In particular, I had to learn to write Foo::<T>::bar(), i.e. the double `::` seems verbose. But it is also unambiguous that the `<` is not a less-than, because it comes to the right of a `::`. There are other `<T>`s that don't need `::` because they're already somewhere that could only be a type. A human probably wouldn't mistake those. So you might have syntactic complexity in a syntax that is easy for a human to wrap their minds around, and perhaps even makes expressing yourself easier.

It wasn't until recently that someone on ##rust pointed out that with unambiguous syntax, you can more easily transform old code, too. So in some future where a piece of syntax changes, you can more easily write a tool that transforms old code into new code.

The turbofish gets everyone's goat at some point but it teaches an important lesson: it is incredibly difficult to build a Rust-size grammar that is free of ambiguities! Modern parser tooling (e.g. tree-sitter) will at least yell at you when you specify an ambiguity.
(comment deleted)
> The mistaken idea that “everything should be an object” doesn’t actually simplify anything in practice. We’re lying to ourselves, and in doing so, we actually makes the life of both language implementers and programmers more complicated.

That gets to the point.

But there is a lower bound of minimalism, below which the complexity increases again; I remember the time where a few academic ergonomists spread the idea that you should have only one knob in the user interface instead of many elements; the result was that you needed many more steps for each function and the operation was anything but intuitive.

It's the same with a programming language: if it's designed too spartan, I have to write too much code that is always similar, which causes effort and obscures the real intention of the code. And if the syntax is too minimalistic, complexity is just moved to semantics or the standard library. So the art is to find the right balance, not just to omit.

> It's the same with a programming language: if it's designed too spartan, I have to write too much code that is always similar, which causes effort and obscures the real intention of the code.

I think Go is the perfect example of this. On paper, it's a simple language. But:

- Not having sum types means it's awkward and unsafe to express concepts such as "X or Y". This is a big deal, because "Result or Error" is one of the most common ideas we have to deal with in programming. I think this tweet visually captures the awkwardness: https://twitter.com/GabriellaG439/status/1521860707444133888. Another place this shows up is pointers: in Go, since there is no way to represent "present or missing" at the type level, Go has to bake that possibility into the semantics of pointers. The billion dollar mistake.

- Every type in Go has a "zero" value. On the surface, this seems simple: when you declare a variable, you get a predictable value without having to explicitly initialize it. But this prevents you from implementing abstract data types which are guaranteed to be constructed by a smart constructor that establishes all the relevant invariants. Now you always have to worry about this zero value, since it might not satisfy the invariants of your data structure (consider the simplest data structure with an invariant: a pointer which is non-null!). Also, you can easily forget to set a field in a struct, which means the zero value will show up in unexpected places (resulting in subtle bugs that might go undetected even at runtime).

- Until recently, Go didn't have generics. Simpler, right? But that means if you want to build a reusable data type, you needed to either (a) make N copies of it, being careful to keep them in sync, with no help from the compiler, or (b) sacrifice type safety and represent data as interface{} (essentially a void* pointer), adding dangerous casts all over the place.

Languages like Haskell and Rust are more complicated than Go. But once you've paid the upfront cost of learning them, common programming patterns actually become simpler.

Of course, there's a flip side to this. Most languages have a lot of accidental complexity too, which isn't what my argument is about. So I almost hesitate to make this comment, fearing that it will be quoted out of context to justify adding badly designed features to programming languages.

In this case, you will not enjoy Oberon+, although it supports (lean) generics. This shows that the view of what is minimal and optimal is ultimately also strongly subjective, which is actually not surprising.
Oberon was designed in a very different context though: its goal was not to have a gazillion users, it was to build one system, that by the way happens to fit in 10,000 lines of code, compiler included.

In such a small system, the benefits of any feature are much smaller. Let's say you have some feature that could simplify your program by 1%. Over 10,000 lines of code that means save 100 lines of "complexity". If implementing that feature takes more than 100 lines, your small 10K lines system will end up more complex, not less.

Go however is fairly widely used. Simplifying by 1% on average is huge, and likely worth it even if it requires 10,000 lines of code all by itself.

Note that I'm talking about Oberon+ (see http://oberon-lang.ch), not the good old Oberon (which has no generics at all); but also the latter was conceived as a general purpose programming language; it was its fate that Wirth and Gutknecht built an operating system with it first and there was a compiler only for that operating system, which was not ideal for the spread of the language. Go is the most famous offspring of Oberon so far.
This is a good overview of my thoughts on it. A few years ago I had a number of colleagues flogging the "I love Go, the code flies from my fingers!" drum, but I didn't get it. Yeah, you know what you have to type but in a more expressive language you don't have to type it at all! It felt like they were making the "Don't mistake motion with action" mistake.

But, people can like what they like, it's all good.

Part of the problem, I think, is that minimalism is only defined with regard to some operation, function, or task, at some level. So what might be minimal for one task becomes complex for another, or vice versa.

I think many languages have some implicit set or class of functions in mind, and some of the frustrations people encounter is in a mismatch between what the language was designed for and what they want to apply it to.

I still think minimalism comes into play as a language design goal for an application domain, in the sense some languages are unnecessarily obtuse. But I also think there is something else — I'm not sure what the term is, but "scalability" or "abstractability" comes close — related to a language's capacity for minimalism at multiple levels of abstraction. That is, can you simplify code as need be if youre not interested in the details, but delve into the details if need be?

Many languages seem to be treated like products ... must add new feature ... must announced new version ... as if the "owner" is worried about getting left behind. Mostly counterproductive.

Many (most?) genuinely widely used languages are more-of-less static. C hasn't changed substantially in 20+ years.

And many of us have worked in projects where a subset of the nominated language is mandated, or certain "features" banned - specifically to prevent the use of overly complex, poorly known, or misunderstood language features.

I'm a fan of "if it works, don't make it worse."

I don't think I agree with that. Java isn't static. Python isn't static. C# isn't static. These are some of the heavy hitters in terms of total number of users. Stability is valuable, but popular languages still manage to add features to keep up with changing needs. Even C isn't %100 static.
Relevant article. When trying to port a project from C to Rust, I would take a look into Elixir instead of Rust. More clear/cleaner syntax, more minimalistic, functional, less verbose, mild learning curve. We did this successfully in our project: ported the entire back-end to Elixir from C++/Php, while initially we considered Python, Java and C#.
How mature is the tooling/libraries for Elixir?
They are top notch. One must give it a try to believe.
The performance characteristics of Rust and Elixir are completely different though.
Keep in mind that you only have to use a subset of a language if you want to.
I personally believe in this too, and feel the same way about frameworks too. Other dev's have the "use every possible feature so this can never be removed" mindset which his bit me in the ass repeatedly.
But everyone has their own subset that they want to use, so I still have to know and understand it all if I want to read other people's code. And moreso if I want to do anything with that code.
This is largely an issue with C++.

Code in other mainstream languages remains generally readable even if the coding style changes.

I was indeed thinking of C++.
This article went in a completely opposite direction I thought it would be going.

Based on the title, I thought they were going to talk about how all operators should be expressed with 3 characters (i.e. `:=:`, `|=>`, etc.) or how all reserved words should be 4 character words.

Minimalism is good if it means not adding unneeded features or not adding things that don't go well together.

Otherwise, it's not a great idea. Basic is a very minimalist language but good luck trying to do anything complex.

Rust might be unnecessary complex and that is the other side of the coin.

Look at it a different way: maybe BASIC is good at exactly what it was intended to do, which is to provide a good intro to programming to kids and beginners. It was never intended to write enterprise-scale software, and that's ok. That's not the intended use case for BASIC, and it's fine that BASIC can't please everyone with every possible use case.
yep, it seems rust is loosing its way like c++ (maybe it is already lost).

And when you look at c23... same tracks.

RISC-V quick!

Many domains are inherently complex. If your programming language is simple, then the complexity will be in the code. Think Java's boilerplate, or having to re-implement hash tables every time in C because you can't abstract over data types.

In a wood shop, you can do everything with a hammer and chisel. You can make a table leg with them. It'll take a long time and won't be super round. However, having a lathe makes it much faster and gives you a better end result.

Also, it's been said that design patterns are just ways around missing language features. What one person considers language complexity, another seems as an essential abstraction.

I think Go had a great observation that reading complexity != writing complexity for a language, and that many features which make writing coffee easier make the language harder to read.

Go also made the fantastic decision to design in concurrency as a primitive... And then kinda for on the hill of generics. There's a lot to be said for choosing the right set of features, keeping the code readable as a first priority, and letting libraries handle the rest... Need a lathe? Import lathe!

> There's a lot to be said for ...

There's nothing to add for what its creators stated: Go is meant for under-average programmers, not for professional software developers.

You, software developer, are tasked with building a complex distributed system. Do you pick a high complexity language to build your high complexity system, or do you opt for a low complexity language do you can focus on the system?

It depends on what benefits those language features have. More often than not, they do not provide benefit to your business.

Plenty of professional systems are built in Go. This opinion that Go developers are subpar is silly rhetoric. It is not meant for everything, but for distributed systems it is great.

> This opinion that Go developers are subpar is silly rhetoric.

I don't think so. But it's a fact that Go was meant for sub-par programmers.

That really isn’t a fact. It was created to be easy to learn such that junior software engineers or people coming from other languages could get to writing effective code in a short period of time. Neither of those groups are sub-par.
That looks like a citation needed situation.

Seeing the kind of usage go is known to have inside Google, and how is generally accepted that Google hires only the best engineers, this fails to be consistent. I doubt it very much that anyone involved in Go development has said anything that can be interpreted anywhere close to what you said.

(comment deleted)
Haha, thank you. However it's weird that a request for citation is met with a negative score especially since it's not really obvious how Rob Pike's quote is referring to "under average developers". :)

Also, to continue being pedantic I must say that by definition every programmer that gets paid for their work is a "professional software developer" despite the hyperboles grandparent seems to be fond of.

The smartest people have the best means to outsmart themselves. The bigger they are the harder they fall.
It's probably not your cup of tea but I cannot resist reminding you about that coffee language and its "decision to design in concurrency as a primitive". How about Object::wait(), Object::notifyAll(), the "synchronized" keyword, every Object a monitor?
There is a difference between a simple language and a small language - sometimes they are the same thing, in other cases the language is small but not necessarily simple.

The C language is a relatively small language, but has been used to write complex databases and operating systems. So is C simple or complex? Or both? (Everyone has a different opinion).

Also, there's also a distinction from the small language core (the syntax and lanuage features) and the much bigger standard library that provides additional functionality.

> Many domains are inherently complex. If your programming language is simple, then the complexity will be in the code.

I think this is equivalent to the "spherical cow" problem in physics. A lot of calculations in physics are much simpler, if you assume everything is a spherical cow. However, the real world is messy, and not everything is a spherical cow.

In programming, a lot of complexity comes from interfacing to the real world. Whether it is CPU performance/memory hierarchy, or messy business logic.

The drive for simplicity, often ends up sweeping these under the rug.

pretty well put.

it is an optimization problem, and the optimum isn't at the extremum of the constraints, it is somewhere in the middle.

just because C++ is a bit of a mess after 37 years of evolution doesn't mean that the simplest language is best.

rust is also the first widely used language with a borrow checker and may suffer from having to discover the correct primitives while hunting around in the dark to begin with.

> rust is also the first widely used language with a borrow checker and may suffer from having to discover the correct primitives while hunting around in the dark to begin with.

Rust is the first widely used language that addresses the question, how do you have memory safety without garbage collection and loss of efficiency. It comes up with (IMO) a very good answer, but likely not the final answer to this question.

But I think trying to answer that question, more than justifies the complexity.

...and all the expressive syntax in the world just leaves the pointy-eared, spike-tailed, non-spherical cows, poking out all over the place.

I pretty much agree with everything you said, and think it's a good analogy, but eventually come back to the point of the article we're responding to, which is that a lot of the complicated syntax, and features that languages introduce, just ends up non-essential complexity around the inherent complexity of the real world, that you're highlighting.

Sorry, but I have a hard time believing the common trope that something silly like more language complexity will significantly help keeping the complexity of the code down. Personally, I believe it is often the opposite.

So you can't abstract over data types in C? I often do. Some random thing I often do:

    void linear_allocate(Linear_Allocator *alloc, size_t size, size_t align, size_t count);
    
    #define linear_allocate_type(alloc, type, count) ((type *) (linear_allocate((alloc), sizeof (type), alignof (type), (count))))
(Anybody objecting they don't like macros and this is not a real abstraction: You are all fools. To a certain point, that is - before you possibly start being right. But don't underestimate how far this can go, and how long this can be a superior solution over alternatives.)

You know something that is very hard to abstract over? Programming language FEATURES! Anybody, please show me the ways of doing computation over all the data that was encoded as fancy programming language syntax. This requires even more language features to get this data back from the compiler in some weird form... if possible at all.

> Anybody, please show me the ways of doing computation over all the data that was encoded as fancy programming language syntax. This requires even more language features to get this data back from the compiler in some weird form... if possible at all.

Happily! https://lisp-lang.org/

So you show me (or do not show me, instead you just link a website) your ways of retrieving the data encoded by fancy syntax, using a language that has literally almost no syntax at all?
Exactly! The extremely simple syntax of a lisp is what enables similarly extreme syntactic consistency. That consistency, in turn, is what enables lisps' uniquely powerful treatment of code as data, so that programmers can access and transform what would be represented in other languages as "syntactic constructs" and "data values" in a uniform (and, in my opinion, quite nice and expressive) way.

At risk of stereotyping myself, if you like this kind of thing, check out SICP. It is a really good dive into exactly how far you can go with this kind of tool, and also has applicability across the engineering discipline (not just for lisp folks).

I've read through most of SICP, but I never found what's so great about it. Maybe it is that you can apply this argument to any other language as well: Represent your theoretical model as data structures that you control, instead of giving up control to the compiler.

Representing code as data is not very interesting IMO. Code is code, data is data. Granted both are stored in RAM, but they are very different beasts considering how they flow through the machine. Almost any language allows to parameterize over functions (e.g. function pointers), but there is no need to make closures a first class citizen. After all, closures are a brutal abstraction, isolating any context from the caller. When overdone this ends up in a terrible mess where you don't know what's happening anymore (otherwise known as callback hell). Conclusion, use this only as a last resort.

IMHO, languages should aim to be minimal, simple constructs, that provide powerful, well thought out building blocks to solve your problem.

Most of the complexity should be in the libraries. Only a small set of select people can modify the core language. Anyone can make a library.

> If your programming language is simple, then the complexity will be in the code.

You can have a conceptually simple language, but include a massive standard library with everything most users would need.

> If your programming language is simple, then the complexity will be in the code

I think there are two different definitions of simple in programming languages: simple to implement and simple to use. Simple to use programming languages can be quite complex under the hood. Python’s bigints are an example of a relatively complex to implement feature that simplifies life for developers.

Whether language features and restrictions simplify life for developers depends on what programs they want to write, though. If you want to emulate wrap-around 32-bit unsigned integers, for example, that’s harder in python than in C.

So, whether a language is simple to use will depend on what programs developers want to write in it. Consequently, language designers should know their audience when designing a language.

Author is complaining not all compilers satisfactorily support C++20 modules. But how is support for C17 features? Kernel only just switched to C11.
C++ shat the bed for all languages :(

Rust can't do anything without people fearing it's will inevitably end up like C++, or even just look at shallow syntactic similarities and conclude it's already just as bad. But really it's not. Rust has two+ kinds of strings on purpose because it has two kinds of ownership, not because it has a historical baggage.

Really, C++'s featuritis and exponential complexity is unique to it. Pretty much every other language has survived decades of evolution without fucking up, and gracefully grew into something better. Java and C# are doing fine. JS and PHP have cleaned up a lot of mess. Even Ada and Fortran have been evolving for decades, and are fine.

People learning Rust keep asking "but whyyyyy I have to write this syntax!?", and often they're surprised that the answer isn't "because BCPL on PDP-11…", but reasonable cases of bugs and ambiguities it avoids. Robust code at scale must avoid implicit behaviors, and good compiler suggestions depend on having redundancy in the syntax, so there's a limit to how much syntax sugar Rust can have to avoid creating gotchas.

> Rust can't do anything without people fearing it's will inevitably end up like C++, or even just look at shallow syntactic similarities and conclude it's already just as bad

Frankly, that's an incredibly naive take. IME engineers that are confident to try out and work in multiple languages don't get scared off by syntax, rather the semantics. The issue with Rust in this regard to many experienced C/C++ developers is that the compiler forbids programs that are valid in C or C++ (for good reason, don't get me wrong), generic programming is highly encouraged and difficult to avoid, "unsafe" code is required yet significantly more difficult to write correctly than equivalent C and C++, and frankly, lifetimes are obnoxious.

I say this as a massive Rust fan and shill. I don't think the syntax is that bad, it's certainly a lot, but I prefer it to something like Idris or Haskell that has a similarly expressive type system with noise-less syntax (that is significantly harder to read/understand compared to Rust).

> C++'s featuritis and exponential complexity is unique to it

Java is similar here, things like Project Loom for example are important but shift the paradigm for concurrency signficantly.

To be totally honest, I dismiss almost all arguments towards languages based on syntax (I think there are some to be made, don't get me wrong). It usually comes from a very naive or inexperienced place. Syntax is important, but so is correctness and non-ambiguity.

> IME engineers that are confident to try out and work in multiple languages don't get scared off by syntax, rather the semantics.

I have never been able to get any one of my many colleagues to have more than a superficial look at a Lisp or a ml.

If you're interested in watching a language constrain syntax but keep cranking on the feature ratchet, follow Swift's development. I can't think of another language that I've used as full of semantic special casing and context dependency.
Interestingly, Scala is a minimal according to language specs but extremely complicated due to flexibility. People can write it like Erland, Haskell, Java, Ruby, or Python
Hear, hear! Wise words from the genius who created Turing Drawings.

It's becoming more and more important to treat complexity as a kind of technical debt that has the potential at any moment to metastasize into eldritch horror.

I've been playing with what you could call a minimalist programming language: Joy created by Manfred von Thun (who was a philosophy professor! See "A Conversation with Manfred von Thun" http://archive.vector.org.uk/art10000350 ) It combines features of Lisp and Forth, and I suspect it may be the simplest useful language. The syntax is enormously simple, and the semantics are likewise simple. It's easy to reason about functions and derive them in a mathematical way using simple algebra, so it's hard to introduce bugs. I recently found a very good 2017 talk by Jon Purdy describing concatinative programming and it's quirks and (potential) advantages: Stanford Seminar - "Concatenative Programming: From Ivory to Metal" https://www.youtube.com/watch?v=_IgqJr8jG8M

Anyway, it's made me "allergic" to complexity. I tried Rust. Wrote the beginning of a Joy interpreter. I paused for a month and came back to it and it had become Greek to me, and I can't read Greek. :( Now part of that is just I 'm getting old, but a lot of it is the complexity of Rust.

I don't want to pick on Rust though, complexity itself is the enemy (Rust is, if anything, an improvement on most of what has come before it, eh? I think only Ada can really be said to be Rust's peer?)

> Designing a programming language to be intentionally minimalistic is an idea that’s highly underrated in my opinion. Most modern programming languages adopt much more of a maximalist design approach. Rapidly adding new features is seen as a competitive edge over other programming languages. The general thinking seems to be that if your language doesn’t have feature X, then people will choose to use another language, or that adding more features is an easy way to show progress. This line of thinking is simplistic, and disregards many other key aspects that are necessary for a programming language to succeed and thrive, such as learnability, stability, tool support and performance.

Not my impression at all. The author presents the design process itself as coming up with a list of features. What is much more sought after is to latch onto some of the following principles:

1. Turtles all the way down

2. Everything is an X

3. Uniformly treating [traditionaly different] things as "the same", like collapsing the value and type levels or letting you run code both at runtime and compile time

You then try to implement the features that you need with one of the above starting points. For example, Zig apparently implements generics using number 3 (compile time code).

In fact, the pinnacle of PL design to some people is to have very little in the way of built-in features and then let the users themselves implement whatever it is they need. Which I guess the Lisp family is the best at.

The quoted paragraph seems like a caricature of the design process of a langauge. She says that this line of thinking "is simplistic". Indeed it is—non-believably so.

I don't think she's presenting the "maximalist design approach" as a good thing, eh?

> The quoted paragraph seems like a caricature of the design process of a langauge. (sic)

It sounds like what's happening e.g. Python or JavaScript, the "design process" seems to be to add features, and then add features to your features so you can program while you program.

> I don't think she's presenting the "maximalist design approach" as a good thing, eh?

The article is predicated on the assumption that this maximalist design is popular. But I have argued that it isn’t.

A polemic needs an adversary. If not then it’s not a polemic and instead just a feel-good article about what most people agree on to begin with. Programming languages should be simple? And certainly not go out of its way to be complicated? Hmm, yes. yawn.

Ah, I see. You're objecting to the "Most modern programming languages" part, eh? It would be interesting to investigate the subject more deeply. Like I said, I was thinking of Python and JavaScript, but you're right, there are lots of modern languages now, and many are very carefully designed (e.g. Elm.)
Minimalism is usually in the eye of the beholder.

There are only a few languages designed with minimalism explicitly in mind (Go is a good example), but even there (a language within which application developers readily accept doing the job normally assigned to the language and compiler) the demand for some minimal set of useful abstractions is forcing the language to grow over time.

Let's be honest: Most complaints about bloat are actually complaints about stupid design decisions.

But at the same time, it's admittedly difficult to learn something when it has a lot of one-off corner cases.

One of the most insightful things Alan Kay said in my opinion is that simplicity is the result of choosing a more complex building block. Intuitively I feel there's truth to this.
> It breaks your intuition, and it constantly feels like the compiler is telling you that you’re writing code wrong.

I had the same experience with Rust, and you know what? It was right. My intuition was in fact wrong, and the code the borrow checker complained about would have lead to memory access violations in other languages. Rust catches it at compile time so my program doesn't fall over at runtime. If you think you know better than the compiler, good on you, more power to you. But in my experience I've found that trusting the borrow checker has lead to rock solid stable code that is performant and never crashes during runtime with segfaults and other memory related symptoms of bad code.

This conversation almost always strikes me as a pointless exercise. We can't even agree on what it means to be "simple" or precisely define what it means to have "less features" in a programming language (or perhaps we can by saying complexity is anything more than what is provided by machine language, but we get no closer to an understanding of how much of this is necessary or when this is desirable). A programming language is an abstraction over machines and inevitably all languages introduce features to unburden the programmer from extraneous detail, toil, or precarity involved in orchestrating the operation of that machine. The author is correct that many "simple" languages like Brainfuck are actually very complex to use, but this strikes at the inherent feebleness of the premise that we can clearly define what we mean by a "simple" language (or at least any clarity on when the complexity tax is worthwhile or not worthwhile). Many programming language features are conditioned on a trade-off that having to think about X less in exchange for requiring knowledge of Y is a net benefit to the programmer. If we take the idea that simplicity means "less features" at face value, we throw out the notion of any high level language having the potential to be simple.

The oft-cited Rich Hickey talk [0] I think gets _somewhat_ closer to vindicating the concept of simplicity being something we can talk about more objectively, but I'm still not convinced the concept is worth redeeming. Instead I would propose that languages should have a clear purpose and context in mind. Designing a language with clarity about the intended scale and pace of change in software that will be built in the language, the domain/category of software it is well adapted to build (i.e. embedded, web, OS, compilers, mathematics, mobile, servers, games, and so forth), and the nature of the teams and the public community it intends to support supersedes and encompasses whatever we want to call "simplicity" or "ease". What is easy, simple, helpful, etc. is incidental to how purposefully built a language is to a set of use cases.

[0]: https://www.youtube.com/watch?v=LKtk3HCgTa8