2,803 karma · joined March 11, 2010
On the other hand, an abstract base class broadcasts the message that we should fold our new cases into the existing design -- that's what base classes are made for. But even when new cases don't fit neatly in the existing design, we often feel as programmers that we need to pay respect and deference to existing design, especially if it was made with extensibility in mind. And so we add more complexity (maybe we need an extra field or an extra method), make more kludges, and soon enough the original OO design is a mountain of complexity and has so much "gravity" that it's nearly impossible to escape it anymore -- nobody can imagine throwing it out and starting fresh, so it just keeps gaining complexity. And for all its complexity, it's also slower.
> The improvement in speed from Example 2 to Example 2a is only about 12%, and many people would pronounce that insignificant. The conventional wisdom shared by many of today's software engineers calls for ignoring efficiency in the small; but I believe this is simply an overreaction to the abuses they see being practiced by penny-wise-and-pound-foolish programmers, who can't debug or maintain their "optimized" programs. In established engineering disciplines a 12% improvement, easily obtained, is never considered marginal; and I believe the same viewpoint should prevail in software engineering. Of course I wouldn't bother making such optimizations on a one-shot job, but when it's a question of preparing quality programs, I don't want to restrict myself to tools that deny me such efficiencies.
That paper was published in 1974 and yet it captures the mindset of many a programmer in 2023 perfectly. The part I like about this paragraph is the "easily obtained" sentence; I saw a comment from someone mentioning that they made a JavaScript program 10 times faster by replacing the common functional programming combinators (map, filter, reduce, etc.) with for loops. I think most of us would say such a change is easily obtained, does not make the code impossible to debug or maintain, and gives such a massive improvement that it should be a no brainer to reach for it.
True, however we must be careful not to go completely bananas either; it's really hard for a reader to piece back together a full mental picture if the functionality is spread into dozens even hundreds of general-purpose 3-line functions.
Simple, `y = f(x)` types of functions are indeed the easiest to understand, but you might be no closer to understanding what the code is actually doing.
When I did web development, I saw it as a "hack" and a "failure to write clean code" whenever I reached for raw SQL. This is of course not true at all, but it was a powerful psychological blocker and I'd spend too much time trying to figure how to get the ORM to do what I wanted instead of writing the SQL myself and moving on to the next problem.
Sure, but let's also not confuse "optimal" with "reasonable". One of the major challenge of modern programs is how slow they are at every level. Very often, this bad performance can be attributed to a style of programming that tries to completely ignore that the program will run on a real machine with real hardware. A little bit of mechanical sympathy (e.g., operations that make good use of the CPU caches or don't confuse the branch predictor) can yield a program that is 10x faster than a naive implementation, with little to no loss of readability or maintainability. (In fact, as noted by Nelson Elhage [1], faster programs enable simpler architectures, which helps make them more readable and maintainable.)
In FP languages, programmers face an extra difficulty: the distance between the code they write and the machine code that will be executed is greater than in languages like Rust or Go. They will need to be knowledgeable about the language, its libraries, and its compiler to avoid the pitfalls that could make their programs slower than would be reasonable (e.g., avoiding unnecessary thunking in Haskell).
[1] https://blog.nelhage.com/post/reflections-on-performance/#pe...
So learn a framework for whatever language you use, that's useful! But also learn to recognize when you are working against the framework and would be better served with a few libraries that you can combine in the way that you need. Frameworks optimize for certain use cases and expectations, and if your project is no longer aligned with those, it's probably a good idea to think about an exit strategy.
And similarly, the performance potential of SwiftUI has not been unlocked. Maybe it will happen, but while we wait for the SwiftUI to implement those fabled optimizations, developers have to wrestle with—from what I gather from the article—a lot of extra accidental complexity so that they don't impose an unusably slow interface to their users.
Data-oriented programming is a style of programming that, as far as I know, originates in the Clojure community. It emphasizes using general data structures (vectors, dictionaries) to store all data and make code more re-usable. It has nothing to do with good cache utilization, pre-fetching, or avoiding branch mispredictions.
It's a shame that two styles of programming which are almost diametrical opposites share such similar names.
From the look of the article, it's discussing data-oriented design, but in Python, and I agree that it's kind of a weird match.
When I was a teenager, sometimes around 1996 or 1997, I watched the movie "Hackers" for the first time and my immature mind was immediately drawn to the idea of using computers to sneak into places you weren't supposed to. (The pretty graphics helped too.) I went online and I found an early version of John The Ripper and thought "yes, this is exactly what I want!" It would be a few years before I even learned what Unix and /etc/passwd were, but it felt so cool to have a real hacking tool on a floppy disk.
Unfortunately, in practice, it's not possible to be oblivious of the layout of data in memory if we want to write fast code. The CPU/memory speed disparity graph [1] shows the new reality for programmers: the slowest part of a program is bringing data from RAM into the CPU registers. Fortunately, modern CPUs have very fast caches that help amortize this cost and it's the responsibility of the programmer to organize data to take advantage of that fast hardware—the compiler cannot do it. That's why two functions, with the same algorithmic complexity, and which compute the same result, can have an order of magnitude of difference in performance between them [2]. The famed sufficiently smart compiler that can do those transformations does not yet exist as far as I know.
[1] https://gameprogrammingpatterns.com/images/data-locality-cha... [2] https://play.rust-lang.org/?version=stable&mode=release&edit...
As I grow older, I find myself wanting to use languages that are more conservative in their designs. Back in my 20s, I was all about expressivity and meta-programming: Scheme, Smalltalk, OCaml, and Haskell were my drugs of choice. I looked down upon those simple peons who used pedestrian languages like Java or PHP and I definitely looked down on those neckbeards who hadn't left their C caverns and didn't know the greatness of closures, higher-kinded types, hygienic macros and all those features that real languages had.
That's how I came to learn about Rust: in the early 2010s, there was a post on Reddit about a new programming language implemented in OCaml. Even before Rust 0.1 was released I was a Rust enthusiast. And what a ride it's been! Rust transformed and evolved a lot since its first days, but what a language it ended up becoming! In a just a few years, Rust went from being a hobbyist toy to an industrial-strength tool used by large projects and corporations like Mozilla, Dropbox, Discord, Amazon, Facebook, and more. I've been using it professionally myself for the past 5 years and it's been (mostly) a great experience.
But as Rust and I continue to change, it seems that the paths of our lives are diverging. As I gather more gray hair, I don't have the energy, the time, or even the passion for learning and master exotic language features anymore. I now avoid a lot of the "cool toys" that I was so enamoured with in my 20s. My programming style is now much closer to what it was when I first learned to program in Turbo Pascal in the mid-90s: mostly functions, arrays, and structs. Rust on the other hand continues its quest to be an industrial language that has ivory tower creds: the community and the core team enthusiastically look forward to having more ways to abstract code, more ways to express constraints at the language level. I feel that before long, my values and Rust's will have grown so far apart that we'll have no choice by to break up our long relationship.
So I've been looking for what else I could use for personal projects. (I think and hope that I continue to use Rust for my professional career.) Odin is at the moment my clear favorite, with Zig slightly behind, and Nim after that. I find that Odin's design represents my own values about what software and a programming language ought to be like better than anything else out there at the moment. It's high-level, has out-of-the-box support for dynamic arrays and dictionaries (I think, the two most useful data structures), and the whole language is simple enough that I learned most of it in a weekend. It does feel like a step back from Rust in certain areas -- null pointers, using product types for returning errors, no ownership checking -- but I think that I'm at a point where I'm ready to forgive and deal with these issues in order to have a tool that is simpler and doesn't evolve at the speed of 20 year olds.
> An enourmous amount of time spent profiling, benchmarking and optimizing things. The answer is different for every part of Bun, but one general theme: Zig's low-level control over memory and lack of hidden control flow makes it much simpler to write fast software.
With modern hardware, accessing memory efficiently is key to writing fast software. Programs written in programming languages that don't let the developer control how data is laid out in memory usually face an uphill battle to be fast.
Second, the justification is inconsistent with other decisions. E.g., the majority argues that it's up to the States to decide their own laws on the matter of abortion, but just the day before, the same majority found that New York could not decide its own laws on concealed carry. You mention that searching for "abortion" in the text of the U.S. Constitution find nothing: you'll also find nothing about concealed carry.
I really like the safety guarantees that Rust provides and I want to keep enjoying them, but the language -- and more importantly, its ecosystem -- is moving from something that was relatively simple to a weird mish-mash of C++, JavaScript, and Haskell, and I'm keeping an eye out for a possible escape hatch.
Zig, Odin, or Hare are not on the same plane of existence as Rust when it comes to out-of-the-box safety (or, at the moment, out-of-the-box suitability for writing production-grade software), but they are simpler and intend to remain that way. That really jives with my values. Yes, this means that some of the complexity of writing software is pushed back onto me, the programmer, but I feel that I have a better shot at writing good software with a simple language than with a complex language where I only superficially understand the features.
The tool described in this blog post is all about text-based rules: what the _text_ ought to look like. To improve your own writing (a) learn who your readers are, (b) know what they value, and (c) write so that as readers, they find your work valuable to them.
[1] https://youtu.be/vtIzMaLkCaM [2] https://youtu.be/aFwVf5a3pZM