Back in the early 70s, I wrote software for the PDP11/20 (in ASM) to collect data from counters for accelerated particles. I recall handcrafting interrupt service routines to ensure they stayed ahead of the counters. And using paper tape to load the software after I hand-toggled the bootstrap loader.
For the next 15 years, I was an APL programmer, and later a Q/K developer. It would have been a hoot to use K was back in the 70s to analyze the collected data!
APL was the first language I learned in school and I was thrown by the precedence of the mathematical operators, but once the other operators were put in play, it was an incredibly simple rule -- and rarely did I or other APL developers have bugs relating to order of operation.
I then moved to C++ and was gobsmacked the the precedence rules and was forced to use parentheses to get things right. I have seen hundreds of bugs produced by programmers that tried to be clever and avoid parens relying on their faulty memory of the precedence priorities.
Just look at the C++ Operator Precedence table listed at the following URL. It's wild!
An excellent article. Thank you for your insights. I used to run a software engineering practice and I must admit that almost all of your points resonated with me.
I'd like to add that one should catch oneself when falling into the "Perfect is the enemy of good" trap. As engineers, we would like things to be perfect, but sometimes at the peril of late delivery, little feedback, and development of features not required.
Back in university (1974), I took a course in AI. The prof wanted us to write a brute-force solution to solve the 8-queens problem -- any language!
I wrote the solution in APL in about an hour and it only had 3 lines of code. The rest of the class spent days on their terminals and keypunches trying to solve it. Most solutions took hundreds of lines of code.
I got a D from my professor. I questioned why and was told that it was unreadable, and that the solution was inefficient. This annoyed me because he didn't know APL, and I figured that since I solved the problem in one hour, while the rest took days, it was very efficient.
I protested the result with the department head (who liked APL) and ended up getting an A+. As you can imagine, all the rest of my assignments, written in a variety of languages, were graded by that AI prof with significant prejudice.
I passed nonetheless. I loved APL and ended up working for one of the major APL providers as my first job out of school.
> I questioned why and was told that it was unreadable, [...] this annoyed me because he didn't know APL.
I often tell people that Spanish is unreadable if you don't know Spanish. This also applies to language features! It's only fair to call things "unreadable" if you have the full context to understand but still find it hard.
There was a discussion on array programming languages here recently where someone proudly showed off a K language solution to a simple problem, stating that the K solution was efficient because it could solve it in 1.2 microseconds. I used Rust to solve it in 5.5 nanoseconds, which is nearly 200x faster. Both used "brute force" with no cleverness, but there's "bad" brute force and "good" brute force.
I've had a similar experience at university while using Haskell. It's a very elegant lazy pure functional language, but it's glacially slow. The natural, low-friction path is very inefficient. The efficient path is unidiomatic and verbose.
I hear people making similar observations about F# also. It's fast to program in, but if you also need performance then it is no better than more mainstream languages -- in fact worse in some ways because you're "going against the grain".
I think that the nuance here was perfectly obvious, if not explicit, to everyone who wasn't busy burying it under a pile of whataboutism.
I would also say that I wouldn't be at all surprised if idiomatic Python is actually quite a bit faster than idiomatic Haskell in some interesting use cases. Which does not at all mean that the opposite is true. There are always interesting cases that make good fodder for "what about" comments, but getting carried away with them doesn't really make for particularly edifying discussion. Just mentally append "in my experience" to everyone's comments and move on.
> I would also say that I wouldn't be at all surprised if idiomatic Python is actually quite a bit faster than idiomatic Haskell in some interesting use cases. Which does not at all mean that the opposite is true.
I'd be utterly amazed. Haskell is orders of magnitude faster than Python for "typical" code (having to do a hash lookup for every method invocation ends up being really costly). It's not a slow language by any reasonable definition. And the fact that you're suggesting it is suggests that the nuances are not at all obvious.
(Not trying to hate on Python - performance is a lot less important than most people think it is - just trying to put Haskell's performance characteristics in a familiar context)
It's not that much of a nuance, TBH. It's just that Python has largely become a high-level language for gluing together libraries that are mostly written in much faster AOT-compiled languages such as C, C++, Fortran, or even Cython. For example, I prefer to stick with Python for a lot of the number crunching things that I do because, while you certainly can beat numpy's performance in other languages, in practice it turns out that doing so is generally more work than it's worth. Especially if you're using the Intel distribution of numpy.
So, yeah, it's true, you do a hash lookup for every Python method invocation, and also you've got to worry about dynamic type checks for all the dynamically typed references. But the practical density of method invocations and dynamic type checks can be surprisingly low for a lot of Python's more interesting use cases.
Haskell doesn't treat all lists as linked lists. It treats linked lists as linked lists. It also has Seq (finger trees), boxed and unboxed arrays and various streaming libraries.
> I've had a similar experience at university while using Haskell. It's a very elegant lazy pure functional language, but it's glacially slow. The natural, low-friction path is very inefficient. The efficient path is unidiomatic and verbose.
Well, if you run a Haskell program on a "C-Machine" of course a comparable program in a "C-Language" will be faster — as it don't need to bridge any gap in execution semantics.
The point is: Mostly all modern computers are "C-Machines". Modern CPUs go even a long way to simulate a "PDP-7 like computer" to the outside world, even they're working internally quite different. (The most effective optimizations like cache hierarchies, pipelineing, out-of-order execution, JIT compilation to native instructions [CPU internal "micro-ops"], and some more magic are "hidden away"; they're "transparent" to programmers and actually often not even accessible by them). So not only there's nothing than "C-Machines", those "C-Machines" are even highly optimized to most efficiently execute "C-Languages", but nothing else! If you want to feed in something that's not a "C-Language" you have to first translate it to one. That transformation will almost always make your program less efficient than writing it (by hand) in a "C-Language" directly. That's obvious.
On the other hand running Haskell on a "Haskell-Machine"¹ is actually quite efficient. (I think depending on the problem to solve it even outperforms a "C-Machine" by some factor; don't remember the details, would need to look through the papers to be sure…). On such a machine an idiomatic C or Rust program would be "glacially slow", of course, for the same reason as the other way around: The need to adapt execution semantics before such "no-native" programs could be run will obviously make the "translated" programs much slower compared to programs build in languages much closer to the execution semantics provided by the machines hardware implemented evaluator.
That said, I understand why we can't have dedicated hardware evaluators for all kind of (significantly different) languages. Developing and optimizing hardware is just to expensive and takes to much time. At least if you'd like to compete on the status quo.
But I could imagine for the future some kind of high level "meta language" targeting FPGAs which could be compiled down to efficient hardware-based evaluators for programs written in it. Maybe this could even end the predominance of "C-Languages" when it comes to efficient software? Actually the inherently serial command-stream semantics of "C-Languages" aren't well fitted to the parallel data-flow hardware architectures we're using at the core now since some time (where we even do a lot of gymnastics to hide the true nature of the metal by "emulating a PDP-7" to the outside world — as that's what "C-Languages" are build for and expect as their runtime).
To add to the topic of the submission: Are there any HW implementations of APLs? Maybe on GPUs? (As this seems a good fit for array processing languages).
Great story, it’s stories like these that make me still hold onto my undergrad work (as bad as it is). Maybe one c#, Visual Basic, asp, php, t-sql and other esoteric projects will be looked at as relics of the past.
The APL language has this system variable ⎕IO (called QUAD IO -- index origin), which could be set to 0 (for 0-based indexing) or 1 globally in the environment.
Also, ⎕IO can be set as a local variable of a function to limit the scope of the indexing origin, which made it convenient to simplify the code to implement certain algorithms.
While studying Comp Sci at University of Toronto in the early 70s, I recall the 360/20 was used as a node to connect suburban colleges (Erindale and Scarborough) to the "mainframe", which was a 360/65 (I believe).
We would iterate from the keypunch room, to the 360/20's card reader, and wait for the mainframe to process our code and transmit the code output to the local printer, for review at tables large enough to page through fan-fold paper, to check and mark-up the code. Back then, we didn't need watches to remind us to stand up and walk around every 30 minutes -- the coding workflow forced us to!
Reading the Principles of Operations for the 360/20, the processor was supported half-word (16 bit), and had no floating point, but support packed decimal numbers. Our machine had 16K.
It was great to see the article -- made me reminisce about the days of me carrying boxes of punch cards and inches of output back and forth to my residence.
I downloaded a very high-resolution version of this map and created a 1m x 1m "poster" in my office. Accompanied with an illuminated magnifying glass, I often take a nice break from the computer monitor to examine the odd details Monte provides in his pictures.
I noticed the iron ring on your pinkie -- Canadian engineer?