Live data from Hacker News

The Manuscripts of Edsger W. Dijkstra

cs.utexas.edu

121–129 of 129 posts

Re: The Manuscripts of Edsger W. Dijkstra

#121
post #95

The most important one in the context of 2025 is this one: On the foolishness of "natural language programming". https://www.cs.utexas.edu/~EWD/transcriptions/EWD06xx/EWD667...

Setting aside the the elephant in the room (modern coding LLMs are in some sense indeed compilers for natural language -- except they still "compile to" ordinary programming languages) it nonetheless seems to me that even conventional programming languages use too little, not too much, natural language. Example: - "&&" rather than "and", - "||" rather than "or", - "if (A) B" rather than "if A then B" This only makes…

Friend, this filter is a feature not a bug.

Re: The Manuscripts of Edsger W. Dijkstra

#122
post #95

Earlier quoted context omitted.

Setting aside the the elephant in the room (modern coding LLMs are in some sense indeed compilers for natural language -- except they still "compile to" ordinary programming languages) it nonetheless seems to me that even conventional programming languages use too little, not too much, natural language. Example: - "&&" rather than "and", - "||" rather than "or", - "if (A) B" rather than "if A then B" This only makes…

Note that only about 1/3 of this critique (the last example, and with slightly different actual syntax in place of the preferred syntax) of "conventional programming languages" applies to the #1 language on the Tiobe index, which does, in fact, use and instead of && and or instead of || but uses ":" instead of "then" in if statements.

Yeah that was more a criticism of the common C syntax. The use of the colon in Python is pretty good as it mirrors the usage in ordinary text. Though it still follows the pattern "if (A): B else: C" with parentheses around A. That seems superfluous.

(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.)

Re: The Manuscripts of Edsger W. Dijkstra

#123

Earlier quoted context omitted.

Note that only about 1/3 of this critique (the last example, and with slightly different actual syntax in place of the preferred syntax) of "conventional programming languages" applies to the #1 language on the Tiobe index, which does, in fact, use and instead of && and or instead of || but uses ":" instead of "then" in if statements.

Yeah that was more a criticism of the common C syntax. The use of the colon in Python is pretty good as it mirrors the usage in ordinary text. Though it still follows the pattern "if (A): B else: C" with parentheses around A. That seems superfluous. (Unfortunately Python also follows the herd in using "=" for assignment and "==" for equality. It should arguably be "<-" or ":=" for assignment and "=" for equality. But…

> The use of the colon in Python is pretty good as it mirrors the usage in ordinary text. Though it still follows the pattern "if (A): B else: C" with parentheses around A.

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 "Note that Python uses ":=" for the assignment expression operator as well as "=" for the simple assignment statement operator.

Re: The Manuscripts of Edsger W. Dijkstra

#124
post #119
post #86

Earlier quoted context omitted.

I’m curious, can you give an example that wouldn’t be solved by polymorphism in a modern statically typed OO language? I would generally expect that for most cases the introduction of an interface solves for this. 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…

That’s basically the main example I’d give. I think the static proponents with that opinion are a little myopic. Those sorts of relationships could generally be statically checked, it’s just that most languages don’t allow for it because it doesn’t fit in the OOP/inheritance paradigm. C++ concepts seem to already do this. The “bug waiting to happen” attitude kind of sucks, too. It’s a good thing if your code can be u…

> 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.

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.

Re: The Manuscripts of Edsger W. Dijkstra

#125
post #102

Earlier quoted context omitted.

One should always use a single kind of half-open range, i.e. with the start closed and the ending open. The whole point here is to use a single kind of range, without exceptions, in order to avoid the errors caused by using the wrong type of range for the context. For backwards iteration the right range is [-1,-1-n), i.e. none of those listed by you. Like for any such range, the number of accessed elements is the dif…

I'm following the convention that lists the smaller value to the left. I would write [-1,-1-n) as (-1-n, -1], which is a shifted version of (-1, n-1]. The supposed advantage of 0-based indexing with half-open ranges is that the programmer wouldn't have to add ±1 to the loop bounds as often as they would with 1-based indexing. But backwards iteration is an example where that's not the case. The open range calls for a…

[-1,-1-n) and (-1-n, -1] are not the same thing, even if they contain the same elements.

They are the same thing as sets, when the order does not matter, but the ranges used in iterations are not sets, but sequences, where the order matters (unless the iteration is not a sequential iteration "for", but a "parallel for", where all the elements are processed in an arbitrary order and concurrently, in which case there exist neither forward iterations nor backward iterations).

Therefore (-1-n, -1] is actually the same as [0, n), where the array is accessed forwards, not backwards (the former range is used with a pointer to the first element past the end of the array, while the latter range is used with a pointer to the first element of the array).

The advantage of half-open ranges is not that you would always avoid adding ±1 but that you avoid the off-by-one programming errors that are very frequent when using closed ranges.

However, if that is what you wish, you could easily avoid any addition or subtraction of 1, by using pointers instead of indices. With half-open ranges, you use 2 pointers for accessing an array, a pointer to the first element and a pointer to the first element after the array. The C standard ensures that both these pointers are valid for accessing the array.

Then with these 2 pointers you can iterate either forwards

  for (p=p1; p!=p2;) *p++;
or backwards

  for (p=p2; p!=p1;) *--p;
There is no difference between the 2 directions and the overhead is minimum.

Re: The Manuscripts of Edsger W. Dijkstra

#126

Earlier quoted context omitted.

So you agree it is a strawman?

Perhaps Dijkstra was going for the former, but is it bad to consider a stronger argument along the lines of what he said?

A charitable interpretation would be he critizises e.g JavaScripts silent type coercion which can hide silly mistakes, compared to e.g Python which will generally throw an error in case of incompatible types.

Re: The Manuscripts of Edsger W. Dijkstra

#127
post #124
post #119

Earlier quoted context omitted.

That’s basically the main example I’d give. I think the static proponents with that opinion are a little myopic. Those sorts of relationships could generally be statically checked, it’s just that most languages don’t allow for it because it doesn’t fit in the OOP/inheritance paradigm. C++ concepts seem to already do this. The “bug waiting to happen” attitude kind of sucks, too. It’s a good thing if your code can be u…

> 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. 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…

> Rather than call it myopic I would say this is a hard won insight. Dynamic binding tends to be a bug farm.

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 (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.

Re: The Manuscripts of Edsger W. Dijkstra

#128
post #127
post #124

Earlier quoted context omitted.

> 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. 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…

> Rather than call it myopic I would say this is a hard won insight. Dynamic binding tends to be a bug farm. 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…

> Almost always the typing issues are the most trivial to fix, too.

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 (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.

Re: The Manuscripts of Edsger W. Dijkstra

#129
post #57
post #14

Earlier quoted context omitted.

Alas, I live an a world where efficiency does actually matter, and elegance to me includes efficiency. I live in a world of embedded software, portability, and reliability. In this regard, almost every single functional language is an utter failure, because they require runtimes and big fat common libraries. Even golang is borderline. Haskell has little chance. Generally I think this does answer the question about wh…

Your environment is probably not more constrained than this: https://github.com/Copilot-Language/copilot

That is really quite cool, and unlike any functional language I've seen.
Post reply on HN