On the foolishness of "natural language programming". https://www.cs.utexas.edu/~EWD/transcriptions/EWD06xx/EWD667...
On the foolishness of "natural language programming". https://www.cs.utexas.edu/~EWD/transcriptions/EWD06xx/EWD667...
Apparently Dijesktra loved using em-dash!
And its just so obviously correct.
(And also, like DW points, that software is way more complex. But on this case, it's the requirements discovery.)
Example:
- "&&" rather than "and",
- "||" rather than "or",
- "if (A) B" rather than "if A then B"
This only makes the code harder to read for beginners without apparent benefit. I'm not sure whether Dijkstra would have agreed.
Thankfully though, programming languages already use mostly explicit (English) language in function names. Which is a much better situation than in mathematics, where almost every function or operator is described by a single nondescript letter or symbol, often even in Greek or in a weird font style.
There is a tradeoff between conciseness and readability. Mathematics has decided long ago to exclusivly focus on the former, and I'm glad this didn't happen with programming. If we read Dijkstra as arguing that only focusing on readability (i.e., natural language) is a bad tradeoff, then he is right.
Personally I think one advantage of '&&' and '||' is that it's clear they're a notation that you need to know the syntax and semantics of. For instance typically '&&' is "short-circuiting" and will not evaluate its RHS if the LHS is true; a natural-language "if a and b then ..." doesn't suggest that critical detail or necessarily nudge you to go and check. (Not that Dijkstra was in favour of short-circuiting logical operators, to judge by https://www.cs.utexas.edu/~EWD/transcriptions/EWD10xx/EWD100... point 4...)
More generally, I'm not sure of the benefit of tailoring the language syntax for beginners rather than experienced practitioners; the advantage of '&&', '||' and the rest of the "C-like" syntax stuff in a new language is that it's familiar to a wide base of existing experienced programmers.
> More generally, I'm not sure of the benefit of tailoring the language syntax for beginners rather than experienced practitioners; the advantage of '&&', '||' and the rest of the "C-like" syntax stuff in a new language is that it's familiar to a wide base of existing experienced programmers.
At one point in time every awkward and outdated syntax was the more familiar option, but that's of course not a good argument not to improve it, otherwise we would be stuck with it forever.
(Unfortunately Python also follows the herd in using "=" for assignment and "==" for equality. It should arguably be "<-" or ":=" for assignment and "=" for equality. But that's a matter of asking for better symbols rather than of asking for better English operator names.)
Python doesn't require parens for the condition in an if-statement, and the default settings of black or Ruff formatters will remove them if present where not needed. (They can be needed if, e.g., you are breaking a condition across multiple physical lines.)
> Unfortunately Python also follows the herd in using "=" for assignment and "==" for equality. It should arguably be "<-" or ":=" for assignment and "=" for equality.
Note that Python uses ":=" for the assignment expression operator as well as "=" for the simple assignment statement operator.
This hits so hard. cough dynamic typing enthusiasts and vibe coders cough
https://www.cs.utexas.edu/~EWD/transcriptions/EWD12xx/EWD124...
Most examples I can think of would be things like “this method M expects type X” but I can throw in type Y that happens to implement the same properties/fields/methods/whatever that M will use. And this is a really convenient thing for dynamic languages. A static language proponent would call this an obvious bug waiting to happen in production when the version of M gets updated and breaks the unspecified contract Y was trying to implement, though.
The “bug waiting to happen” attitude kind of sucks, too. It’s a good thing if your code can be used in ways you don’t originally expect. This sort of mindset is the same trap that inheritance proponents fall into. If you try to guess every way your code will ever be used, you will waste a ton of time making interfaces that are never used and inevitably miss interfaces people actually want to use.
Rather than call it myopic I would say this is a hard won insight. Dynamic binding tends to be a bug farm. I get enough of this with server to server calls and weakly specified JSON contracts. I don’t need to turn stable libraries into time bombs by passing in types that look like what they might expect but aren’t really.
> If you try to guess every way your code will ever be used
It’s not about guessing every way your code could be used. It’s about being explicit about what your code expects.
If I’m stuffing aome type into a library that expects a different type, I don’t really know what the library requires and the library certainly doesn’t know what my type actually supports. There’s a lot of finger crossing and hoping it works, and that it continues to work when my code or the library code changes.
I run into typing issues rarely. Almost always the typing issues are the most trivial to fix, too. Virtually all of my debugging time is spent on logic errors.
> It’s not about guessing every way your code could be used. It’s about being explicit about what your code expects.
This is not my experience in large OOP code bases. It’s common for devs to add many unused or nearly unused (<3 uses) interfaces while also requiring substantial refactors to add minor features.
I think what’s missed in these discussions is massive silent incompatibility between libraries in static languages. It’s especially evident in numerical libraries where there are highly siloed ecosystems. It’s not an accident that Python is so popular for this. All of the interfaces are written in Python. Even the underlying C code isn’t in C because of static safety. I don’t think of any of that is accident. If the community started over today, I’m guessing it would instead rely on JITs with type inference. I think designing interfaces in decentralized open source software development is hard. It’s even harder when some library effectively must solely own an interface, and the static typing requires tons of adapters/glue for interop.
For sure. My issue is with the ones I find in production. Trivial to fix doesn’t change the fact that it shipped to customers. The chances of this increases as the product size grows.
> It’s common for devs to add many unused or nearly unused (<3 uses) interfaces while also requiring substantial refactors to add minor features.
I’ve seen some of this, too. The InterfaceNoOneUses thing is lame. I think this is an educational problem and a sign of a junior dev who doesn’t understand why and when interfaces are useful.
I will say that some modern practices like dependency injection do increase this. You end up with WidgetMaker and IWidgetMaker and WidgetMakerMock so that you can inject the fake thing into WidgetConsumer for testing. This can be annoying. I generally consider it a good trade off because of the testing it enables (along with actually injecting different implementations in different contexts).
> I think what’s missed in these discussions is massive silent incompatibility between libraries in static languages.
What do you mean by this?
> It’s especially evident in numerical libraries where there are highly siloed ecosystems. It’s not an accident that Python is so popular for this. All of the interfaces are written in Python.
Are we talking about NumPy here and libraries like CuPy being drop-in replacements? This is no different in the statically typed world. If you intentionally make your library a drop in replacement it can be. If you don’t, it won’t be.
I am not personally involved in any numeric computing, so my opinions are mostly conjecture, but I assume that a key reason python is popular is that a ton of numeric code is not needed long term. Long term support doesn’t matter much if 99% of your work is short term in nature.