Posts in Elixir (20 found)
マリウス 1 months ago

Hyperuplink: Discuss like it's 1998

Between the screeching handshake of a 2400-baud modem, the steady hum of a computer in Turbo mode, and the pale flicker of a CRT glowing at three in the morning, the internet seemed to have had something that, over the past two decades or so, it has completely lost: A soul . Communities made up of actual humans gathered around bulletin boards that loaded in a single request, where regular hardware could bring an entire world to your screen without borrowing half your CPU just to show you a list of topics. If you happened to have lived through that brief and peculiar moment in history, you probably still carry a fondness for those days, and you remember the strange little corners of the internet that you discovered, the hours you spent exploring them, and perhaps the friends you made along the way. Hyperuplink is my attempt at bottling those memories and the feelings attached to them, and pouring them into something that makes sense in 2026. It is a modern internet bulletin board that doesn’t require Telnet and works inside your browser, that renders proper, modern HTML5 and CSS on the server-side, that runs 100% JavaScript-free, and that ships as a single statically linked binary with no external runtime, no interpreter, no FastCGI , no , no , and not a single loose file spilled across your disk. It can talk to a PostgreSQL server or an entire cluster, it makes use of any Redis -compatible cache, and it comes with a whole set of nostalgic as well as modern themes. More importantly, Hyperuplink is fun, it doesn’t take itself too seriously, and it is the forum software for everyone who’s done wrangling with phpBB ’s runtime environment or Discourse ’s broken JavaScript UI. And yes, Hyperuplink , too, really whips the llama’s ass. The short version, for anyone who has been idling in the community channel or who already read the back-story in an earlier status update , is that back at the time I wanted a community discussion forum for the people who use any of the tools, programs and services I’m building , and I could not find a single piece of software that I actually wanted to put up with. I was looking for an internet forum that would resemble the good old BBS systems from back in the day, but that would allow people to use it from the comfort of their modern-day browsers. I also wanted something that … phpBB was the obvious first stop, because it has been around for decades and, unlike Discourse and Lemmy , it does not force JavaScript down the visitor’s throat. But phpBB is a monster that carries far too many features, takes time to install and configure, and, once you account for its extensions and runtime dependencies, demands a recurring maintenance ritual that I frankly do not have time for. Discourse and Lemmy , on the other hand, I won’t even consider to begin with, because they simply don’t work without JavaScript enabled. Everything else I looked at either missed something I needed, would have brought similar runtime headaches, or would have required me to fork it and maintain that fork forever, just to get a few of the features that I needed. So I did the reasonable, well-adjusted thing and started writing my own bulletin board software at the end of last year . Before writing a single line I sat down and weighed the usual suspects, PHP with Laravel , Python with Django , Elixir with Phoenix , Go with Fiber , and Zig with Jetzig . I did not consider server-side TypeScript for even a second, because the Node.js and NPM ecosystem is a dumpster fire of outright malware that I refuse to pick for even something as deliberately absurd as Hyperuplink . The scripting stacks make web development pleasant, and they abstract away the tedious HTTP, session and form handling so you can focus on the thing you are building, but every one of them drags a runtime and a maintenance burden behind it. My one goal for Hyperuplink was for a hobbyist admin to be able to run a board without babysitting a whole stack. From an administrative perspective I wanted to be able to update one or two binaries every once in a while and be good, without having to subscribe to e.g. the PHP security announcements, and its mailing lists, and its security advisories on GitHub, and the NVD only to make sure I’m not missing a critical CVE of one of the gazillion of dependencies. Go sits in the sweet spot between the low-level compiled languages like C , C++ and Zig that hand you performance at the cost of development speed, and the interpreted languages like PHP and Python , that make data structures a joy to work with but that are expensive to run. The deciding factor was that Go compiles down to one statically linked binary that you copy to any VPS and simply launch. The one downside is that Go is not exactly a “web native” language and has nothing like Django or Laravel to accelerate the boring parts, so I built my own small web application framework on top of the Fiber v3 framework and went from there. Hyperuplink is a single static binary, compiled with CGO disabled, cross-compiled to Linux, macOS, FreeBSD, NetBSD, OpenBSD and a long tail of architectures, so that deployment is nothing more than copying that binary into place. It is PostgreSQL-native and cluster-friendly and it uses materialised views to optimize read performance. Schema migrations are embedded and run automatically on startup, which means there are no external migration files and upgrades should be as easy as simply firing up the new version. In addition, a Redis -compatible service is used for caching, and sessions, and the async job queue. Profile pictures, attachments, and custom assets can either be uploaded to the local disk or into any S3-compatible object store (such as MinIO ), which is useful when scaling the service horizontally. Hyperuplink runs zero client-side JavaScript, meaning that every page is server-rendered HTML5 and CSS, and there is nothing that logs how your cursor drifts back towards that thread about whether pineapple belongs on pizza, just to collect data on your account. Speaking of which, accounts can sign-up/-in via local password, with optional TOTP two-factor authentication, but Hyperuplink also supports login through OAuth providers for the friends you are trying to lure over from other platforms. And for anyone who finds email too boomer , sign-ups and notifications work over XMPP , too. As for authorization, accounts can be assigned to groups with per-category permissions so the good stuff stays available only to the good people . Hyperuplink features a range of pre-built themes, some of which feature beautiful retro aesthetics that it owes to the classic-stylesheets project. There are also slightly more modern looks available, and with every theme the colour schemes are interchangeable, so a Gruvbox -tinted macOS 9 board is very much a thing. The bulletin board supports Markdown in posts, it offers uploading profile pictures and attachments, it comes with reporting and moderation features for the admins, and the UI supports i18n. Hyperuplink also ships with a REST API that I believe is friendlier to work with than what Lemmy or Discourse expose, and it even has its own TUI client with the official Hyperuplink integration into Neon Modem Overdrive . Hyperuplink is developed on tty.fail and mirrored to GitHub , the mirror being where the pre-built binaries and container images are built and hosted ( thanks for the free CPU cycles! ). Regardless of how you decide to run your bulletin board, you will need a PostgreSQL and a Redis -compatible server reachable, plus, optionally, an S3-compatible store if you would rather keep uploads off the local disk. The official repositories contain all the required documentation and configurations to get you started as quickly as possible, but the basic idea is that you can simply grab the binary for your platform from the releases page , drop it wherever you please, and run it: There’s a whole Docker and Podman (rootless!) setup available, if you would rather bring the entire stack up at once. The repository ships a / with PostgreSQL and Valkey and an optional MinIO profile: The Podman setup does everything the Docker setup does but rootless, and alongside the there is even a set of Quadlet units if you prefer using systemd . Kubernetes works, too, and a minimal with a couple of replicas and the config handed in through a is pretty much all you need. Since the uploads can use S3 the pods stay stateless. Ebuilds are available in the repository so you can compile it on your own Gentoo… server… I guess. I tried including all that’s necessary for Nix but to be fair, I’m not actively using it anywhere so consider this more of a proof-of-concept rather than an actively maintained thing. If you feel like actively maintaining the Nix part of things I’d be happy for you to reach out. The repository also contain the required init scripts for FreeBSD, OpenBSD, OpenRC and even a Service definitions for systemd . If you want to build Hyperuplink yourself, for which you need Go , it’s equally easy to do: The self-contained binary lands in , ready to be moved wherever you please. Note: Alright, alright, I lied, and you got me. No runtime dependencies was not 100% accurate and you’re probably staring at a forum that refuses to allow its users to upload profile pictures. The reason for that is that Hyperuplink has one runtime dependency, which is ImageMagick ’s command. The service must be able to find that binary in its in order for profile pictures to work. As for why, it’s a long story, but the tl;dr is that image processing is hard and there aren’t many people, me included, who feel like reinventing the wheel for, let’s say, the WebP compression algorithm, by building a native Go implementation. Because I explicitly didn’t want to use for the sake of preserving Go ’s easy cross-compiling, I decided that calling the binary was the most reasonable approach. After all, you’re likely to have ImageMagick on your system if you’ve ever hosted anything web related on there. However you end up running the process, put it behind a reverse proxy that terminates TLS, because in the session cookie is HTTPS-only and you most definitely wouldn’t want to run the forum in mode. Also, if you want the service supervised there are the aforementioned service files for systemd , OpenRC on Gentoo and Alpine, and rc.d on FreeBSD and OpenBSD waiting in the repository. Hyperuplink is open source under the SEGV License , the code is available on tty.fail with the mirror and the pre-built binaries over on GitHub , and everything else you could want to read about it is either available at hyperup.link , or within its own embedded manual that you can find under Help -> Manual . If all of this sounds like your kind of thing, come and say hello in the chatroom , show off your board once you have it set up, and if you would like to lend a hand with development or testing then reach out , because the best communities were always the ones with real people, putting in real work. can either use an existing database table to authenticate users, and/or… supports simple signups, ideally with XMPP JIDs instead of email addresses supports notifications and replies via email and ideally via XMPP is lightweight and does not drag along a mountain of runtime dependencies does not require users to have JavaScript enabled does not bury me under administrative features I will likely never touch is reasonably easy to theme and, more importantly, to maintain long term

0 views
danluu 2 months ago

How does programming language affect token efficiency and correctness?

This somewhat widely cited post (I keep seeing it cited, anyway) suggests that dynamic languages and/or languages that represent things more concisely are more token efficient. It seems to be cited enough that LLM search results agree. For example, when I searched for "dynamic vs static language token cost" (no quotes), Google's AI summary opened with Dynamically typed languages generally have a lower LLM token cost than traditional statically typed languages because omitting explicit type declarations makes the code more compact. Google's AI cited the same post, which suggests that some concise dynamic languages have maybe 1/2 to 1/3 the token cost of static languages like Rust, Go, C++, etc. The author says There was a very meaningful gap of 2.6x between C (the least token efficient language I compared) and Clojure (the most efficient). And then they later tried J, saying It dominates at just 70 tokens average, nearly half of Clojure (109 tokens). Array languages can be extremely token-efficient when they avoid exotic symbol sets. If token efficiency turns out to be a key driver, this is perhaps a very interesting way for languages to evolve. The other dynamic vs. static language token comparison I've found floating around is this one , which supports the same conclusion. If you want to treat this as part 8 of this series of exercises on benchmarking, evals, and experimental design , you can click through to the links and think about eval issues before reading further. Without running our own eval, one problem the first experiment has is that the problems are trivial, which we can see from quote above; a problem that can be solved in 70 tokens in J and 109 in Clojure isn't much of a problem at all (the author used Rosetta Code). As we saw when we looked at other evals of caveman mode vs. our own evals , you can get very different results from trivial problems where most of the work is in printing out an answer vs. slightly less trivial problems that actually require some amount of "real work"; the big gains claimed by caveman mode and shown in replications go away when you start looking at problems that take more than just a few tokens. In general, performance on trivial tasks doesn't generalize. The issues in the second link are a little more subtle, so we'll defer most of them to an appendix, but they include issues like one of the tests executing the wrong path (which doesn't exist), causing a test to fail. One of the later agents then symlinks the non-existent path to its own executable, which works for that case, but also causes every later test to run that one agent's executable instead of the correct executable. The author tries to draw conclusions about what it means that Rust had some failures, but all it means is that scoring for Rust ran before the Go agent symlinked all scoring on that broken test to the Go executable. Instead of relying on these evals, we can try running some of our own evals. As we can see from these evals as well as the evals discussed in our last exercises on evals , it's very easy to make an eval that doesn't say what the creator of the eval seems to think it's saying. No doubt these evals will not be an exception to this and will be flawed (see appendix below for more details). As a way to build my intuition about things, I like to pre-register guesses before looking at results 1 . Some things I pre-registered with friends were: For the first eval, I tried giving agents the zstd RFC (plus errata) and telling them to implement a complete zstd decoder (agents are stuck in a container without internet access). The tests were not given to agents. For something with the surface area of zstd, it's not really reasonable to expect that the tests cover every possible case. For example, even though zstd is a fairly well-tested piece of software, I once found a data corruption bug in zstd . The test suite isn't intended to find extreme corner cases that might be lurking for years and is instead intended to check various cases that can "easily" be derived from the RFC that should work. Below, the x-axis is cost and the y-axis is correctness score (up and to the left is better / down and to the right is worse); average result on medium and ultra efforts with GPT-5.6 Sol. If we only look at medium (and ignore the fact that results often wildly differ on different tasks), we might come to a conclusion like the Alderson evaluation, that dynamic languages are more efficient and better when using LLMs because (ignoring relatively obscure languages) the cluster of dynamic languages lands up and to the left of the cluster of static languages (we used Alderson's color-coding for static vs. dynamic to make it easy to compare at a glance). But if we look at ultra effort, the results are quite mixed, with a couple static languages doing the best, with more static than dynamic languages among the better results. The graphs below also have a toggle to convert the x-axis to time instead of cost. The noted that it's valuable to get results more quickly (I personally don't find this to be the case because results take long enough that I multitask instead of waiting), so we can also look at that. Similarly, we observe that neither language type dominates the other although, at medium effort on this particular task, the best dynamic language results are once again better than the best static language results (though, once again, they're fairly close). We can observe that, just like when we compared completely trivial caveman mode evals to a less trivial caveman mode eval, the very strong relationships that held in the trivial evals don't generalize to this larger case. As was the case there, the extreme ratios in performance go away in these larger evals, except in cases where we might expect poor performance, such as when using assembly (which would be significantly more time consuming and difficult for a human) and when using relatively obscure languages where we might not expect that AI labs are expending effort generating synthetic RL environment data. Note that this is the opposite of what the 1st eval found when it suggested that very dense languages like J would make sense for efficiency reasons. Perhaps using an obscure (and "weird") language can make sense if you have a very large budget and you can train or fine-tune a model to be effective for your pet language, but if you're a normal user of LLMs, it seems like sticking with a mainstream language is likely a better bet than using an obscure dense language. And it turns out that if we plot language popularity vs. performance on this eval (not shown), we observe a weak to moderate positive correlation where more popular languages end up with more correct as well as cheaper solutions. As we previously noted, very closely related evals can give substantially different results. For example, we saw significantly different results in the Optimization 1 vs. Optimization 2 evals here when Optimization 1 and Optimization 2 were optimizing bzip2 compression and decompression in wasm, which are fairly closely related tasks as evals go. To make a strong, universal, claim, like "dynamic languages are more efficient than static languages", we'd have to run evals across many tasks. However, showing that a claim like Dynamically typed languages generally have a lower LLM token cost than traditional statically typed languages because omitting explicit type declarations makes the code more compact. is maybe at best vaguely directionally true and not really relevant to any particular case and maybe not strong enough to be relevant in general, we just need to try a few cases and see that the claim doesn't generally hold. Above, we saw that at one effort level, the claim seems to maybe be kinda sorta true, but with exceptions, and then at a higher effort level, the claim seems to not be particularly true, which is sufficient to say that the claim is probably not universally true, modulo our eval having a confounder that completely invalidates it. But, just to get a view on a very different task that's also presented in a different way (more TDD-like than "read a spec"-like), this next eval takes the Pandoc ProgramBench eval and modifies it for our use case. Instead of the reverse engineering task presented by ProgramBench, we present agents with ProgramBench materials as well as the ProgramBench tests and then score agents against a holdout set of tests to measure the performance of each condition 2 . In the results below, the x-axis is cost again and the y-axis is score on the holdout tests. As before, we don't see a very strong relationship between success or cost and whether a language is static or dynamic or very dense. We once again see that relatively obscure languages tend to do poorly (although Clojure does much better here than on Zstd). Also, Assembly does much worse, which seems expected in that we would expect a human writing Assembly to be at much more of a disadvantage implementing Pandoc than implementing Zstd and there doesn't seem to be a strong reason to think that LLMs would be different in this regard. I have a lot of questions about what works well when using LLMs (such as, what test techniques work well, what languages work well, what software architectures work well, if bug fixing cost varies by language, if general program maintenance cost varies by language, etc.). Most of these questions are unanswered in public data and, if they've been answered in AI labs, the information mostly hasn't been made public. Most of the claims that get thrown around about how a particular language is good for LLM use seem to be wrong (e.g., the claim that Ruby, Clojure, and J, are particularly well suited to LLMs, which were mentioned in the evals linked above, as well as the somewhat common claim that Elixir is particularly suited to LLMs), but it's not clear what's right. In 2014, we looked at the literature on static vs. dynamic types and found that surveying the literature wasn't very informative outside of a few case studies. For an example that typifies a standard academic study, we saw the paper, Do Static Type Systems Improve the Maintainability of Software Systems? An Empirical Study, on which I commented: Subjects were given classes in which they had to either fix errors in existing code or fill out stub methods. Static classes for Java, dynamic classes for Groovy. In cases of type errors (and their respective no method errors), developers solved the problem faster in Java. For semantic errors, there was no difference. The study used a within-subject design, with randomized task order over 33 subjects. A notable limitation is that the study avoided using “complicated control structures”, such as loops and recursion, because those increase variance in time-to-solve. As a result, all of the bugs are trivial bugs. This can be seen in the median time to solve the tasks, which are in the hundreds of seconds. Tasks can include multiple bugs, so the time per bug is quite low. Picking tasks that avoid "complicated control structures" such as loops and recursion, where tasks take hundreds of seconds makes the result meaningless with respect to tasks that really eat up a professional programmer's time, just like the first eval we saw where tasks took high tens to low hundreds of tokens. However, with LLMs, we can actually feed them non-trivial tasks and compare how they do. There's the issue of how well results generalize to different tasks, but we'd have that exact same issue with human studies, but worse (LLM variance is huge, but human variance is even huger since you can't get the same human to do a bunch of tasks with different seeds). And while $20 to get an LLM to implement a Zstd decoder isn't exactly cheap once you multiply by the number of languages and the number of iterations per condition per language, if you think about how much it would cost to hire a professional programmer who can read the zstd RFC and implement it, there's no way the equivalent study would've been done because the cost would've made it completely infeasible. That goes double for the Pandoc task. With LLMs, a lot of the questions have gone from being effectively unanswerable to being answerable with a bit of effort and some tokens. Due to the incentives that are in play 3 , it's not clear that we'll get answers to questions like this any time soon, but it's at least possible to take a crack at it now. There are a lot of claims I've seen floating around that these evals can't prove or disprove (for the reason noted above that, due to the variance across different problems, many more tasks would have to be tried), but that these shed some light on, such as: For my pre-registered guesses, we had By the way, a major reason Clojure improves by so much in the Pandoc eval compared to the Zstd eval is that, in the Zstd eval, 36/40 medium and 5/40 ultra Clojure programs had test failures because conversion throws on (maybe should've been used?) and they used this conversion inappropriately. That's a real result, in that, if you ask the best publicly available GPT model to implement Zstd (and presumably if you do other bit/byte manipulation tasks where this might come up), it will emit code that fails in this particular way. If there are tests that catch this, the bug will get fixed, but it will still cost time and tokens. Whether or not a language did well, there are costs like this all over the place (for example, cargo repeatedly gets invoked with the wrong arguments, which then immediately gets caught and fixed, but I've noticed this loop can actually consume a decent amount of wall clock time on my real projects unless you give explicit instructions to codex on how to invoke cargo, and it's clear that's worth the space in the context window). Anyway, all of this is an illustration of why, if someone wanted to make a strong claim about which languages or classes of languages are particularly good with LLMs, they would need to run quite a few different evals. If we dig into why any particular condition got a certain score, the failures that caused the score are generally something idiosyncratic where it's not always obvious how much the issue generalizes across tasks or across setups. There's no way to look at the score on one eval or even five or ten evals and draw a conclusion about programming in general. It's true that, in both the Zstd eval and the Pandoc eval, we see a correlation between language popularity and positive outcomes (higher correctness, lower cost, lower wall clock time) and it seems plausible that we'd see this across other evals, but it would be a mistake to draw a strong conclusion about any particular language. I gave a warning like this back when I looked at how often different projects have a broken build according to GitHub CI data , noting that there are different reasons that a build might be broken more or less often across projects and that one shouldn't draw strong conclusions because results across projects aren't necessarily comparable (for example, if one project's main branch is some kind of release candidate that's gone through other vetting, that project would be expected to have low build breakage, but that's not comparable to a project where people are developing directly against main). Shortly afterwards, someone involved in one of the languages with a high score (IIRC, it was Martin Odersky and Scala) tweeted out the post and cited the language's high ranking as a victory for the language. That was an unwarranted conclusion there and, due to the many sources of variance that are in play here, any such conclusion about a single language would be even more unwarranted here. This data (assuming eval validity) can refute some strong claims and is suggestive of other claims, but it can really only be suggestive of things for classes of languages and not for particular languages due to having only two tasks, which any particular language could do well or poorly on for some idiosyncratic reason which may or may not generalize to other tasks. Thanks to Max Bittker, Yossi Kreinen, Aaron Levin, Alan Boll, Luke Burton, Marco Primi, Milosz Danczak, Justin Blank, and Tom Adamczewski for comments/corrections/discussion. Like I said above, my eval here is a quick and dirty eval and I'm sure it's full of flaws, so I'm not trying to say the evals I've presented here are great and this is bad, but here are a number of issues in the Endoh ai-coding-lang-bench eval. One issue is that the wrong executable appears to have been run for some of the tests. The setup for the published run seems to have executed inside each candidate's directory for one of the tests when the candidate's generated executable is at . doesn't exist. Because statically typed languages had a lower correctness score, the author of the eval noted "the only failures in 600 runs were in Rust and Haskell (both statically typed, both relatively "difficult" languages)" and suggests that "difficult languages", such as "C's memory management, Rust's ownership model, and Haskell's monads/purity may add overhead for the AI". However, Rust's failures were because there is no executable at , causing the test to fail. The first Go run "fixed" this by executing and linking to its own run, but this means that every later execution (for every language) actually executed the first Go run's executable. On rescoring Rust against its own executable (as opposed to having it fail by trying to execute a non-existent file), Rust gets a perfect score, invalidating the theory that Rust had failures because it's a difficult language to deal with. Other tests also have issues. For example, two tests have a structure that causes them to pass regardless of the actual value being checked. One of the tests has The inner has a in both branches, meaning that this is almost equivalent to The inner appears to be intended to have the actual check, but due to a coding error (perhaps a copy+paste error?), the check is effectively elided. Also, as noted above, agents can modify the test environment, which the 1st Go agent did to fix a broken environment. They have full access to tests and the environment and can do anything and the test suite is visible during development with no holdout, which can easily lead to cheating by special-casing code in a way that passes tests but creates a program that's useless "in real life". At a high level, something like this seems to have happened in that many programs fail to implement large parts of the spec but do pass all tests, which may indicate that the agents "understood" how to pass the tests and preferred that over implementing the spec (it could also indicate that the tests are very thin and are easy to pass). Another issue is that the Claude Code CLI versions aren't the same for all runs (it varies from 2.1.66 to 2.1.68). There are a handful of other issues like this that could be significant, but are likely small compared to the issues noted above. As an example of something we can compare, I was curious how cost effective using medium + asking the agent to keep working would be and then, in the back of my mind, I also had this question about something "Ralph loop" advocates say, that you're better off clearing the context window on every iteration of the loop and giving the agent the full prompt again. As with the above, my pre-registered guesses here are: Below, we have the average result for medium in a loop vs. ultra, sorted by best to worst ultra correctness score, for a prompt that simply resumes individual runs that don't have 100% test correctness as well as a Ralph-loop like prompt that discards context and gives the original prompt again (x-axis is cost, y-axis is number of correct test cases): For this one problem, on average, running ultra once seems better than repeatedly running medium per unit cost (and much more so per unit time) and continuing with previous context outperforms Ralph. The problem with naively running medium on repeat is that the agent can get anchored to a bad solution and fail to make progress. The theory behind the Ralph loop is that you throw away bad context which can cause this to happen, but that doesn't save you from having a bad artifact. Just from using LLMs, I've noticed that you're often better off throwing away a chunk of code and having an LLM re-write it from scratch than you are having an LLM modify it or try to re-write it in place. Michael Malis, who's been re-writing Postgres in Rust and has been making major changes has also noted this. This also relates to this idea noted previously that, due to the high variance (plus this path dependence) you're often better off rolling the dice multiple times and taking the best result, if you don't mind spending the tokens. It's hard to say too much about static vs. dynamic languages from looking at just this one condition, but a naive thought like "static languages will outperform when iterating" isn't obviously true. If there's one pattern that jumps out at me, it's that the cases where the Ralph loop most badly underperformed continuing with context were generally dynamic languages. It's possible this is because of the lack of type information, but we'd need to both look at the differences in trajectories in more detail as well as look at other examples to observe if that's a real pattern. Even if you don't care about Ralph loops now that the Ralph loop trend has passed, being able to make changes to a codebase more effectively when starting a new task or starting with fresh context is something you might care about and the pattern here is suggestive of a possible advantage. I tried to do a third eval that seemed like a more "business logic" kind of eval in both how the problem is presented and the actual execution of the problem. You can argue that the Zstd eval and the Pandoc eval are quite unusual tasks for a programmer to face in that not many programmers receive a specification as well-written and thorough as the Zstd RFC and not many programmers are handed a problem with as many pre-created tests as you get from ProgramBench tests. The idea here was to implement a board game. In general, board game rules are written by people who aren't experts in writing clean specs, so implementing a board game is more like what happens when a non-programmer (or a programmer who isn't an expert at writing good specs) gives someone a task. The problem here is getting a game where I have a reasonable oracle for scoring that isn't trivial for LLMs. For example, LLMs were able to one-shot the rules for Scout and Azul , which make those poor tasks. For games that an LLM won't immediately one-shot, I happen to have an oracle for Guards of Atlantis 2 because I had an LLM implement a copy for me and my friends to play (no link for this one because I don't see how to make an interface that's free of copyright infringement). The backend only took a few hours of my time, but it took a fairly large amount of LLM time to get the rules to be roughly correct. I like this as a task in that the rules are tricky in the same way a lot of problem descriptions that are delivered to programmers are tricky, but it is, in principle, possible to figure out the correct rules and implement them (after all, humans implicitly do this when they play the game correctly offline). In board game rules, it's fairly common to have rules where reading the rule strictly as written is incorrect and you need to use "common sense" (or read some kind of FAQ) to play the rule correctly (there are some game designers who strive to avoid this, such as J C Lawrence, but this is fairly uncommon). Guards of Atlantis has quite a few rules like this. The designer of Guards of Atlantis is also vocal about there being no such thing as the spirit of the rules or common sense interpretations of the rules and says that you should always read the rule exactly as written, which creates two difficulties. One is that there are also many cases where you need to ignore the "common sense" interpretation and read the rule exactly as written. The other difficulty is, as anyone who's ever tried to write a formal spec knows, it's very easy to accidentally have ambiguity or contradictions. Even people who do this profesionally are unlikely to be able to create a non-trivial, complete, clear, spec without formal methods or a very large amount of human review. Realistically, a board game designer who doesn't have a background in writing formal specs doesn't have a chance, and thinking that it's easy (as the designer seems to) reduces the already low odds even further. A nice way to mitigate this kind of issue to write down your intent or "the spirit of the rules", but because the designer says that there is no such thing as the spirit of the rules and you should read all rules exactly as written, there are no meta-comments in the rulebook that would help someone interpret confusing or abmiguous rules. This combination is quite difficult for LLMs (and, judging by the rate at which I see humans play the game according to the designer's intent, it's also quite difficult for humans). I think it would be effectively impossible to just read the rules and play correctly (of course it would be possible, but it would require knowing which rules are to be read as written and which rules are not, which one would have to do randomly and get lucky as the rules don't define a consistent system that one could use to infer which rules obey which meta-ruleset). When I was implementing the game, in order to get my LLM to understand the rules, I gave it various resources such as an unofficial rules FAQ (which is correct), an unofficial short version of the rules (which is better written than the official rules and correct, but incomplete), an opening book (which can be used to test rules against on the assumption that the opening book only contains legal moves), comments from the rules channel on Discord, etc., and had the LLM do consistency checks across these with the understanding that things like the FAQ and the Discord comments have higher authority than the actual printed rules. One additional source of difficulty is that the designer is frequently delibrately unhelpful when answering rules questions. He often likes to make fun of people who ask rules questions or played rules incorrectly, which has a chilling effect and reduces the number of rules questions (multiple people have said that they don't ask rules questions because of how the designer behaves), and when he does answer questions, it's often with something like a meme image that says "reading the card explains the card". To extract the information, the LLM has to process these meme images, and then there's often no information or delibrately round-about information, such as, in the case of ambiguity, a referenece to a particular section. When people do point out contradictions, the designer often says it should be obvious which side of the contradiction is correct, which may be true for a human who's kept up on all rulings to date, but current SOTA LLMs find many of the designer's rules clarifications unhelpful. Yet another source of difficulty, perhaps related to the designer's propensity to make fun of people who ask rules questions or are confused by rules, the game's interface seems almost designed to trick people into doing the wrong thing. There are multiple design affordances that I've seen trip up most new players (even if you explain to the UI trap to them, there are enough rules to take in they often forget, and then when it trips them up, they'll say something like "I'm an idiot, you even explained that to me twice"). It's not clear if these traps were created to give the designer people to make fun of, but that's certainly one result. Another is that LLMs struggle to understand the games rules and UI. With my $200/mo personal OpenAI/codex account, I let an LLM use all my spare capacity to run consistency checks and make rules fixes. I didn't closely track how long this took, but I think it was something like a month or two of cranking on fixes like this to get a somewhat reasonable result that's playable, but that I wouldn't really trust to be correct. The only reason I somewhat trust this is that Pedro Oliveira also implemented Guards of Atlantis and they used a completely different approach (a more standard approach of having a human drive an LLM rather than trying to get the LLM to figure things out itself). When we compared implementations, we found maybe 10-ish bugs in each. There are probably some remaining bugs where both of our implementations incorrectly do the same thing and perhaps some where our implementations differ but the checking system didn't notice, but I think the rules for both of our implementations are now reasonably solid. That's how I have an oracle for this game. I like this as a task because it feels more like the kind of "specification" you get in the real world, where the spec is ambiguous and contradictory and sometimes just plain wrong, and then you need to use other information to get a correct result. For this eval, to avoid having it be a test of how well LLMs can access data in annoying formats (such as converting the opening book from a set of images to some kind of structured data, converting a scan of the rules to text, etc.), I gave agents both the originals of anything where I directed an LLM to extract the data (which also required various consistency checks to get correct) as well as the the extracted data (the originals were presented so that LLMs could check the originals for extraction errors if they chose to). While I did this task with older models (I did a chunk of it with GPT-5.1 or 5.2, and then another chunk with 5.4 or 5.5), with newer models but without the kind of guidance I gave to the older models, the task was still far too hard. Regardless of language, agents scored approximately 0 on this task. BTW, if you're curious what LLMs (and humans) struggle with, here are some examples. There's one card whose text reads "Target a unit adjacent to you. After the attack: may repeat once on a different enemy hero." In this game, a hero is a type of unit. Read strictly, with full knowledge of the rules, e.g., what "After the attack" means, etc., this should mean that you can either attack a single unit or you can attack two heroes (after all, to repeat the attack on a different enemy hero would mean that the first unit was a hero; otherwise it would be a different unit that is a hero, not a different enemy hero). This card actually has what is effectively an errata printed on the card because people complained it was unclear; the errata reads "(You may repeat even if the original target was a minion)". That's already confusing to LLMs (and some humans), but the real killer here is that there are other cards that use the same construction and don't have this correction. To play other cards with the same construction correctly, you need to know that every time this construction is used, you should play it with the errata that's on this card. There are a number of constructions the game designer likes to use that have a specific non-literal meaning that you have to keep in mind. Another example of a rule that shouldn't be played in the obvious way is a character with a card which reads "Choose one, or both, on different targets: A, B". Reading this strictly as written, one would expect to be able to, on different targets, do either A or B, or both A and B. But part of the spirit of the game is the meta-rule that a character can't attack another character multiple times with one card , so the interpretation that you can do what the card says and do both and A and B on some number of different targets can't be right. Based on similar deductions and how similar constructions are used, the way this card is supposed to be interpreted is "Choose one, or both on different targets", which is arguably still ambiguous and could be more clearly written as "Choose one or both (must be on different targets if both)". As a human, once you understand what the "spirit of the game is", you can resolve these kinds of things. But, by design, this isn't written down clearly in the rules and one has to infer this from Discord discussions, which appears to be beyond the capability of today's models even though humans who are outperformed by today's models on many specialized tasks are able to do this. When I was supervising the LLMs that implemented the rules, the reason LLMs reached a ceiling and didn't converge to fully correct rules was that an LLM would observe that a rule was inconsistent and incorrect. It would then try to fix this rule and would also fix other things to try to make them consistent and correct. This would sometimes make things more correct and sometimes make things less correct. When making things less correct, the LLM would sometimes modify an existing correct test to turn it into an incorrect test so, after a while, the LLM wasn't really improving correctness and was just churning on which rules were incorrect. That was with some guidance on what to check and how to check it; without that guidance, even with the more advanced models that are available today, LLMs were unable to navigate this in a reasonable way. I'm sure there is a board game of the right rules complexity to make for a good eval here but, by definition, this would be something where it would take some work to create the oracle and I don't have an oracle handy for a board game with the right rules. If my goal were to make evals, I would've used board games with actual game replay data to get good tests or oracles for a whole bunch of games, but my goal was to play a particular game with some friends. But, if one were inclined to try this board game thing, it should be possible to create hundreds (thousands?) of these in a scalable way, so one could get a reasonably correct oracle for hundreds or thousands of games and then check which games are at the correct level to be an interesting test for LLMs today. This is arguably a bit of a funny problem in that, given a clear spec, e.g., a clearly written set of rules, an artifact that's more complex than Guards of Atlantis can be implemented by LLMs (I would argue the Zstd RFC is more complex, and Pandoc certainly is; even individual document formats Pandoc supports, like PDF, are more complex than Guards of Atlantis), so the problem isn't finding a game with rules that are complex enough that LLMs struggle and the problem is more about finding a game with rules that are ambiguous or contradictory enough that LLMs struggle, but not so much so that LLMs are completely hopeless. This is an actual real-world problem, in that humans are generally not very good at writing clear specifications and how well models and harnesses can handle a human's unclear, contradictory, and sometimes just plain wrong, specification is probably more relevant to the typical user than how well an LLM can implement something from a specification as well-written as the Zstd RFC or how well an LLM can implement a problem when handed the 4800 ProgramBench Pandoc test cases plus documentation. And these problems seem solvable in principle, in that humans who want to play board games correctly (even ones who would have no hope of "playing" Zstd correctly, let alone Pandoc) are generally able to navigate the mess of information out there to figure out what the rules to a board game are. If we look at it form the other side, this is suggestive that, to get an LLM to do something, maintaining a clear, canonical, spec is an effective way to work. When it comes to performance benchmarking, I've done enough of it that I feel like I generally know how my benchmarks are flawed and I can make an informed time/effort vs. flaw tradeoff and I have decent confidence the flaws that exist in the benchmarks aren't material to the thing I'm trying to understand. I haven't done enough AI evals to have this kind of feel for AI evals so, at a meta level, I would expect any AI eval I do to have some unknown-to-me flaws. Another reason I would expect some flaws here is that I had coding agents set up these evals and every time I spent a minute looking for issues I would find at least one issue. This indicates that it's fairly likely that these evals have additional flaws that could be uncovered by looking a bit more, but I wanted this to be more of a "quick toy project" level of correctness than a "Gary Bernhardt" level of correctness, so I stopped after fixing a handful of issues. Back when I was working as a verification engineer, I attended a meetup by a Sun/Oracle engineer in Austin, maybe around 2007 or so, where they mathematically formalized this idea of converting the time between bugs to a level of confidence in a chip release. I haven't seen people do this much, but I recently heard Will Wilson (co-founder of Antithesis) mention that some folks at Antithesis used math from ecology (the literature on rare species observation) to estimate true bug rate, which seems like a much more sophisticated version of what this engineer at Sun/Oracle was doing a couple decades ago. That's a cool idea, but when you're finding a bug every minute you look, you don't need fancy math to tell you that there are probably a lot of other bugs. If I were doing this for work and we had some reason to care about the fidelity of these evals, it would probably make sense to look at these more closely and fix more issues (and I would probably have the skills and experience to make fewer mistakes in instructing LLMs to set up these evals if I did this kind of thing for work). But, for the purposes of answering the question "is the claim that dynamic languages are meaningfully better than static languages when using LLMs?", I have a little more confidence that the claim isn't true, and there are a lot of other questions that seem more likely to yield some kind of actionable result (such as, what techniques or test libraries work best). I normally don't publish things on the blog until I feel like they're somewhat solid, but this means that I often explore some data enough to satisfy my curiosity and then never publish the result. From talking to people about these non-published results, people I talk to are often curious about the results even if they're not done to a standard that I really like, which seems like an indication that folks I don't talk to might be interested as well. From what I've seen so far, I suspect it would take at least 10x the time I've put into this to get this to a standard I really like. I'm fairly busy at the moment and can't see myself having the time to do that for months, at which point I'm not sure I'd really ever get around to publishing this. In a recent post, I mentioned an analysis I did almost a year ago where I was trying to understand which cars are better for concussion risk in accidents, where I spent some time figuring that out, got far enough to get an answer that satisfied me, and then didn't ever get around to doing the work it would take to clean up the result enough to publish it. There are some results from that analysis seem "publishable", in the sense that they could turn into a published paper (such as finding from actual crash data that the relationship between HIC and velocity looks like it's to the fourth power (!); there's a paper that tried to find this relationship, but did the wrong kind of analysis and wasn't able to find an "O(n)"-style relationship and had something much fuzzier), but I've never really cared about whether something is a paper or a blog post and it turns out that I'm more likely to just move on to the next analysis instead of cleaning up the analysis enough to publish a post. A more recent project along these lines is that, after making a superhuman Azul AI, I tried to make a superhuman Splendor AI using a much less human-time-intensive process. I believe that didn't succeed, but it beats every other Spelndor AI I could find by a good margin, which is a mildly interesting result. I think I know enough about board game AIs to write something up about them, but my main interest was in figuring out if I could get something decent, and then I keep just doing other projects instead of spending the time to do a nice write-up. An example of something I think is interesting there is that a lot of the performance optimizations you want to do actually change the result, so you can't only rely on optimizations that can be strictly checked to not change the result. But, if you naively ask a coding agent to do these optimizations in a way that doesn't reduce playing strength, they'll do all sorts of things that reduce strength. Cases where the strength reduction is very severe are easy to catch, but there are more subtle issues that sometimes result in (for example) no change in strength vs. your own AI in self-play but a reduction in strength against humans or other AIs, so some kind of process to catch bad optimizations is necessary, and it's inherently a kind of arbitrary process that has to be designed using some combination of your intuition and relying on LLMs (which will be very helpful but also often completely wrong). For these kinds of data-y projects that I'm interested in, LLMs massively reduce the amount of effort it takes to get a result that's strong enough to satisfy my curiosity but, AFAICT, they don't reduce the effort it takes to publish a result by much (at least if you write up results by hand instead of having an LLM write up the results and you want the results to be nice and clean), which means that writing them up runs into a kind of Ahmdhal's law bottleneck, so I've been doing more projects like this and writing up fewer of them. If anything, I think it actually takes more time to write these up because of how I've changed my workflow. For example, instead of just outputting some graph from ggplot2, I'll make a version an interacive version that's nicer in some ways, but definitely takes more time to produce. And I run an LLM spell/grammar check pass (at least so far, that's the only LLM assistance I've used for writing), which turns up a bunch of issues to be fixed. Since I look at each one manually instead of taking the fixes (and I make a lot of typos), that's actually fairly time consuming (over an hour on my last post and over half an hour on this post even though I didn't even make corrections all the way to the end and abandoned the process maybe halfway through). Anyway, publishing this is an experiment in publishing some half-baked notes instead of having the kind of cleaned up version that I'd really like to have before publishing something. If you have opinions on this, please let me know ( X Bsky Mastodon )! I don't have GitHub links to the current evals. On the one hand, I feel like I really should. On the other hand, they're a mess and there's a bunch of stuff I'd want to clean up before publishing the code, and I don't know if/when I'll get to that and this way, at least I'm putting something out there instead of just talking to a few friends about the result and then having the result sit on my hard drive indefinitely? Agents were instructed to ignore performance, but the timeout wasn't infinite and, under the medium condition, some test cases timed out. This is arguably unfair, but this didn't materially impact the score. For non-infinite loop timeouts, there were 2 test cases in Clojure (across 40 * 34 tests), 2 in J, 2 in Tcl, 1 in Factor, and 1 in PHP. And, at 9000s (2.5h), the timeout was fairly generous considering that the largest test case was 4 GiB. Failing to decode 4 GiB in 2.5h is an implied rate of less than 0.5 MB/s on a Graviton 5 core, which is quite slow. Here are some of the issues that I ran into when trying to get agents to set this up (and, as noted above, the short amount of time it took to find each issue implies there are more issues) There's one thing which arguably wasn't a bug that I removed anyway. One of the tests was very hard (maybe 10% of agents passed the test on the first try). On testing the current zstd release binary, the zstd binary also fails this test. On reading the RFC, this seems to be an ambiguity in the RFC about the legality of a certain edge case. There was fairly strong clustering with respect to which languages passed this test case more frequently, which I think is interesting, but doesn't seem like a very useful thing to measure when all of the other tests are measuring (or at least attempting to measure) something more straightforward. Anyway, in the above list (which is not exhaustive), many of the issues impacted a large fraction of languages and some issues had to be fixed multiple times. All told, if you count each condition as a separate bug, I probably fixed (had agents fix) over 100 of these bugs and I expect there are more. When I talked to Max Bittker (who runs an RL environment startup), he noted all the evals I've worked on, I ended up putting a huge amount of time and effort into, mostly in the form of reading trajectories (or summaries of many trajectories) and then triaging issues , e.g "oh this class of bug shouldn't be possible, lets update X "(X being the prompt, the harness/ environment, or the verifier)" agents tend to slop this up, so I put a lot of care there to make sure things get fixed at the right layer, for instance it's very sensitive what's in-context for the agent under test (bad to add random junk it has to worry about, or at worst leaking answers) vs whats fixed behind the scenes in other parts of the system. agents, when writing evals, are not sensitive enough to the experience of the agent under test, and will just give it the answer or fix problems by making it the inner agent's problem ("remember to not reward hack plz") I also have had a lot of success re-using existing things (repos, games, tools, levels) and building harnesses and verifiers around them, versus trying to make something from scratch for an eval by prompting In retrospect, I sort of regret doing a cross-language eval. Even after fixing 100 or more eval issues, I have no doubt that plenty more remain. Maybe this is just a "grass is greener on the other side" thought and I'll also regret the next eval I try, but I think it would've been a lot less work to try to evaluate how well different test techniques or testing frameworks work than to evaluate different languages and I find that topic at least as interesting. And, in retrospect, had I done a lot more work by hand and relied on agents less, this would've gone a lot better. For example, I should've had agents produce an environment for one language and then both had agents inspect it and inspected it myself and fixed the issues before producing the environment for another language. After doing this a few times, I might've had a better setup for producing environments for other languages (and if not, I could've just repeated this process for each language and gotten a more reliable result, likely without even taking more time). Another thing to note is that a number of things that are genuine differences in languages weren't really tested, such as memory safety against adversarial inputs. If agents had a harder time producing generally roughly correct code in C or C++ than Rust, that would be observed, but if a fuzzer or valgrind or other tools would turn up issues, that's not likely to be captured in the small set of tests. Just out of curiosity, I asked an agent to (briefly) check the Zstd C and C++ code for memory safety issues. The agent claims it ran the C and C++ code under ASan+UBSan and tried a few fuzz inputs (4000 each) and didn't find issues, but of course that doesn't mean there aren't issues or that a larger codebase wouldn't have issues. And, in fact, doing an analogous quick check for memory safety issues for the Pandoc eval found memory safety issues in all of the C programs and all but one of the C++ programs (the issues were things like incorrectly dereferencing out-of-bounds memory; one specific example is that, in one of the C programs, a truncated LaTeX table could result in an out-of-bounds memory read). The fact that these issues were findable with 10 of seconds prompting indicates that many such issues could be found and fixed without much human effort, but it would cost quite a few tokens and would push the cost of the C and C++ versions well beyond the cost of the Rust version and after doing all of that you would still have less confidence in the memory safety of the C and C++ versions than in the Rust version. Anyway, if you're curious about the distribution of results, we have the following for medium and ultra: I don't love that the ultra results are somewhat saturated here, but one "problem" with testing ultra is that it will keep going for a long time as problems get harder (e.g., most of the Pandoc ultra runs ran for 12+ hours, and the assembly runs went for much longer), so the things that don't get saturated are very large tasks, like the Pandoc eval, or tasks that are too difficult in some way, like the Guards of Atlantis eval. The holdout tests seem necessary because, without them, agents cheat and will detect a test input and hard-code the passing test output (they sometimes do this even when instructed not to cheat). If all cheating was that blatant, that wouldn't be a problem (and could be an interesting thing to measure, as agents differentially following directions or not across languages is something that matters to real users), but a lot of the cheating is more subtle and difficult to adjudicate. For example, some agents wrote code that branched off of the structure of the tests, but then filled in the contents of the branches with code that wasn't special-cased to a single test result and could pass many variants of the same test. For any point on the spectrum from "definitely not cheating" to "obviously cheating", some agent tried it. As we saw when we looked at Senior SWE-Bench , LLM scoring of evals is tricky and a great way to introduce both bias and variance; using a holdout set of tests has some problems, but it lets us avoid this much larger set of problems. For one thing, the holdout tests are suspsicious because they were created by agents. The intention was to create holdout tests that a reasonable person (or agent) would be able to make pass if they're not cheating. Agents audited this set of holdout tests for cases where this wasn't reasonable and eliminated some, but I didn't check these by hand, so I find it likely that there's at least one holdout test that's unfair in some way. However, the overall score against holdout tests is low enough that I'm not too worried about a small number of tests being bad (if I worked at an AI lab and was trying to train next-generation models, I would be more worried about this, but I don't think it's material for our use case here). Instructing agents not to cheat while having a holdout set of tests didn't prevent blatant cheating that scored extremely poorly on holdout tests, but telling agents that there was a holdout set of tests they were graded against seemed to reduce the score they achieved on the agent-visible tests while increasing the score they achieved against holdout tests (without telling them this, a number of agents achieved 100% on the Pandoc tests with uselessly brittle code; on telling them there's a holdout, no agent scored 100% after 1 turn on ultra, but the holdout scores were substantially better, indicating better generalization). There are various Substacks, YouTube channels, and other things that promise to tell you the secrets of LLM coding success, but the ROI on spending time running actual experiments isn't really there. When we looked at caveman mode , we saw that one of the biggest programming YouTubers had a video where they spent a few minutes looking into it and decided that it worked. Spending even 15 minutes looking into whether or not it really works is probably negative ROI compared to spending that time producing more content instead . There are various papers that discuss different techniques, and these sometimes go into more detail than most blog posts or videos but, on average, they don't necessarily have more useful information. For example, when I asked ChatGPT (5.6 Sol, Pro) to find discussions of language effectiveness with respect to LLMs, it turned up this paper on token efficiency , which has an interesting idea, but has the same issue as the caveman mode evals we discussed earlier, where it's not looking at a task that's interesting enough for the result to be relevant to me as a programmer. Just seeing what cited that paper, we find this paper by three academics on token efficiency of languages titled "The Best Programming Language for Tokenmaxxing" , but compared to this post, that paper only compares four languages, uses worse models, and uses small toy problems (from something called LiveCodeBench; the cost to solve problems with GPT-5.5 is often on the order of 1000 tokens). Regardless of how well done the eval is, as we've noted in this post and in our caveman mode eval , we often see wildly different relative results when going from a small toy problem to a problem that I might care about for hobby projects or work. Also, in that paper, they note that they gave the prompt "To test your program, run exactly ./test.sh... These are the only tests I care about" and they say this is realistic because "We believe that this setup is a realistic way to study agent behavior: in everyday use, programmers don’t hide their tests from agents. Instead, programmers direct their agents to keep working until all tests pass." but, as we noted above, doing this results in brittle code that fails in the real world (or if you have holdout tests that aren't given to the agent, it fails the holdout tests at a very high rate; this problem cannot be solved by just adding a few more tests; it can perhaps be addressed via something like fuzzing or property-based testing, but how well that works is a topic for another post). I'm not saying these papers are bad or that there isn't something interesting to learn from these papers, but as a programmer who wants to know what techniques or tools I should use, I can't get that information from papers like the ones linked above. [UPDATE: Tom Adamczewski sent me a link to his paper, https://arxiv.org/pdf/2606.30182 , which does handle a lot of the issues mentioned above. Relative to this post, it tries a lot more different tasks (which is great) and tries fewer languages and fewer ways of presenting tasks. One conclusion they draw in the paper that I think falls out of trying fewer languages is that language doesn't matter; even if you exclude the very obscure languges from the evals we tried here, we can observe a correlation between language popularity/usage and result quality; because Adamczewski's paper tries a lot more tasks, you can get a more complete picture by looking at this post and that paper combined than you can by looking at either in isolation.] High confidence (95%): the overall dynamic vs. static language claim won't hold For reasons stated above: this feels analogous to the caveman eval, where the result will, at best, get diluted as the problem gets larger Low confidence (60%): static languages will be somewhat better than dynamic at ultra effort Very weak confidence that, at ultra effort, the harness will get feedback to the model more quickly and this will result in some kind of benefit for either correctness or efficiency, but it would also seem reasonable for this to not be the case for all kinds of reasons, e.g., I've noticed that codex, when invoking the Rust compiler, very often makes the exact same error and then has to fix it; perhaps this kind of thing dwarfs things like a hypothetical faster feedback cycle High confidence (98%): the "weird" language supremacy of something like J won't hold Same reasoning as the overall static vs. dynamic claim, with the additional thought that AI labs are going to have much less (and possibly zero) synthetic data RL env effort on obscure languages Languages with a lot of bad code out there (e.g., PHP) will perform worse Appears to be false on these tasks Because it's so easy to re-write now, you should use a powerful language (like Haskell) Appears to be false on these tasks You should use a popular language There's weak support for this statement High confidence (95%): the overall dynamic vs. static language claim won't hold This seems correct Low confidence (60%): static languages will be somewhat better than dynamic at ultra effort There's not enough information to determine this conclusively, but if we had to make a binary correct/incorrect call, I would call this incorrect High confidence (98%): the "weird" language supremacy of something like J won't hold This seems correct [from a draft reader]: "dynamic is better on small-scale, but gets overtaken by static as the size of the project grows" Not supported by these tasks (static languages didn't seem to do substantially better than dynamic on the much larger Pandoc task vs. the smaller Zstd task), but the tasks and the presentation of the tasks are so different that it's unclear if this is because task-size scaling or because of other differences Zero confidence (50%): Ultra is more effective than medium in a loop I'm not sure how to think about this. I guess the case for this would be that ultra was designed in some way and should be smarter than repeatedly doing medium in a loop. But it's possible that there's some tradeoff where ultra was made for more speed and, as we've noted, the variance is very high so even if ultra wins on most problems it might lose here; ultra might also be more optimized for trading off to improve wall clock time or another parameter; ultra also has the disadvantage that it doesn't "know" to stop after reaching correctness on the hidden tests, whereas medium conditions that hit full correctness aren't run again under this setup, which hugely advantages medium in a loop (which is arguably realistic w.r.t. how someone might use these) You could maybe say this is 50% + epsilon since my mind went to framing it this way and not the other way around, but I would say extremely low confidence here at best Medium confidence (80%): continuing with context outperforms Ralph loop /goal mode, etc., don't do this by default and, presumably, folks at Anthropic and OpenAI have tried things like the Ralph loop and found them less effective Watching your context window very closely seems to have gotten less important as harnesses (and models?) have improved; in late 2025 / early 2026 I often had to throw out my context window when working on a long-running task to avoid issues and that's gotten rarer over time but, even then, because I wasn't paying attention to what people were saying, I was running agentic loops with a default of keeping context and only clearing when there were obvious problems, which seemed to work ok, e.g., I built the world's strongest Azul AI doing that, so it's not clear to me that having a default of clearing context on every loop iteration was the right choice back then Testing ultra I've seen people say that you shouldn't really measure this because this is a harness thing and not a model thing. I can see why you'd want to measure these separately if you're working on improving models or harnesses, but when looking at how users use things, many people are just going to use codex or claude with the various built-in features and options; whether or not something is a harness thing or a user thing isn't really relevant to them Using codex I've seen evals use a very thin harness for the same reason as above and my reason for using codex and not a very thin harness is the same as above Similarly, in this caveman model eval , I used claude with Opus and Fable and codex with GPT No internet access Models will often cheat if given internet access and there are plenty of problems where searching on the internet doesn't turn up source code that solves the problem, so this makes these evals approximate those more closely Relatively large tasks compared to a lot of benchmarks people pass around Although I have LLMs do plenty of trivial tasks, the things that take my time or take tokens tend to be larger than the kinds of tasks that were in the Alderson eval or the Endoh eval; LLMs are good enough at trivial tasks that it doesn't make too much difference to me if some condition makes them slightly better or slightly worse at one of those trivial tasks, but for a task like implementing Guards of Atlantis, where I have to spend some number of hours setting up scaffolding for the task to even sort of work, I care a lot about what makes models perform better or worse Agent-specified prompts Public evals seem to have moved to relatively thin/lightweight prompts that don't specify the task in great detail; this is said to be better because an agent setting up a task will give too much information that helps agents succeed at the task I can see why you would want to test that, but it's also the case that I care a lot about how well agents do at tasks set up by agents because a lot of the tasks that I have agents execute are tasks that are defined by agents; I care about how agents perform under both styles, not just one style, and the public evals have moved towards one style Zstd eval: asking agents to fix bugs without telling them the issue or the failing tests In general, if you tell an agent to fix a specific thing, it will fix it, but it won't necessarily fix the class of issue; I've found that if you tell it there's an issue but don't tell it what the issue is, it sometimes does a more general thing instead of just putting in a narrow, brittle fix, so I do care about how agents behave when given instructions like this (of course you can tell agents to not just make a narrow, brittle, fix, but that often doesn't work) This feels a bit related to the issue we noted in the Pandoc holdout footnote, where telling agents we had a holdout set appeared to force agents to produce more generalized and less brittle solutions Originally, the build setup wasn't clearly specified to agents, causing some languages to randomly fail when agents did something that seemed reasonable based on how this was specified to agents but didn't work when scoring occurred BTW, I was very exicted by the initial result here because it was super interesting looking and it confirmed my biases. Dynamic languages were substantially worse than static languages. What a blockbuster result! But it turned out that the real result from the initial setup was that static languages were less likely than dynamic languages to have problems caused by this issue because static languages were less likely to have issues with the idiosyncratic way project builds were ambiguously specified In the original assembly conditions, agents implemented code in C and then compiled it to assembly and submitted the assembly With this issue, asssembly did as well as other languages, which is super interesting! And also false once this issue was fixed. It turns out to be very easy to get incorrect but compelling looking results that would easy go viral if you aren't careful. After fixing those two issues, the results looked fairly mundane and fall into what you might call a "negative result" in the framing of a paper, in that there's no interesting or surprising or contentious thing the results show; perhaps slightly favoring boring languages would've been contarian result for very online people 10-20 years ago, but very online trendy discourse seems to have moved away from that, so this isn't really an interesting contarian result anymore For some reason, the agent doing the setup imposed unusual arbitrary restrictions on some languages and not others (for example, the Rust setup didn't have access to rustfmt or Clippy); most, but not all, languages had things like this Many of the tests (which were created by an agent) were actually some kind of performance/stress tests even though agents were instructed to ignore performance (I wouldn't consider processing 4 GiB of Zstd in 9000 seconds a performance stress test) Some language conditions had arbitrary instructions to agents (for example, the Haskell condition had instructions not to use bytestring, with instructions on alternative implementation suggestions) Some language conditions had old toolchains (for example, Zig was on 0.10) Some language conditions had scaffolding to help agents implement Zstd Some language conditions had explanations of tools that were available that were incorrect (for example, assembly conditions were told they had access to GDB, but GDB didn't work) The agent responsible for health checks for running iterative evals would sometimes decide that evals weren't making enough progress and give held out tests or other information to agents inside the eval a draft reader pre-registered the guess, "dynamic is better on small-scale, but gets overtaken by static as the size of the project grows". [return] The holdout tests seem necessary because, without them, agents cheat and will detect a test input and hard-code the passing test output (they sometimes do this even when instructed not to cheat). If all cheating was that blatant, that wouldn't be a problem (and could be an interesting thing to measure, as agents differentially following directions or not across languages is something that matters to real users), but a lot of the cheating is more subtle and difficult to adjudicate. For example, some agents wrote code that branched off of the structure of the tests, but then filled in the contents of the branches with code that wasn't special-cased to a single test result and could pass many variants of the same test. For any point on the spectrum from "definitely not cheating" to "obviously cheating", some agent tried it. As we saw when we looked at Senior SWE-Bench , LLM scoring of evals is tricky and a great way to introduce both bias and variance; using a holdout set of tests has some problems, but it lets us avoid this much larger set of problems. For one thing, the holdout tests are suspsicious because they were created by agents. The intention was to create holdout tests that a reasonable person (or agent) would be able to make pass if they're not cheating. Agents audited this set of holdout tests for cases where this wasn't reasonable and eliminated some, but I didn't check these by hand, so I find it likely that there's at least one holdout test that's unfair in some way. However, the overall score against holdout tests is low enough that I'm not too worried about a small number of tests being bad (if I worked at an AI lab and was trying to train next-generation models, I would be more worried about this, but I don't think it's material for our use case here). Instructing agents not to cheat while having a holdout set of tests didn't prevent blatant cheating that scored extremely poorly on holdout tests, but telling agents that there was a holdout set of tests they were graded against seemed to reduce the score they achieved on the agent-visible tests while increasing the score they achieved against holdout tests (without telling them this, a number of agents achieved 100% on the Pandoc tests with uselessly brittle code; on telling them there's a holdout, no agent scored 100% after 1 turn on ultra, but the holdout scores were substantially better, indicating better generalization). [return] There are various Substacks, YouTube channels, and other things that promise to tell you the secrets of LLM coding success, but the ROI on spending time running actual experiments isn't really there. When we looked at caveman mode , we saw that one of the biggest programming YouTubers had a video where they spent a few minutes looking into it and decided that it worked. Spending even 15 minutes looking into whether or not it really works is probably negative ROI compared to spending that time producing more content instead . There are various papers that discuss different techniques, and these sometimes go into more detail than most blog posts or videos but, on average, they don't necessarily have more useful information. For example, when I asked ChatGPT (5.6 Sol, Pro) to find discussions of language effectiveness with respect to LLMs, it turned up this paper on token efficiency , which has an interesting idea, but has the same issue as the caveman mode evals we discussed earlier, where it's not looking at a task that's interesting enough for the result to be relevant to me as a programmer. Just seeing what cited that paper, we find this paper by three academics on token efficiency of languages titled "The Best Programming Language for Tokenmaxxing" , but compared to this post, that paper only compares four languages, uses worse models, and uses small toy problems (from something called LiveCodeBench; the cost to solve problems with GPT-5.5 is often on the order of 1000 tokens). Regardless of how well done the eval is, as we've noted in this post and in our caveman mode eval , we often see wildly different relative results when going from a small toy problem to a problem that I might care about for hobby projects or work. Also, in that paper, they note that they gave the prompt "To test your program, run exactly ./test.sh... These are the only tests I care about" and they say this is realistic because "We believe that this setup is a realistic way to study agent behavior: in everyday use, programmers don’t hide their tests from agents. Instead, programmers direct their agents to keep working until all tests pass." but, as we noted above, doing this results in brittle code that fails in the real world (or if you have holdout tests that aren't given to the agent, it fails the holdout tests at a very high rate; this problem cannot be solved by just adding a few more tests; it can perhaps be addressed via something like fuzzing or property-based testing, but how well that works is a topic for another post). I'm not saying these papers are bad or that there isn't something interesting to learn from these papers, but as a programmer who wants to know what techniques or tools I should use, I can't get that information from papers like the ones linked above. [UPDATE: Tom Adamczewski sent me a link to his paper, https://arxiv.org/pdf/2606.30182 , which does handle a lot of the issues mentioned above. Relative to this post, it tries a lot more different tasks (which is great) and tries fewer languages and fewer ways of presenting tasks. One conclusion they draw in the paper that I think falls out of trying fewer languages is that language doesn't matter; even if you exclude the very obscure languges from the evals we tried here, we can observe a correlation between language popularity/usage and result quality; because Adamczewski's paper tries a lot more tasks, you can get a more complete picture by looking at this post and that paper combined than you can by looking at either in isolation.] [return]

0 views
danluu 2 months ago

Bad benchmarks and evals: Senior SWE-Bench, napkin math, and winter tires

We're going to look at three different kinds of benchmarks, one set of calculations for baseline numbers for performance "napkin math" estimates, one set of AI model evals, and one on car tires. To build my intuition for things, I like thinking about them before seeing the explanation, so these are presented with the benchmark information first and the explanation later in case you want to think about your answer before seeing my thoughts. 29. A friend of mine is reviewing performance orders of magnitude to prep for computer performance interviews and found that https://github.com/sirupsen/napkin-math (5.4k stars) was the top hit. The README's tables include: What's wrong with this benchmark? 30. I keep seeing people reference DeepSWE and Senior SWE-Bench to "prove" that their favorite model is better than other people's favorite models or just as generally good benchmarks, such as in What's wrong with these benchmarks? 31. People frequently say that winter tires are superior to all-season tires in cold weather. For example, on googling "all season tires during winter cold" (no quotes), the Google AI summary leads with All-season tires lose traction and stiffen in freezing winter temperatures. Their rubber compounds are designed for warmer weather and become hard below 7°C (45°F), leading to significantly longer braking distances and reduced grip ... The rubber in all-season tires cannot maintain pliability in sub-zero temperatures, causing them to perform more like hard plastic on snow and ice. Given that there are a lot of internet comments in the training data, this is a reasonable comment, in that I frequently see variations on this comment on discussions of which tires one should use. What's wrong with this benchmark? The thing that immediately jumped out to my friend (Jamie) as odd was random memory R/W listed as 20ns, since random memory R/W is implied to be a real DRAM read (as opposed to a cache hit), which he felt this should be around 100ns for an order of magnitude estimate. As we were chatting about this, he noted that the README uses the term "latency" for some things that aren't really latencies. Then, when he pulled up the code for random memory read latency, he found the following (if you want another exercise, consider what's wrong with the following code before reading the explanation below): Jamie noted that there's no data dependency between the loop iterations, so the memory reads here happen in parallel. Since the alleged latency number is determined by finding the average time for an access, this is incorrect because the CPU can have multiple loads in flight at once. If you wanted to measure latency this way, you'd have to introduce a dependence between loads, to prevent overlapping accesses (we discussed a related topic in exercise 19, covered in part 4 of this series ). I agree with all of Jamie's comments, although I didn't really flag the use of the term latency myself because maybe it's shorthand for latency in some cases and something a bit latency-like in other cases (such as reciprocal throughput), which makes the table simpler. What first jumped out to me, besides the memory latency number, was some of the other numbers. For example, random SSD read is listed as 100 us / 70 MB/s. You can get much faster (as well as much slower) SSDs. For example, if you have a fast (but non-exotic, e.g., non Optane) device, you might see latencies below 40us, e.g., the Kioxia CD9P-R was measured at ~30 us here . Other than for some trivial scripts, I haven't worked on anything where I care about disk performance, so I don't have an intuition for what numbers someone would want to have in mind 1 , but I also wonder if having a single number for random read latency and throughput is less useful than it is for DRAM accesses. Whenever I've looked at disk benchmarks, it seems like there's a huge range of results based on read size, queue depth, and number of jobs (e.g., see the previous link on the Kioxia CD9P-R ). Of course, there are analogous factors that influence DRAM latency and bandwidth, but it seems like you're much more often in a regime where knowing one or two numbers is helpful when thinking about memory accesses. Since I don't know anything about disk performance, I asked Peter Geoghegan, who's done work on Postgres disk performance; he concurred and also wrote some additional comments on the complexity of disk performance below If we look at the code for this SSD random read number, it feels off to me in the same way that the random memory read code felt off to Jamie. It generates offsets with and then does 8 KiB reads (offsets are shuffled to create random reads). Some things that don't feel right about this are: The "buffer.len() / page_size" construction seems to be intended to keep accesses in bounds, but this is independent of the access length. If we want to be lazy and not think about exact offsets, consider some huge access length like 4 GiB (the buffer size is 8 GiB). That will surely overflow. If we want to be more precise, the overflow case will be more like a 4KiB page with 8KiB access length, but the same idea applies. The very last offset is going to be SIZE - 4096 + 1. This gives us 4095 bytes we can access, but we try to access 8192 bytes. Because the benchmark only runs for 5 seconds, it may or may not actually try to read past EOF and fail, but there's a bug here regardless of whether or not it randomly fails on any given run. Just looking at the code, a lot of it doesn't feel quite right to me. For example, consider the code that's used to generate the sequential 8 KiB SSD read, which is said to have 1us latency and 8 GiB/s throughput. Like I said, I haven't worked on any problems where disk performance matters, so I don't have an intuition on whether or not numbers like this are plausible, but the code feels off to me. The code takes a 1 GiB file, flushing it, and then re-reading it repeatedly, so we'll have one uncached read followed by cached reads. It seems like the intent is to measure uncached reads here, but if the intent is to measure cached reads, the code isn't doing that either (this appears to be an issue for some of the other numbers as well, such as 3 GiB/s of fsync'd reads). One could argue that it's realistic to have an uncached read followed by cached reads, but it's not clear what someone who's using the aggregate number of 1 uncached read followed by N cached reads should do with the number when they don't have the exact same workload; N isn't stated in an obvious way, so they wouldn't even know if they have the same workload. Since I have no idea what the numbers should be here, maybe we can look up some numbers. The measurement was said to be done on a . Google's docs for that instance claim that the maximum throughput for all 8 attached disks is 5000 MiB/s (from Google's table, this scales per number of attached disks and is 625 MiB/s per disk). From the very little I know of disk benchmarks, it seems like peak throughput numbers are generally done when using larger reads, so 8 GiB/s seems excessive and the feeling that something is off from the code seems to be right. And if we look at other numbers, it seems like the broader point that having a few single numbers for specific read sizes isn't representative of disk performance in general. But the idea behind this kind of "napkin math" generally isn't to know how exactly one cloud instance performs; it's to get some basic numbers that can be used to estimate performance in various ways. If we look back to the Kioxia CD9P benchmarks, there are plenty of read benchmarks with higher bandwidth than that, with various parameters (and also plenty with lower bandwidth, with various parameters). For latency, the latency is higher even for sequential reads at settings that minimize latency (including for the other disks in the benchmark), which is another sign that the benchmark is inadvertently reading from cache, but even if the numbers were correct, it's not clear what you'd do with the numbers. It seems like the code has an attempt to prevent caching and prefetching. If it detects the test is being run on Linux, it sets an advisory and, before the test starts, it sets an advisory , but neither of these will prevent caching on this benchmark at the OS level, nor should these be expected to prevent lower-level caching (such as inside the SSD). On Mac, the benchmark calls beforehand, but there's no equivalent of and there doesn't seem to be anything done on other OSes (such as BSD or Windows). There are other issues in other parts of the code, but rather than get into the weeds on every specific issue and most of the presented values, if we come back to this idea that there are things where we want to get an idea of a range of numbers in different regimes, there are quite a few places where that seems to be the case. To pick another example, the README cites "Decompression" at 1 GiB/s and "Compression" at 500 MiB/s. Of course, any kind of napkin math isn't going to be precise, but just playing with different compression options, we get more than two orders of magnitude difference in compression speeds and there are algorithms that are more specialized for high speed compression, giving an even larger range (and of course you can spend more effort to get lower speed and denser compression). Going back to the disk example, we noted that the disk numbers come from a VM configuration with 8 disks. The numbers appear to be incorrect but, if the numbers were correct, of course you'd get different numbers for something like read bandwidth if you used a single-disk version of the VM. You'd expect roughly 1/8th the read bandwidth for the read benchmarks if it wasn't reading from the page cache. It's not clear why it's particularly useful to have a read bandwidth number for one particular 8-disk configuration on GCP memorized as a napkin math figure. Overall, I do find knowing some of these kinds of numbers useful, but I don't know that I'd necessarily want to look at a table to see the numbers (except maybe as interview prep that I'd expect to forget immediately after the interview if I had good reason to believe I'd be asked about them in an interview). In general, if you're doing things where it makes sense to know these numbers, you'll pick them up just by using them. For example, I still remember that dispersion in standard single-mode fiber is 17 ps / nm * km because I did some optics / photonics work twenty years ago. This comes up often enough in back-of-the-envelope calculations that you'll just remember this at some point if you use it enough. Likewise for various powers of 2 (e.g., 2^8 = 256, 2^16 = 65536, etc.), which I didn't try to memorize but picked up because, if you do enough coding where you touch these numbers, you end up remembering the numbers that often come in handy. The linked napkin math repo notes that "numbers [are] rounded for memorization", implying that it makes sense to memorize these. In addition to what's mentioned above, a lot of these numbers are derivable and, in my opinion, if you're using these for work, it often makes sense to understand the derivation even if you have a ballpark number memorized. For example, we derived the single-core memory bandwidth number for a Sandy Bridge processor from some basic parameters in part 4 . If you just want to know how fast a piece of code is going to run, you generally don't to rederive everything from first principles. But, if you're trying to understand the implications of changing something, it's helpful to know the what mechanisms are in play and how they'll interact, which is something you don't get from having a handful of numbers memorized. Going back to the context for this question, my friend who was doing interview prep, the last concern he mentioned was that this repo is very popular, so the interviewer might be use it without knowing that most of the numbers that are listed are wrong. BTW, I was curious what memory latency is actually observed on real systems, so I plotted the data from the instlatx64 site , which reveals the following: There are two graphs here because two different, non-comparable, methodologies were used. The original methodology used accesses with a 1024 byte stride to find memory latency, which worked fine for accesses over a large enough data set on older processors. Newer processors added mechanisms that can make this fail to be a pure DRAM access, so the newer methodology uses random accesses to find memory latency (some of the later numbers using the old methodology aren't really valid if you're thinking of them as a random memory access time). The latencies come from the instlatx64 site and the CPU release year was found by asking GPT-5.6 Sol ultra in codex without verifying the results, so some of the years are probably incorrect. Just from eyeballing the graphs, we can see that memory latency improved tremendously for a while, but this improvement eventually stalled out and we actually see higher observed latencies over time for reasons that are outside the scope of the post. We also see some extremely high outlier results from the 90s. Without spending too much time looking at these results, it's not obvious that the results are incorrect. On the Intel side, the big outlier is an 83 MHz Intel Pentium Overdrive. The other old Intel results are all non-Overdrive Pentiums. The Overdrive Pentiums were chips you could slap into a motherboard for a previous generation CPU. It looks like the test was run on a Gigabyte GA-5486AL motherboard with an ALi M1489/M1487 chipset set to a 33 MHz bus speed. According to the ALi M1489/M1487 datasheet, there are four possible DRAM read timings. If it's configured to the "normal" setting, a read page miss is , with a read timing. I'm even less familiar with 486 bus timing than I am with disk performance, so I asked an LLM about this and it told me that this is correct and we should expect 21, 22, or 23 cycles for a memory access here. This doesn't feel quite right since, on asking the LLM what the heck these numbers mean, it's for the first word followed by each additional word, so that number of cycles is for a full cache line fill. The actual load-to-use latency for a word access should then be the first part, or 11 bus cycles, but if you want the time for the whole cache line, then the number seems plausibly like it's in the right benchmark. On looking at the outlier AMD K5 PR166 result, there's something a bit odd about it, but we're pretty far off into the weeds on a question about modern computer performance, so maybe that can be another question for later in the series. Before looking at the methodology of these benchmarks and just looking at the results, neither DeepSWE nor Senior SWE-Bench feel plausible as summaries for how well coding agents work overall. A surface-level reading of the DeepSWE homepage has OpenAI's last-generation model (GPT-5.5) being as good as Anthropic's current-generation model (Fable 5) and a surface-level reading of the Senior SWE-Bench has Anthropic's last-generation model (Opus 4.8) as being better than OpenAI's current generation model (GPT 5.6). In general, the surface-level reading is what most people will take away and this is how I generally see these used (e.g., in work slack, when people send these to me directly, etc.). If we look at how the sausage is made, very few of the publicly available benchmarks seem like reasonable things to rely on for getting a general idea of how good coding agents are. In terms of methodology, the benchmarks don't really make sense with respect to what you'd need to measure to get a generalizable result. Just like I don't know anything about disk performance, I don't know anything about AI, so I asked someone who ran an evals team at Anthropic for a while (Aaron Levin) to review the reasoning and conclusion and he concurred with the general idea and the reasoning. As with the consultation with the disk-performance expert, the point of this isn't to say that you should agree because an expert agrees; it's to say that, in these cases, you don't need any kind of specialized knowledge about the field to come to the same conclusion an expert would come to . You just need to apply the same kind of generic reasoning you'd use to evaluate any benchmarking or experimental design problem. In the last post , we discussed the high-level idea that a single summary score can say pretty much anything because, when we look at subbenchmark results, there will be plenty that favor model X over model Y and there isn't a particularly good way to, in general, sample the distribution of tasks out there to say that benchmark A is better than benchmark B because it's more representative. If we look more at the details of these benchmarks, for DeepSWE, there are 113 tasks (or that's what codex told me, anyway), each one of which is run four times, with what generally appears to be a pass/fail score (models appear to score 0%, 25%, 50%, 75%, or 100% on each task). On the graph, we can see that GPT-5.5 is much better than Opus 4.8; the difference between GPT-5.5 and Opus 4.8 is about as large as the difference between Opus 4.8 and Gemini-3.5 Flash. As we noted above, if you've used these models, this doesn't really match the experience I or anyone whose judgement I trust has, overall (of course there are specific tasks or sub-benchmarks where this is true). If we look at why this is supposedly the case, GPT-5.5 xhigh is allegedly a bit cheaper than Opus 4.8 xhigh and much better (scoring 67% vs. 54%). Of the 113 tasks, the models tie on 34 tasks, GPT-5.5 xhigh wins on 57 tasks, and Opus 4.8 wins on 22 tasks. For me or another programmer, this might be meaningful if these tasks are representative of tasks I or another programmer do. 113 tasks (or even just the 79 differing tasks) are more than we're going to look at in detail in this post, but from looking at the names of the tasks, few to none of them seem relevant to tasks I do at all. And then looking at language, of the tasks that differ, 4 tasks are in a language I often use coding agents for (Rust), and the rest of the tasks are in languages where I don't use coding agents or use them for trivial problems where any model is fine 2 . The four Rust tasks where results differ are: Hierarchical evaluation cancellation in Boa ( https://deepswe.datacurve.ai/data/v1.1/tasks/boa-hierarchical-evaluation-cancellation ), Deterministic multi-key sorting in fd ( https://deepswe.datacurve.ai/data/v1.1/tasks/fd-deterministic-multi-key-sorting ), Preserve stylesheet-selector structure in oxvg ( https://deepswe.datacurve.ai/data/v1.1/tasks/oxvg-structural-selector-preservation ), and Trap coredump generation in wasmi ( https://deepswe.datacurve.ai/data/v1.1/tasks/wasmi-trap-coredumps ). None of these seem all that related to things I use coding agents for, so this is worthless to me. One of these seems vaguely like something I've done in the past year and the other three don't. We know from looking at individual benchmarks that there's significant variance in results between different benchmarks (for example, in the Optimization 1 benchmark in the last post , we get a vaguely DeepSWE-like model ranking, but in the GameAI we get a Senior SWE-Bench-like ranking, but as we also observed in that post, you can have one benchmark that nominally appears to resemble a task we care about that gives a result that's the opposite of what we see on the actual task, again because variance is very high). Having 1 out of 113 tasks sort of be similar to a task I've done means the DeepSWE benchmark score is meaningless to me personally. Moving on to the other benchmark, Senior SWE-Bench has all of the problems noted above, and it also presents the results in a more misleading way and has the additional issue of doing more subjective grading of results. I don't want to do one of these super long point-by-point teardowns, but to look at one issue with it, to qualify as a "tasteful solve", a solution has to meet multiple criteria, including scoring better than a certain score on a rubric and having a result that isn't >= 2x the length of a reference result. Without even looking at it more deeply, we already see this is a classic https://danluu.com/discontinuities/ situation. The benchmark has these continuous scores and then it introduces threshold effects by requiring a strict cutoff. From what I've seen, this kind of thing is often done because it makes things simpler, but if you believe the underlying criteria are important, in general, you often don't want to say that a score of X is a pass and a score of X-epsilon is a failure. Instead, the scores should be aggregated in some non-discontinuous way. I think there's often a hesitancy to do this because trying to write down a formula for this often makes it obvious that the weights are arbitrary and the score is meaningless. We probably know we don't want to give up to N extra score for a 1 LOC solution if the reference is R LOC, so we need some function that will cap the value there. Maybe we can cap the bonus at 2 by doing something like . Maybe this doesn't penalize large functions enough, so we should switch to . It might be easier to see the behavior of this if we write it as , so you can mentally substitute that if you prefer. We then need to combine this with the other scores, so we need to add at least M-1 of the M formulas so we have some relative weighting for them. This would clearly be an arbitrary formula that's hard to justify. But the actual formula used has these discontinuities is another completely arbitrary function, but with worse properies that make it even harder to justify! It's just that whoever's writing it down doesn't have to think of it as a formula so they can avoid thinking about how arbitrary it is. If we look specifically at the LOC measure as defined by Senior SWE-Bench, of course we see threshold effects. For example, on https://senior-swe-bench.snorkel.ai/tasks/paperless-ngx-perf-workflow-queries , GLM-5.2 scores tasteful at 121 LOC vs. 61 for the reference. If there was one single LOC more, it would be 122, or double, which would cause GLM-5.2 to fail instead of pass. We can also see from the link that the benchmark was run once per condition. As anyone who's used LLMs knows and as we saw in the last post , there's tremendous variance between runs (quite often, there is commonly variance between runs than across different models and effort levels, which we observed in the last post), which already makes a single run not very meaningful when scored with some kind of reasonable continuous score. When noisy metrics like this then have information removed with these threshold effects, the result becomes even less meaningful. That isn't even a particularly problematic benchmark with respect to the LOC score. plausible-fix-top-pages-comparison is worse because the reference solution is 1 LOC (since addition and deletion each count as 1 LOC, this is scored as 2 LOC). This makes the maximum size of a tasteful solve 3 LOC; if additions and deletions both happen, this would have to be 1 LOC deleted and 2 added or vice versa. If we look at the actual results, they don't make sense. We can see that, on this task, Opus 4.8 scores "tasteful" while Opus 4.7 and Fable 5 don't. If we look at the actual diffs and compare them to the reference solution, we find the following (note that only changes to the actual code count for the LOC criteria; test LOC, comments, etc., do not count). I'm not an Elixir programmer, nor am I familiar with this codebase, but just looking at the code, the failing, "non-tasteful" Opus 4.7 solution looks semantically identical to the reference solution. The only difference is that the pipeline was expanded onto multiple lines for readability. Without knowing Elixir, it strikes me as absurd to fail this based on "tastefulness". I've used other languages where you commonly use a pipe operator like this (such as F# or R with tidyverse) and I don't believe I've ever run into anyone who would reject the Opus 4.7 change for being "untasteful" (unless there was a style guide which had strict rules about what should be expanded into multiple lines and what shouldn't, but if that were the case, an autoformatter should deal with this and the formatting of the solution is irrelevant ). The Fable 5 solution should arguably be rejected for expanding the scope of the change too much but, whether or not it should be rejected for other reasons, it seems wrong to additionally reject it as "untasteful" due to the length. LLM variance also applies to the grading itself. Of course it must be the case that if we feed the results of one single run to an LLM grader multiple times, we'll get different scores for the same reason we often get wildly different results when we ask an LLM to solve the same problem multiple times. I tried having my friendly neighborhood coding agent re-run grading 10 times for each condition that GPT-5.6 Sol and Opus 4.8 were tested under (codex tells me grading was run using Sonnet 4.6, so it re-ran with that). The expected LLM-graded tastefulness result flips from the official result 23% of the time when using the same model and effort level (in terms of sub-results, relative taste flips in 32% of cases, practice alignment flips in 5% of cases, and task rubric flips in 3% of cases). If we instead look at the fraction of the time the official result differed from the typical/median result, there's a 21% difference overall (27% for relative taste, 3% for practice alignment, and 2% for task rubric). The overall flip rate is lower than the individual flip rate because, in some cases, a result flipped from tasteful to untasteful in a sub-score when the overall score was already untasteful. Just to be clear, this is not run-to-run variance. This is the variance from using LLM grading on a single run, which, across the publicly available GPT-5.6 Sol and Opus 4.8 benchmarks, appears to give an incorrect result about 20% of the time (if we assume what's being measured is correct and reasonable to measure in the first place and tha the most likely Sonnet score is the correct score). Of course we get different results if we grade with different models as well. If we re-grade with GPT-5.6 Sol instead of Sonnet 4.6, the number of solutions that are judged to be tasteful is cut by more than half for both models. Is that more or less accurate? Who knows? Sometimes, you can look at a benchmark and say that, while some individual results are wrong, in aggregate, the noise cancels out and the overall results make sense. I don't think that's the case here. I've seen a lot of people passing Senior SWE-Bench around, seemingly because it purports to give realistic problems and score them in a reasonable way. We already noted that, prima facie, the results don't seem plausible, and, that looking at the methodology supports the prima facie thought that the result is not meaningful 3 . The presentation of results also leaves something to be desired. On a Slack I'm on, someone linked to this, which shows a preview snippet with the following: They gave an approving comment, saying this was more realistic than other benchmarks (referring to one of the many benchmarks that put GPT-5.5 ahead of Opus 4.8). If you actually look at the results, it's clear that the difference between 25.0% and 24.4% is pretty much meaningless, but the results are presented as if these are meaningful differences . Although the page makes it clear that GPT-5.6 Sol is, as measured, much cheaper than Opus 4.8, most discussions I've seen that refer to Senior SWE-Bench elide this and mention only the headline result. It also seems odd that the headline result uses for Fable, Opus, and Sonnet, but for GPT-5.6, GPT-5.5, and GPT-5.4. Although people commonly say that all-season tires become hard (for some reason, the phrasing that they become as hard as "hockey pucks" is common) at 7C / 45F and have poor grip, there's no benchmark! This has been a common theme in this series: people repeating a claim that has no apparent basis in a measurement 4 . Luckily, as we discussed in this post on platforms and monetization , Jonathan Benson has been able to monetize in-depth explorations on tires, resulting in a never-before seen level of detail in public tire benchmarks. He tested how well different kinds of tires perform at different temperatures and in different conditions . I'm sure tire manufacturers have all sorts of tests like this but, AFAIK, this hadn't been done publicly in a comprehensive way before (hmm, this doesn't seem so different from public benchmarks of coding agents). In Benson's testing, he finds that, in dry conditions, summer tires have the best grip down to 0C / 32 F (he didn't test colder conditions), followed by all-seasons, with winter being worse than both summer tires and all-seasons by a fairly large margin. In wet conditions, he only tested down to 2C since, at 0C, you have icy conditions and not just wet conditions. The ranking is a bit different since all-season tires wildly outperformed summer tires at 2C in the wet, but summer tires still outperformed winter tires. Note that, in the video, what Benson calls a winter tire is a UHP winter tire, which I very rarely see people using in the US or Canada (although it's what I use for a winter tire since that makes sense for the local conditions where I live). What he calls a "nordic" tire is what most people use for a winter tire even locally here and everywhere else I've lived, all of which are locations where that kind of tire doesn't really make sense unless you're spending a lot of time driving into the mountains (and even then, it's probably still not the right choice for most people where I've lived) or you spend a lot of time driving on ice. But even if you look at the UHP winter tire results compared to all-seasons, it's still true that all-seasons are better in dry or wet conditions above 0C, although the magnitude of the difference is much smaller than it is relative to the "nordic" winter tires that most people in the US use (I think the terminology he's using might be more common in Europe?). Of course different tires will perform differently and we'd see some variation in results with different tires, and of course there are many conditions where it's better to have winter tires than all-season tires or summer tires, but the idea that all-season tires become too hard to grip and you have to have winter tires for cold alone is clearly false. BTW, if you're wondering why you should care about tires at all, on average, motor vehicle accidents are a fairly major cause of death and, if you look at the impact of velocity on accident severity, it's pretty significant, so it stands to reason that having tires that let you brake more rapidly or corner a little better and maybe avoid or deflect the accident a bit, it's reasonable to think this would have a substantial impact on accident severity. I don't think this is the kind of thing there's really good data for (it would be very hard to run the randomized trial and observational data is going to be highly confounded, in general). But, as part of an analysis I did last year, I tried to find the relationship between HIC and velocity in actual crash test data. Surprisingly to me, I couldn't find a paper that had done this (I did find some papers that could serve as exercises for this series, though), but a straightforward analysis put the relationship as roughly to the fourth power. I should really write that up into a post that's like this other post on crash testing , but specifically about the HIC and concussion risk of various vehicles! Anyway, I try to drive a car with the right tires for the locale because it seems like that's plausibly one of the higher impact interventions I could do for my own safety per dollar and/or effort. But I've never gotten close to a situation where my really good tires have made a difference and someone who's going to try to find the right tires for safety reasons may be less likely to get into an accident in the first place, so this may just be a silly hobby that doesn't matter at all. If you liked this post. this is part of a series of exercises on benchmarking, evals, and experimental design ( 1 , 2 , 3 , 4 , 5 , 6 ) 5 . Thanks to Peter Geoghegan, Aaron Levin, Luke Burton, Em Chu, Jamie Brandon, Yossi Kreinin, Jeshua Smith, and Ikhwan Lee, for comments/corrections/discussion. Here are some follow-up comments by Peter Geoghegan who, unlike me, actually knows something about disk performance: I've seen significant variation in performance across more or less comparable SSDs for certain access patterns. This is likely due to FTL/firmware level differences. Evidently some SSDs are much better than others at reading backwards sequentially, independent of OS read ahead (with direct IO). Here's a blog post about it from the person I'm working with on IO prefetching for index scans in Postgres: https://vondra.me/posts/fun-and-weirdness-with-ssds . I'm fairly sure that these things are still opaque to the OS/filesystem. This admittedly-dated LWN.net article provides some justification for this: https://lwn.net/Articles/353411 , "The message to file systems developers is "Just trust us" and "Don't worry your pretty little systems programmers' heads about it" whenever we ask for more information on SSD implementation". I asked Linux hacker Matthew Wilcox about this in 2023. He said that it was about the same, and that if I wanted to account for performance variation for microbenchmarking purposes the best way was still to be very defensive about provisioning, running TRIM regularly, etc. At one point (I think around 2015), I wrote some code with the intention of turning it into some exercises or a tutorial on CPU performance. It was sort of like the napkin math repo, but much narrower. The idea was that you could have questions like: I had the code I wanted for various things but, for some reason, the code I wrote didn't elicit a difference between a DRAM open page access and a closed page access and then I got distracted with other things and didn't end up writing it up. Pre-LLM, doing this kind of thing was fairly time consuming, because to get it right, you have to know enough about what the mechanisms that are in play are and then take some care in writing the code and checking what it does. And then, because I screwed something up and make enough time to debug it, I never ended up writing up the exercises because I didn't want to write it up when there was some kind of mystery that implied that my code had at least one issue. Anyway, disk is way more complicated and getting good numbers would take a lot more care. With LLMs, I think this would now be doable without it taking a ton of time, but some care would still be necessary. P.S. The friend of mine mentioned in (29) is Jamie Brandon , who's actively interviewing and looking for work. He's done a fair amount of work on databases (query engines) and streaming systems. His best-known writing is probably Against SQL , but he's also written quite a few other posts I like, such as this analysis of streaming systems consistency bugs . He's mainly looking for a Vancouver-local job or a remote job. If you'd like to talk to him, you can reach him at [email protected]. At a meta level, people who I talk to who generally have comments I find reasonable on other topics don't take these headline/summary results very seriously. For example, In a comment on the usefulness of these benchmarks, Em Chu said: twitter/hacker news sentiment, which at least won't be misleadingly precise, feels like a better way to tell whether or not a model is useful, as strange as that is (which unfortunately requires reading a lot of hacker news posts, so I cannot recommend.) I usually find my eyes skipping over anything that looks like an LLM benchmark since the chances that it's worth reading are near zero. (I wish I would do this for hacker news comments too.) Most people I know whose judgment I trust take a similar approach (sometimes substituting opinions of people they know for online sentiment). The exceptions to this are generally people who work in the field and look at a ton of benchmarks and do some kind of mental aggregation of them. For example, when I talk to Max Bitker (who runs an RL environment startup), he's familiar with seemingly every public benchmark and can seem to predict what sentiment will be like a couple weeks after a model release based on his mental model of the aggregate landscape of all the benchmarks out there, but that's a very different thing than looking at a summary score metric and time-consuming enough that, unless you work on AI, this seems more like a hobby interest than something you'd reasonably do to evaluate model effectiveness (nothing against hobby interests; I have lots of hobby interests). For a concrete example of what it looks like to take the results of these benchmarks seriously vs. what's observed in the real world, here's a thread where someone creates an effectiveness vs. cost table of the then-new 5.6 Sol/Terra/Luna vs. 5.5 using DeepSWE results. Someone (who I'd agree with, although I'd phrase it differently) replies Bullshit. Have you actually used the models or are you having a wank? According to this table 5.6-sol xhigh would be both cheaper and better than 5.5 xhigh. In what reality is that actually true? Another person replies to them with In none. I think the benchmark tasks are really straighforward in which case the table may be true. I don't think that's quite fair (I've tried tasks where it seems to be true) but, in general, people mostly have very different experiences than public benchmarks are showing. This seems to be understood by quite a few people, from people I know in person to random internet commenters. But it's not universal, as I still see people passing around these scores to explain why they use some model and effort level, which doesn't seem justified in general. I've been publishing these on Patreon without a strong reason to. I make a bit of money off Patreon, but if I was optimizing for money I think it would obviously be the right choice to just publish everything publicly since the potential delta in earnings from maybe getting connected to a potential job dwarfs what I could earn directly via Patreon. That goes double considering how bad I am at interviews (I've almost exclusively gotten jobs where the interview is formality as my odds of passing an interview are otherwise close to zero; the last time I did an interview, I failed a phone screen on a leetcode-style question, and when I pass those I'll typically fail the full interview later if it's a real interview). I originally started publishing things on Patreon that I thought were too small or inconsequential to turn into a "real" blog post, but then I got in the habit of publishing things on Patreon and haven't written much publicly for a while. This kind of post, which is part of a long set of exercises, falls squarely into the category of things that seems too small and inconsequential to put onto the main blog. If you have opinions on this, I'd be curious to hear what you think. The idea behind this series was that I wanted to write some kind of tutorial or blog post to help people with better benchmarking and evals. But, my feeling on evals is that it's more about avoiding mistakes than following some particular process, so there isn't really a step-by-step guide format that works in the general case. I know there are approaches to experimental design where they teach you to do things like drawing a causal graph and then looking at the graph to figure out the potential problems, e.g., collider bias . Just from seeing how people do data analysis before and after learning techniques like this, I don't think this makes a huge difference on average (although a few people do find it very useful). I saw a criticism of this as a generalized way to avoid experimental design issues somewhere (maybe from Andrew Gelman) that the problem is that everything is related to everything, so you're still applying your judgement when you create the causal graph. Being able to mechanically see the problems once the graph is created doesn't stop someone from drawing the wrong graph in the first place. A vaguely related idea that I saw when I read the first chunk of McElreath's Statistical Rethinking many years ago, hoping to learn some process that would lead to rigorous statistical analysis is that there isn't really such a process and you ultimately have to use your judgement to decide if something makes sense or not. That being the case, I thought a series of exercises might work, so I had this idea to write maybe 50 or 100 exercises into a single post. That seems quite do-able for small exercises, but it's clear from watching people learn a variety of things that giving people a bunch of small exercises and then hoping that people generalize the techniques onto larger, more complex, exercises, doesn't usually work very well. Once you start adding in larger, more complex, exercises, you quickly get beyond the length of a long post, even by the standards of this blog, which has this 32k word post on what the FTC got wrong in their 2011-2012 investigation of Google (for reference, a typical novel is often said to be 80k-100k words). In general, I've avoided putting multi-part posts on the blog because, as a reader, I much prefer it if things are all in one post instead of spread across some kind of long series of posts. I get that authors often prefer multi-part posts because it generally results in more traffic, better odds of a post going viral on social media, etc., but I've always optimized this blog to be more like what I want to read than to maximize page views. In this case, it seems like the single-post version could easily be as long as a doorstop fantasy novel (for reference, Brandon Sanderson's Stormlight Archive books are said to be around 450k words), compared to this post with 3 exercises and maybe 7k words. I suppose I need 64 posts at that rate, and I'm only on 7, but there are certainly enough problems out there to write up 64 posts and it's just a question of making time for them. The +1 makes every read unaligned. With a 4KiB page size, this makes one read touch 3 pages Different offsets can overlap the same pages, causing seemingly unintended reads from page cache Depending on the page size, reads can extend past the end of the file and cause a panic Claude Fable 5: 29.1% Claude Opus 4.8: 25.0% GPT-5.6 Sol: 24.4% You have CPU X. If you want to know how fast this loop is, what parameters do you need to know? Given these parameters, how fast should the loop be? I'm often mistaken for a performance engineer, but I think it's more like, I sometimes solve performance problems due to a combination of having an unusual degree of experience with benchmarking / evals / experimental design for a programmer due to my hardware background (where this is a more mature field than it is in software, as discussed here ) and my propensity to go after problems that can easily be linked to dollar value such as this , or this , but I'm as likely to solve a performance problem as any other problem and I don't have a particularly deep or broad knowledge of performance problems compared to people who do performance work day in and day out. [return] On the topic of whether or not it makes sense to filter by language, I looked into this after seeing people cite this post about token efficiency of languages; the results from that post didn't replicate for non-trivial tasks, but there seemed to be real enough differences between languages that it plausibly made sense to filter by language . In particular, when agents fail to implement something, especially on lower effort levels, it's often due to some idiosyncratic incorrect usage of a language. For example, for the zstd eval in that post, agents using Clojure would very often rely in incorrect semantics of byte conversion, but agents using Java, which fundamentally has the same operations available, wouldn't make that mistake. [return] At a meta level, people who I talk to who generally have comments I find reasonable on other topics don't take these headline/summary results very seriously. For example, In a comment on the usefulness of these benchmarks, Em Chu said: twitter/hacker news sentiment, which at least won't be misleadingly precise, feels like a better way to tell whether or not a model is useful, as strange as that is (which unfortunately requires reading a lot of hacker news posts, so I cannot recommend.) I usually find my eyes skipping over anything that looks like an LLM benchmark since the chances that it's worth reading are near zero. (I wish I would do this for hacker news comments too.) Most people I know whose judgment I trust take a similar approach (sometimes substituting opinions of people they know for online sentiment). The exceptions to this are generally people who work in the field and look at a ton of benchmarks and do some kind of mental aggregation of them. For example, when I talk to Max Bitker (who runs an RL environment startup), he's familiar with seemingly every public benchmark and can seem to predict what sentiment will be like a couple weeks after a model release based on his mental model of the aggregate landscape of all the benchmarks out there, but that's a very different thing than looking at a summary score metric and time-consuming enough that, unless you work on AI, this seems more like a hobby interest than something you'd reasonably do to evaluate model effectiveness (nothing against hobby interests; I have lots of hobby interests). For a concrete example of what it looks like to take the results of these benchmarks seriously vs. what's observed in the real world, here's a thread where someone creates an effectiveness vs. cost table of the then-new 5.6 Sol/Terra/Luna vs. 5.5 using DeepSWE results. Someone (who I'd agree with, although I'd phrase it differently) replies Bullshit. Have you actually used the models or are you having a wank? According to this table 5.6-sol xhigh would be both cheaper and better than 5.5 xhigh. In what reality is that actually true? Another person replies to them with In none. I think the benchmark tasks are really straighforward in which case the table may be true. I don't think that's quite fair (I've tried tasks where it seems to be true) but, in general, people mostly have very different experiences than public benchmarks are showing. This seems to be understood by quite a few people, from people I know in person to random internet commenters. But it's not universal, as I still see people passing around these scores to explain why they use some model and effort level, which doesn't seem justified in general. [return] In general, I don't turn these examples into an exercise unless it's a common claim that I see many times because completely unsupported incorrect claims happen so frequently that it's not really interesting in the general case. [return] I've been publishing these on Patreon without a strong reason to. I make a bit of money off Patreon, but if I was optimizing for money I think it would obviously be the right choice to just publish everything publicly since the potential delta in earnings from maybe getting connected to a potential job dwarfs what I could earn directly via Patreon. That goes double considering how bad I am at interviews (I've almost exclusively gotten jobs where the interview is formality as my odds of passing an interview are otherwise close to zero; the last time I did an interview, I failed a phone screen on a leetcode-style question, and when I pass those I'll typically fail the full interview later if it's a real interview). I originally started publishing things on Patreon that I thought were too small or inconsequential to turn into a "real" blog post, but then I got in the habit of publishing things on Patreon and haven't written much publicly for a while. This kind of post, which is part of a long set of exercises, falls squarely into the category of things that seems too small and inconsequential to put onto the main blog. If you have opinions on this, I'd be curious to hear what you think. The idea behind this series was that I wanted to write some kind of tutorial or blog post to help people with better benchmarking and evals. But, my feeling on evals is that it's more about avoiding mistakes than following some particular process, so there isn't really a step-by-step guide format that works in the general case. I know there are approaches to experimental design where they teach you to do things like drawing a causal graph and then looking at the graph to figure out the potential problems, e.g., collider bias . Just from seeing how people do data analysis before and after learning techniques like this, I don't think this makes a huge difference on average (although a few people do find it very useful). I saw a criticism of this as a generalized way to avoid experimental design issues somewhere (maybe from Andrew Gelman) that the problem is that everything is related to everything, so you're still applying your judgement when you create the causal graph. Being able to mechanically see the problems once the graph is created doesn't stop someone from drawing the wrong graph in the first place. A vaguely related idea that I saw when I read the first chunk of McElreath's Statistical Rethinking many years ago, hoping to learn some process that would lead to rigorous statistical analysis is that there isn't really such a process and you ultimately have to use your judgement to decide if something makes sense or not. That being the case, I thought a series of exercises might work, so I had this idea to write maybe 50 or 100 exercises into a single post. That seems quite do-able for small exercises, but it's clear from watching people learn a variety of things that giving people a bunch of small exercises and then hoping that people generalize the techniques onto larger, more complex, exercises, doesn't usually work very well. Once you start adding in larger, more complex, exercises, you quickly get beyond the length of a long post, even by the standards of this blog, which has this 32k word post on what the FTC got wrong in their 2011-2012 investigation of Google (for reference, a typical novel is often said to be 80k-100k words). In general, I've avoided putting multi-part posts on the blog because, as a reader, I much prefer it if things are all in one post instead of spread across some kind of long series of posts. I get that authors often prefer multi-part posts because it generally results in more traffic, better odds of a post going viral on social media, etc., but I've always optimized this blog to be more like what I want to read than to maximize page views. In this case, it seems like the single-post version could easily be as long as a doorstop fantasy novel (for reference, Brandon Sanderson's Stormlight Archive books are said to be around 450k words), compared to this post with 3 exercises and maybe 7k words. I suppose I need 64 posts at that rate, and I'm only on 7, but there are certainly enough problems out there to write up 64 posts and it's just a question of making time for them. [return]

0 views
ava's blog 7 months ago

AI clones and data protection

A few days ago, news spread through the web about a Meta project for letting an AI run the social media account of a deceased person, as it could emulate the person's activity like posting content and responding to messages. The goal was to maintain engagement on the platform and reduce the grief when a person passes away. If you believe a screenshot going around, a poster on 4chan revealed this years prior, saying it has the internal name " Project Lazarus ", referencing the Lazarus of Bethany . While Meta spokespeople said they had no plans to pursue this (yet?), there are other services like ELIXIR AI , who want to push digital immortality via an " eternal doppelganger from a customer's lifetime data ". In general, we are already dealing with a deluge of deepfakes online. Not only are people using AI to remove the clothes on the images of people, but they are also creating new images, video and audio material with a person's physical and vocal likeness, trained on even just a handful of photos, up to terabytes of video material if it's a popular and active YouTuber. This also happens in the education and entertainment industry. Notable figures have digital copies in museums and other places to be interacted with, and deceased actors get "revived" to show up or to lend their voice to a character. Researchers talk about this as " spectral labour " in a " postmortal society ", meaning the " exploitation of digital remains for aesthetically pleasing, politically charged, and communicative representations ". The companies that provide these resurrection services are referred to as "* transcendence industry ". The tech and availability is changing fast, and as with any developing field, it can be hard to apply existing legal frameworks that didn't have this use case in mind specifically. While I have to leave the issues around general ethics and monetization to another day, I'd like to focus further on ( European ) data protection and privacy laws! First up, good to know: Are your body and voice capable of being personal data? Yes! They make you identifiable. You can also see this in Article 9 GDPR , which prohibits processing data related to racial or ethnic origin, and the processing of genetic data, biometric data for the purpose of uniquely identifying a natural person and data concerning health, unless it falls into very specific allowed purposes. Your body carries this type of information. Additionally, the European Data Protection Board has also given out guidelines that suggest that voice data is considered inherently biometric . That means making a model of you via a series of photos from different angles, motion capture, voice recordings etc. is processing personal data, some of it sensitive data under Article 9 GDPR . This is then further processed during AI training and finetuning to reproduce a person's physical or vocal likeness reliably. Recital 51 of the GDPR mentions: " The processing of photographs should not systematically be considered to be processing of special categories of personal data as they are covered by the definition of biometric data only when processed through a specific technical means allowing the unique identification or authentication of a natural person. " So, simply taking or editing some pictures is not considered processing of special (sensitive) personal data, as this would reach too far; it needs specific technical means that take measurements to turn it into biometric data, like when you set up to unlock your phone for FaceID, or if you get an eye scan or fingerprint scan to be able to unlock a door with your eye or finger. There are actually quite a few interesting discussions on whether taking a picture of someone wearing glasses is processing data about their health - but I digress. AI models trained on reproducing your likeness reliably have turned you into a dataset, a bunch of measurements, a model, which generally counts as biometric data processing. Once data processing falls under Article 9 GDPR , legal bases of Article 6 GDPR - like legitimate interest, fulfillment of a contract, compliance, etc. - fall away, as only the specific allowances of Article 9(2) GDPR make an exception from the general prohibition. In the case of the entertainment and education industry, that will likely reduce it to the explicit consent named in: " a) the data subject has given explicit consent to the processing of those personal data for one or more specified purposes, except where Union or Member State law provide that the prohibition referred to in paragraph 1 may not be lifted by the data subject " This impossible for people who have already passed away, but you can usually ask their estate/remaining family members for consent in their stead though. Consent, under GDPR, always needs to be given freely. Article 7 GDPR says, among other things, " When assessing whether consent is freely given, utmost account shall be taken of whether, inter alia, the performance of a contract, including the provision of a service, is conditional on consent to the processing of personal data that is not necessary for the performance of that contract. " It's also referred to as the Coupling prohibition . That may be difficult to avoid in the entertainment industry: What if getting the role is tied to agreeing with AI cloning, even if not explicitly, then implicitly? What if refusing, at some point, gets you blacklisted? What if agreeing has an effect on your success and income at an agency? Many actors now have to deal with this as studios try to reduce the time spent on set for actors to reduce costs via AI clones, and also want a backup AI clone option in case the actor dies during production. What's also problematic: How do you you freely and productively consent to something you don't understand? Of course, you don't need to be an expert in everything, but usually, stuff is pretty straightforward in terms of taking pictures, video or audio recordings. Explaining how AI models work has been very difficult, even for people deeply involved, but now we are likely dealing with studios who are completely uninvolved with the company that actually handles the AI cloning. And also, how do you properly inform someone contractually about how their data will be used and processed if the field and possibilities develop so fast? It's difficult to anticipate potential future use cases you'd want or not want. And if the data gets sent to somewhere outside of the EEA, you have a so-called ' third country transfer ' to worry about, which needs special considerations and protections. Now, we have established that your body and voice are personal data, and that processing them in this way falls under the GDPR. What about your clone data within the training set, or the output itself? This is a bit controversial at the moment! It makes sense that this would also be regarded as personal data, as it is still identifiably you when it gets used with zero alterations. Where it gets problematic are use cases where you lend your likeness to something, especially your voice. For example: Use for an ad that is not supposed to literally embody you, but instead just offer a neutral voice-over; or you're the new voice for Siri; or you might synchronize a cartoon character. Obviously, your friends and family could reliably recognize your voice, so it could count. But there are data protection authorities in Germany who vouch for a more usage-oriented interpretation, meaning: If your clone is used to identify you and represent you in some content, it is biometric identification, but if your voice is just used as one voice for a job, it's just imitation or synthesis. I don't agree with that, as the data itself and the identification methods are still the same and current synthesis usage can still be used for biometric identification later, but that's the discussion right now. Okay, so this type of data generally falls under the GDPR. That means I have the same rights as usual - right to deletion, too. But how I said before in my post about AI and the GDPR , it can be hard or impossible to delete data from a training set. Deleting the entire model or having to retrain it would incur massive costs and losses; it would make more sense instead to have more individual models that can be more easily separated and deleted, if possible. But since that is not in control of the person holding the rights, it might be hard to enforce them. It's equally difficult for the output of these models: That falls under the GDPR as well and would be affected by the deletion or restriction requests, but that's also where lots of contracts, laws and rights collide. It needs to be assessed in each case individually. There was an interesting case in Germany a while ago: A YouTuber using an AI generated voice from a famous voice actor in his videos, and the actor objecting to it. The YouTuber had around 190,000 subscribers and an associated online shop. He published two political satire videos on YouTube that used an AI-generated voice that closely imitated the actor’s voice, but didn't label that it was AI. Viewers in the comments identified the voice as the actor's as well. The videos ended with references to the online shop, which sold merchandise linked to the channel’s political opinions. The actor objected to the use of his voice towards the YouTuber and requested he stops, and wanted reimbursement of legal costs. The YouTuber agreed to cease, but refused to pay damages, arguing that the voice was synthetic, lawfully acquired from an AI voice provider, and used for satire rather than advertising. Meanwhile, the actor claimed that the AI-generated voice constituted use of his personal voice, that the processing occurred without consent, and that it created the impression that he endorsed the videos and products. He now also sought compensation equivalent to his usual licensing fees. The court sided with the actor and saw that the YouTuber interfered with the actor’s right to his own voice, as despite being AI, the voice closely imitated a distinctive personal characteristic. The court considered that a significant part of the audience would associate the voice with the data subject, which was sufficient to establish personal attribution. As expected and further explained above, the court rejected the reasoning of "legitimate interest" in Article 6(1)(f) GDPR , and saw that the voice primarily served the YouTuber's commercial interests. No exemption applied under Article 85 GDPR as the processing was neither journalistic nor genuinely artistic in a way that would justify overriding the data subject’s rights, particularly given the commercial context and the lack of transparency about the AI-generated nature of the voice. As a consequence, the court ordered the YouTuber to pay €4,000 as a fictitious license fee for the unauthorized use of the voice and €1,155.80 for reimbursable legal costs, plus interest. I think it's important to talk about this as it doesn't only affect actors and voice actors, or historical people's likeness used in the classroom or at concerts, but also has the potential to affect you. Your employer could ask to make an AI clone of you, for example. At the data protection law conference I attended in Munich, the AI Officer of a big insurance firm said they are holding the data protection trainings required for their employees via AI generated videos and AI generated avatars of him and his colleague. That means employees that need to do the training get in a digital environment with this avatar of him that responds, smiles, blinks and leads them through the material, some of which is AI generated as well. Circling back again to this research paper , we are at a point in time where, depending on your job, your body and voice can work independently of you, and people can monetize you after your death not by further selling what you produced in your lifetime, but producing new things indefinitely that you had no hand in while you were life, or selling access to "you". Eerie, huh? So it's important to know your rights and what's going on in the space :) Reply via email Published 02 Mar, 2026

0 views
Simon Willison 9 months ago

Fly's new Sprites.dev addresses both developer sandboxes and API sandboxes at the same time

New from Fly.io today: Sprites.dev . Here's their blog post and YouTube demo . It's an interesting new product that's quite difficult to explain - Fly call it "Stateful sandbox environments with checkpoint & restore" but I see it as hitting two of my current favorite problems: a safe development environment for running coding agents and an API for running untrusted code in a secure sandbox. Disclosure: Fly sponsor some of my work. They did not ask me to write about Sprites and I didn't get preview access prior to the launch. My enthusiasm here is genuine. I predicted earlier this week that "we’re due a Challenger disaster with respect to coding agent security" due to the terrifying way most of us are using coding agents like Claude Code and Codex CLI. Running them in mode (aka YOLO mode, where the agent acts without constantly seeking approval first) unlocks so much more power, but also means that a mistake or a malicious prompt injection can cause all sorts of damage to your system and data. The safe way to run YOLO mode is in a robust sandbox, where the worst thing that can happen is the sandbox gets messed up and you have to throw it away and get another one. That's the first problem Sprites solves: That's all it takes to get SSH connected to a fresh environment, running in an ~8GB RAM, 8 CPU server. And... Claude Code and Codex and Gemini CLI and Python 3.13 and Node.js 22.20 and a bunch of other tools are already installed. The first time you run it neatly signs you in to your existing account with Anthropic. The Sprites VM is persistent so future runs of will get you back to where you were before. ... and it automatically sets up port forwarding, so you can run a localhost server on your Sprite and access it from on your machine. There's also a command you can run to assign a public URL to your Sprite, so anyone else can access it if they know the secret URL. In the blog post Kurt Mackey argues that ephemeral, disposable sandboxes are not the best fit for coding agents: The state of the art in agent isolation is a read-only sandbox. At Fly.io, we’ve been selling that story for years, and we’re calling it: ephemeral sandboxes are obsolete. Stop killing your sandboxes every time you use them. [...] If you force an agent to, it’ll work around containerization and do work . But you’re not helping the agent in any way by doing that. They don’t want containers. They don’t want “sandboxes”. They want computers. [...] with an actual computer, Claude doesn’t have to rebuild my entire development environment every time I pick up a PR. Each Sprite gets a proper filesystem which persists in between sessions, even while the Sprite itself shuts down after inactivity. It sounds like they're doing some clever filesystem tricks here, I'm looking forward to learning more about those in the future. There are some clues on the homepage : You read and write to fast, directly attached NVMe storage. Your data then gets written to durable, external object storage. [...] You don't pay for allocated filesystem space, just the blocks you write. And it's all TRIM friendly, so your bill goes down when you delete things. The really clever feature is checkpoints. You (or your coding agent) can trigger a checkpoint which takes around 300ms. This captures the entire disk state and can then be rolled back to later. For more on how that works, run this in a Sprite: Here's the relevant section: Or run this to see the for the command used to manage them: Which looks like this: I'm a big fan of Skills , the mechanism whereby Claude Code (and increasingly other agents too) can be given additional capabilities by describing them in Markdown files in a specific directory structure. In a smart piece of design, Sprites uses pre-installed skills to teach Claude how Sprites itself works. This means you can ask Claude on the machine how to do things like open up ports and it will talk you through the process. There's all sorts of interesting stuff in the folder on that machine - digging in there is a great way to learn more about how Sprites works. Also from my predictions post earlier this week: "We’re finally going to solve sandboxing" . I am obsessed with this problem: I want to be able to run untrusted code safely, both on my personal devices and in the context of web services I'm building for other people to use. I have so many things I want to build that depend on being able to take untrusted code - from users or from LLMs or from LLMs-driven-by-users - and run that code in a sandbox where I can be confident that the blast radius if something goes wrong is tightly contained. Sprites offers a clean JSON API for doing exactly that, plus client libraries in Go and TypeScript and coming-soon Python and Elixir . From their quick start: You can also checkpoint and rollback via the API, so you can get your environment exactly how you like it, checkpoint it, run a bunch of untrusted code, then roll back to the clean checkpoint when you're done. Managing network access is an important part of maintaining a good sandbox. The Sprites API lets you configure network access policies using a DNS-based allow/deny list like this: Sprites have scale-to-zero baked into the architecture. They go to sleep after 30 seconds of inactivity, wake up quickly when needed and bill you for just the CPU hours, RAM hours and GB-hours of storage you use while the Sprite is awake. Fly estimate a 4 hour intensive coding session as costing around 46 cents, and a low traffic web app with 30 hours of wake time per month at ~$4. (I calculate that a web app that consumes all 8 CPUs and all 8GBs of RAM 24/7 for a month would cost ((7 cents * 8 * 24 * 30) + (4.375 cents * 8 * 24 * 30)) / 100 = $655.2 per month, so don't necessarily use these as your primary web hosting solution for an app that soaks up all available CPU and RAM!) I was hopeful that Fly would enter the developer-friendly sandbox API market, especially given other entrants from companies like Cloudflare and Modal and E2B . I did not expect that they'd tackle the developer sandbox problem at the same time, and with the same product! My one concern here is that it makes the product itself a little harder to explain. I'm already spinning up some prototypes of sandbox-adjacent things I've always wanted to build, and early signs are very promising. I'll write more about these as they turn into useful projects. Update : Here's some additional colour from Thomas Ptacek on Hacker News: This has been in the works for quite awhile here. We put a long bet on "slow create fast start/stop" --- which is a really interesting and useful shape for execution environments --- but it didn't make sense to sandboxers, so "fast create" has been the White Whale at Fly.io for over a year. You are only seeing the long-form articles from my blog. Subscribe to /atom/everything/ to get all of my posts, or take a look at my other subscription options . Developer sandboxes Storage and checkpoints Really clever use of Claude Skills A sandbox API Scale-to-zero billing Two of my favorite problems at once

1 views
マリウス 9 months ago

Updates 2025/Q4

This post includes personal updates and some open source project updates. As the year comes to a close, I’d like to begin this update by sharing a famous (and sadly now gone ) tweet . My goal is not only to remind those who have seen it before, but also to introduce it to those who haven’t, along with the thoughts it inevitably sparks. It’s a way to preserve this rare gem of social media for posterity. Below is the original post, with added speaker information for easier reading. Warning: This text is a bit long. If you’d rather skip ahead to the next part of the update, click/tap here . Someday aliens are going to land their saucers in a field somewhere in New Jersey and everything is going to go just fine right up until we try to explain our calendar to them. Humans: “yeah we divide our year into a number of sub units called ‘months’ made up a number of days, and they’re not all the same length” Aliens: “I guess that’s unavoidable, if your rotations-count per orbit is a prime number” Humans: “yeah, our’s isn’t prime” Aliens: “but surely you have most of these ‘months’ the same length and just make the last one shorter or longer?” Humans: “No… They’re different lengths following no logical pattern” Aliens: “what” Humans: “and we further subdivide the months into ‘weeks’, which is 7 days.” Aliens: “ahh, so each month is an integer multiple of weeks?” Humans: “that would make sense, but no. Only one is, sometimes” Aliens: “SOMETIMES?!” Humans: “yeah our orbit around the sun isn’t an integer number of days, so we have to change the number of days to in a year from time to time” Aliens: “oh yes, a similar thing happens on Epsilon Indi 7, where they have to add an extra day every 39 years to keep holidays on track” Humans: “yeah that’s how ours work! Although the ratio doesn’t work out cleanly, so we just do every 4 years, except every 100 years, except except every 400 years” Aliens: “oh, you number your years? What’s the epoch?” Humans: “uh, it’s supposed to be the birth of a religious leader, but they got the math wrong so it’s off by 4 years, if he existed at all.” Aliens: “if? You based your calendar off the birth date of someone you’re not sure exists?” Humans: “yeah. He’s written about in a famous book but historical records are spotty.” Aliens: “interesting. I didn’t realize your planet was one of the ones with a single universal religion, that usually only happens in partial or complete hive minds.” Humans: “uhh, we’re not.” Aliens: “You’re not?!” Humans: “yeah we have multiple religions.” Aliens: “oh but they all have a common ancestor, which agrees on the existence of that leader, right?” Humans: “uh, no. Two of the big ones do, but most of the others don’t believe in him” Aliens: “YOUR CALENDAR IS BASED ON A RELIGIOUS LEADER THAT NOT EVERYONE BELIEVES IN?” Humans: “well, on his birth. And yeah, we got it wrong by a couple years.” Aliens: “OK, fine. So, you have somewhat complicated rules about when you change the length of your years, and I’m scared to ask this, but… You definitely just add or subtract that extra day at the end, right?” Humans: “…. Nope.” Aliens: “At the start of the year? " Humans: “nah. The end of the second month” Aliens: “WHY WOULD IT BE THE SECOND MONTH?” Humans: “I’m not sure, really.” Aliens: “huh. So at this point I’m dreading asking this, but how do you measure time within each day?” Humans: “oh that’s much simpler. Each day is divided into hours, each hour has minutes, and each minute has seconds.” Aliens: “ok. And 10 of each?” Humans: “10 hours? No. There’s 24 hours, 60 minutes, 60 seconds” Aliens: “…. I thought you said you used a base-10 counting system” Humans: “we do! Mostly. But our time system came from some long gone civilization that liked base-60 like 5000 years ago” Aliens: “and you haven’t changed it since?” Humans: “No.” Aliens: “huh. Okay, so why 24? That’s not a divisor of 60” Humans: “oh because it’s actually 12!” Aliens: “what” Humans: “yeah each day is 24 hours but they are divided into two sets of 12.” Aliens: “and that’s 5 12s, right, I see the logic here, almost. So like, after hour 12, it becomes the second half, which is 1?” Humans: “No, after 11.” Aliens: “oh, you zero-index them! So it’s hours 0-11 in the first half, then 12-23 in the second half?” Humans: “No. 12 to 11 in the first half, and again in the second half” Aliens: “please explain that before my brain melts out my mouth” Humans: “the first hour is 12. Then the next one is 1, then it goes back up to 11, then 12 again” Aliens: “that is not how numbers work. And how do you tell first 12 apart from second 12?” Humans: “oh we don’t use numbers for that!” Aliens: “you don’t number the two halves of your day?” Humans: “nah, we call them AM and PM” Aliens: “WHAT DOES THAT MEAN” Humans: “I think it’s ante-meridian and post-meridian? But I’m not sure, I dont know much Latin” Aliens: “Latin?” Humans: “yeah it’s an ancient language from an old empire which controlled a lot of the world and we still use some of their terms” Aliens: “oh, and that was the civilization that liked base-60 and set up your time system?” Humans: “that would make sense, but… No, completely different one.” Aliens: “okay, and what do you do to if you want to measure very short times, shorter than a second?” Humans: “oh we use milliseconds and microseconds” Aliens: “ahh, those are a 60th of a second and then 60th of the other?” Humans: “No. Thousandths.” Aliens: “so you switch to base-10 at last, but only for subdivisions of the second?” Humans: “yeah.” Aliens: “but at thousands, ie, ten tens tens” Humans: “yeah. Technically we have deciseconds and centiseconds, which are 1/10 of a second, and 1/100 of a second, but no one really uses them. We just use milli.” Aliens: “that seems more like a base-1000 system than a base-10 system.” Humans: “it kinda is? We do a similar thing with measures of volume and distance and mass.” Aliens: “but you still call it base-10?” Humans: “yeah” Aliens: “so let me see if I get this right: Your years are divided in 10 months, each of which is some variable number of days, the SECOND of which varies based on a complex formula… and each day is divided into two halves of 12 hours, of 60 minutes, 60 seconds, 1000 milliseconds?” Humans: “12 months, actually.” Aliens: “right, because of the ancient civilization that liked base-60, and 12 is a divisor of 60.” Humans: “No, actually, that came from the civilization that used latin. Previously there were 10.” Aliens: “what” Humans: “yeah the Latin guys added two months part of the way through their rule, adding two more months. That’s why some are named after the wrong numbers” Aliens: “you just said two things I am having trouble understanding. 1. Your months are named, not numbered? 2. THE NAMES ARE WRONG?” Humans: “yep! Our 9th month is named after the number 7, and so on for 10, 11, and 12.” Aliens: “your 12th month is named… 10?” Humans: “yeah.” Aliens: “what are the other ones named after?!” Humans: “various things. Mainly Gods or rulers” Aliens: “oh, from that same religion that your epoch is from?” Humans: “uh… No. Different one.” Aliens: “so you have an epoch based on one religion, but name your months based on a different one?” Humans: “yeah! Just wait until you hear about days of the week.” Aliens: “WHAT” Humans: “so yeah we group days into 7-day periods-” Aliens: “which aren’t an even divisor of your months lengths or year lengths?” Humans: “right. Don’t interrupt” Aliens: “sorry” Humans: “but we name the days of the week, rather than numbering them. Funny story with that, actually: there’s disagreement about which day starts the week.” Aliens: “you have a period that repeats every 7 days and you don’t agree when it starts?” Humans: “yeah, it’s Monday or Sunday.” Aliens: “and those names come from…” Humans: “celestial bodies and gods! The sun and moon are Sunday and Monday, for example” Aliens: “but… I looked at your planet’s orbit parameters. Doesn’t the sun come up every day?” Humans: “yeah.” Aliens: “oh, do you have one of those odd orbits where your natural satellite is closer or eclipsed every 7 days, like Quagnar 4?” Humans: “no, the sun and moon are the same then as every other day, we just had to name them something.” Aliens: “and the other days, those are named after gods?” Humans: “yep!” Aliens: “from your largest religion, I imagine?” Humans: “nah. That one (and the second largest, actually) only has one god, and he doesn’t really have a name.” Aliens: “huh. So what religion are they from? The Latin one again?” Humans: “nah, they only named one of the God-days” Aliens: “only on… SO THE OTHER DAYS ARE FROM A DIFFERENT RELIGON ENTIRELY?” Humans: “Yep!” Aliens: “the third or forth biggest, I assume?” Humans: “nah, it’s one that… Kinda doesn’t exist anymore? It mostly died out like 800 years ago, though there are some modern small revivals, of course” Aliens: “so, let me get confirm I am understanding this correctly. Your days and hours and seconds and smaller are numbered, in a repeating pattern. But your years are numbered based on a religious epoch, despite it being only one religion amongst several.” Humans: “correct so far” Aliens: “and your months and days of the week are instead named, although some are named after numbers, and it’s the wrong numbers” Humans: “exactly” Aliens: “and the ones that aren’t numbers or rulers or celestial objects are named after gods, right?” Humans: “yup!” Aliens: “but the months and the days of the week are named after gods from different religons from the epoch religion, and indeed, each other?” Humans: “yeah! Except Saturday. That’s the same religion as the month religion” Aliens: “and the month/Saturday religion is also from the same culture who gave you the 12 months system, and the names for the two halves of the day, which are also named?” Humans: “right! Well, kinda.” Aliens: “please explain, slowly and carefully” Humans: “yeah so cultures before then had a 12 month system, because of the moon. But they had been using a 10 month system, before switching to 12 and giving them the modern names” Aliens: “the… Moon? Your celestial body?” Humans: “yeah, it completes an orbit about every 27 days, so which is about 12 times a year, so it is only natural to divide the year into 12 periods, which eventually got called months” Aliens: “ok, that makes sense. Wait, no. Your orbital period is approximately 365.25 days, right?” Humans: “yeah. That’s why we do 365 or 366 based on the formula” Aliens: “but that doesn’t work. 365 divided by 27 is ~13.5, not 12” Humans: “yeah I’m not sure why 12 was so common then. Maybe it goes back to the base 60 people?” Aliens: “okay so one final check before I file this report: Years are numbered based on a religious leader. Years always have 12 months, but the lengths of those months is not consistent between each other or between years.” Humans: “don’t forget the epoch we number our years from is wrong!” Aliens: “right, yes. And your months are named, some after a different religion, and some after numbers, but not the number the month is in the year.” Humans: “right. And when we change the month lengths, it’s the second one we change” Aliens: “how could I forget? After months you have a repeating ‘week’ of 7 days, which is named after gods from two religons, one of which is the month-naming one, and a nearly extinct one. And you don’t agree when the week starts.” Humans: “nope! My money is on Monday.” Aliens: “that’s the Monday that’s named after your moon, which supposedly influenced the commonality of the 12 months in a year cycle, despite it orbiting 13 times in a year?” Humans: “correct!” Aliens: “and as for your days, they split into two halves, named after a phrase you don’t really understand in the long dead language of the same culture that named the months and Saturday.” Humans: “Yep. I took some in college but all I remember is like, ‘boy’, ‘girl’, ‘stinky’, ‘cocksucker’” Aliens: “charming. And then each half is divided into 12 hours, but you start at 12, then go to 1, and up to 11” Humans: “all I can say is that it makes more sense on analog clocks.” Aliens: “i don’t know what that is and at this point I would prefer you not elaborate. So each of those hours is divided into 60 minutes and then 60 seconds, and this comes from an ancient civilization, but not the one that gave you the month names” Humans: “yep. Different guys. Different part of the world.” Aliens: “ok. And then after seconds, you switch to a ‘base-10’ system, but you only really use multiples of a thousand? Milliseconds and microseconds?” Humans: “right. And there’s smaller ones beyond that, but they all use thousands” Aliens: “right. Got it. All written down here. Now if you’ll excuse me, I just gotta go make sure I didn’t leave my interociter on, I’ll be right back.” The tall alien walks back into their saucer without a wave. The landing ramp closes. The ship gently lifts off as gangly landing legs retract. There’s a beat, then a sudden whooshing sound as air rushes back into the space that previously held the craft, now suddenly vacuum. NORAD alarms go off briefly as an object is detected leaving the earth’s atmosphere at a significant fraction of the speed of light. In the years to come, many technological advances are made from what was left behind, a small tablet shaped object made of some kind of artifical stone/neutrino composite material. The alien message left on screen is eventually translated to read “Untitled Document 1 has not been saved, are you sure you wish to quit? (yes) (no) (cancel)” Many years have passed, and we await the day the aliens return. They have not. As I mentioned in the previous update ( here ), my beloved 9barista coffee brewer started malfunctioning at the end of Q3, likely due to the age of the O-ring sealing the water chamber and the descaling process I performed. However, I was able to fix the machine using the official 9barista repair kit and have been using it daily ever since. In recent months, though, I’ve almost entirely switched to decaf coffee in an effort to reduce some recurring headaches I’ve been dealing with for a while. It doesn’t seem to be the constant consumption of caffeine causing the issue; rather, the headaches mostly appeared whenever I skipped a cup, making it seem more like a caffeine withdrawal effect. Although I continued to experience headaches in Q4, those were likely linked to being sick rather than coffee, see below . That said, both the frequency and intensity of the headaches have noticeably decreased. Toward the end of Q4, I also began experimenting with additions to my coffee, specifically Lion’s Mane , a well-known component of traditional Chinese medicine that’s often advertised as an alternative to caffeine. It’s believed to enhance focus without the jitters or cold sweats that usually come with high caffeine consumption. In mid-October, I unfortunately got hit with a heavy dose of COVID-19 , which knocked me out for three weeks and has had (once again) a lasting impact on my overall health. Since I was mostly bedbound during that time, I spent some of it exchanging COVID anecdotes with the friendly folks in the community channel . I was surprised to find that many people there had similar negative experiences, particularly in relation to post-vaccine infections. My first encounter with COVID was back in 2020, and for me, it turned out to be little more than a bad flu, with two days of fever and some headaches. I didn’t lose my sense of smell or taste, nor did I experience any long-term effects. In fact, the most troubling part of the whole COVID experience for me back then wasn’t the sickness itself, but the fear of being picked up by local authorities for having an elevated body temperature. This was especially concerning because I was still traveling the world at the time, enjoying the eerie quiet of empty airports and cities. Due to increasing social pressure, especially from governments imposing heavy travel restrictions, I was eventually pushed into getting vaccinated shortly after that. Unfortunately, my body didn’t handle the two doses very well. I experienced extreme muscle pain and a general sense of being under the weather . While those side effects faded after a few days, in the months that followed, I felt more tired and inflamed than usual, with recurring flu-like symptoms and headaches. At some point, COVID hit me again, but this time it was really bad. I ended up battling a fever around 40°C/104°F for over a week, and I was completely knocked out for almost two months. On top of that, I began experiencing cardiovascular symptoms, which persisted for months and even years afterward. The adverse effects I’d never experienced before didn’t just show up with subsequent COVID infections, but also with regular flu. There was one point when a strain of Influenza B hit me so hard that I had to visit the emergency room, which is something I’d never done before, even though I’d never received the annual flu vaccine. To this day, it feels like ever since I got the Pfizer shots (for which I had to sign a liability waiver), my health has been in a constant decline, especially whenever influenza or COVID strikes. No matter how healthy my diet or activity level, it doesn’t seem to make much of a difference. In fact, the ongoing inflammation and regular flu-like symptoms have made it especially hard to push myself during a workout or a run. At some point, I started digging deeper into the issue, with regular bloodwork and visits to specialists, particularly cardiologists. Unfortunately, as is often the case, no medical expert has been able to diagnose the underlying issue(s) or propose meaningful solutions. Society seems quick to ridicule those who seek to improve their health through unconventional methods, yet most people fail to recognize the globally poor state of healthcare, which leaves people stranded, regardless of how much private money they’re willing to spend to solve their problems. Long story short, will I continue to get the battletested shots for Hepatitis , Tetanus , and other dangers humanity faces? Definitely. But will I be significantly more skeptical of vaccines that didn’t undergo year-long trials and were fast-tracked by every government on Earth to curb an allegedly man-made virus that escaped a biological research facility, all while creating shareholder value ? You bet! Note: This is a complex topic, and everyone has their own personal experience. For many, the COVID shots seem to have had no negative side effects. For some, however, they did. This doesn’t mean that COVID doesn’t exist, nor that lizard overlords used it as an excuse to inject us with nanobots . Medicine certainly has its flaws, and financial interests were prioritized over absolute safety, something that’s happened in other areas as well over the past few years (e.g., Boeing ). If, however, you think there’s a pLaNdEmIc or some intentional, eViL gEnEtIc ExPeRiMeNt at play, there’s no need at all to launch your XLibre Xserver to reach out to me with fUrThEr iNfO oN tHiS tOpIc . Thank you. You might have noticed that the main menu at the top of this website has grown, now including a now page , as well as a link to Codeberg, but more on that in a second . The now page is exactly what the name suggests: a now page . Given the failure of social media, I’ve pretty much given up on maintaining a public profile for posting status updates. Up until the end of 2021, I was still actively maintaining a Mastodon account alongside a TUI client , but that eventually fell apart for multiple reasons. After that, I used Nostr for a while, but eventually gave it up too. These days, I’m somewhat active on Bluesky , though my account isn’t publicly available. I don’t have high hopes for Bluesky either, and I’ll probably delete my account there one day, at the latest when Bluesky inevitably becomes enshittified . The now page , however, is here to stay. It will continue to feature short, tweet -like updates about all sorts of things. If you’re interested, feel free to check it every once in a while. I might even activate a dedicated RSS feed for it at some point. For the past few months I’ve been silently moving most private project repositories away from GitHub towards privately hosted instances of Forgejo – a terrible name, btw – as well as many of my public GitHub projects to Codeberg . One reason to do so is… well, let me just quote Andrew Kelley here, who probably put it best: […] the engineering excellence that created GitHub’s success is no longer driving it. Priorities and the engineering culture have rotted, leaving users inflicted with some kind of bloated, buggy JavaScript framework in the name of progress. Stuff that used to be snappy is now sluggish and often entirely broken. Most importantly, Actions has inexcusable bugs while being completely neglected . After the CEO of GitHub said to “embrace AI or get out” , it seems the lackeys at Microsoft took the hint, because GitHub Actions started “vibe-scheduling”; choosing jobs to run seemingly at random. Combined with other bugs and inability to manually intervene, this causes our CI system to get so backed up that not even master branch commits get checked. However, unlike most people who decided to migrate from GitHub to Codeberg, I won’t be deleting my repositories on GitHub just yet. Instead, I’ve updated all my local clones to point toward Codeberg, and I’ve enabled synchronized pushes from Codeberg to GitHub, as I plan to continue using GitHub’s workflows. “But why?!” you might ask. The reason is simple: Because I’m happy to waste Microsoft’s resources on automated tests and build actions. While I could use Codeberg’s Woodpecker CI or even set up my own, I’m more than content to keep using GitHub’s CPU cycles for free to build my silly little projects , while hosting the primary source code repositories on Codeberg. Since there doesn’t seem to be a way to disable Pull Requests on GitHub for my respective projects, I’ve added pull request templates that warn against opening PRs there. I’ve also disabled the Issues tab and updated the short descriptions to link to Codeberg. Additionally, my overview page on GitHub now links to Codeberg, with the GitHub repositories listed explicitly as GitHub mirrors . At the end of October I encountered an issue with ungoogled-chromium on my Gentoo laptop that prevented it from compiling successfully. Upon further investigation I learned that, quote: Using the system libc++ is no longer supported This change was driven by the Chromium project and affected my, along with many others’, Gentoo installation, due to the use of system libraries instead of the in-tree ones provided by Chromium. As mentioned here , this is a security concern, as users will need to trust the Chromium-provided libraries over those from their distribution. In case you’ve ever wondered why anyone in 2025 would still compile from source when tHe PeRfOrMaNcE bEnEfItS aRe NeGlIgIbLe , this is one of the key reasons why compiling from source still makes sense and, in fact, is more important than ever. The same projects that have historically taken a controversial stance on sensible default settings are now the ones seemingly rejecting security-critical system components in favor of their own. Tl;dr: If you’re using Chromium or a Chromium-based browser (other than ungoogled-chromium on Gentoo through PF4Public ’s repository), it’s highly likely that your browser is not using your system maintainer’s libraries, but rather Chromium’s in-tree ones with whatever versions and features the Chromium developers deem necessary and sensible. In what to this day remains a mystery the keyboard switch of my key has decided that it rejects its existence and seemingly removed one of its legs, presumably in an effort to escape and start a new live. I had documented the whole incident on Keebtalk for anyone who’s equally as puzzled by this as I am. I invested quite some time in pursuing my open source projects in the past quarter, hence there are a few updates to share. At the beginning of November I released Zeit v1.0.0 , a full rewrite of my command line time tracking tool. In case you missed it, I summed up everything in a dedicated post and have also published a dedicated project website that will soon act as more than just a landingpage. With 📨🚕 (MSG.TAXI) continuing to grow and evolve, Overpush has received a few important updates improving its stability with long-running XMPP connections. One thing that made me very happy throughout the debugging phase was the fact that despite stability of Overpush not being perfect , no messages ever got lost whatsoever and were always successfully delivered the moment the service would be able to reach the target platforms (specifically XMPP in this case). :-) If you haven’t yet tried Overpush yourself, I encourage you to sign up on 📨🚕 and give it a go. If you find the service useful you’ll be able to easily spin up your own Overpush instance further down the line and won’t have to depend on any closed-source proprietary platfrom. As those of you idling in the community channel might know, I’ve been actively working on an internet forum software for some time now . What kick-started my efforts was the desire to set up a support and discussion forum for 📨🚕 , among other things, but I was dissatisfied with the existing options. I was looking for an internet forum that… The first thing that came to mind was phpBB , which has been around for decades and appears to be one of the few options that (unlike Discourse and Lemmy ) doesn’t require users to have JavaScript enabled. Sadly, phpBB is a monster . It has too many features, takes a lot of time to properly install and configure, and, more importantly, when looking at its runtime dependencies and extensions, it requires some recurring effort to keep it safe and sound. Don’t get me wrong, unlike Discourse , which is frankly terrible, phpBB is a solid piece of software. However, for my use cases, I wanted something more lightweight that is easy to set up and run. None of the existing solutions, with maybe one or two exceptions like DFeed , came close to what I was looking for. And those that seemed like a good fit sadly lacked some functionalities, which would have required me to extend them in ways that would significantly alter core functionality. These changes would have likely not been merged upstream, meaning I’d probably end up maintaining my own fork anyway. The bulletin board I’m working on is built in Go, as a single executable binary (without CGO ) for all major platforms ( Linux , * BSD , (maybe) Plan 9 , macOS , and (maybe) Windows ) that doesn’t require a runtime (like Erlang / Elixir , PHP , Ruby , Python , or worse, Node.js ) or even assets (e.g., HTML/CSS files) anywhere in . It renders modern HTML on the server-side and doesn’t require any user-side JavaScript to be enabled. The forum will support only PostgreSQL (single- and multi-node setups), require a Redis/Valkey instance or cluster, and use S3-compatible storage for user content (e.g., profile pictures, file uploads, etc.). The platform will allow sign-ups via email and XMPP addresses, supporting notifications and replies through both services. But don’t worry: OAuth authentication via popular providers will also be available. Additionally, the forum will feature a dedicated REST API that, unlike Lemmy ’s or Discourse ’s APIs, will be much easier to work with. One mid-term goal is to integrate this API into Neon Modem Overdrive , which will become its official TUI client. Short story long: I’ve been working on this project for a little while now and expect to release a first live demo around February ‘26. While many basic features are already implemented, there are still details I’d like to perfect before publishing the first version. I’ll set up a live online demo for people to try out first, and only after fine-tuning the code based on feedback will I wrap up the actual source release. The forum will be open-source and available under the SEGV license. If this sounds interesting to you and you’d like to participate in development or testing, reach out to me ! With that said, I sincerely hope you’re enjoying a wonderful holiday season and gearing up for a great new year! As we wrap up 2025, I’ll be taking a well-deserved break from posting here on the site. The start of 2026 is shaping up to be quite hectic, and I’m looking forward to diving into some exciting projects, especially focusing on the ▓▓▓▓▓▓▓▓▓▓▓ bulletin board system I’m building. I hope this season brings you moments of joy, relaxation, and time well spent with those who matter most. May the new year be filled with new opportunities, exciting adventures, and personal growth. I look forward to reconnecting with all of you next year ! Stay safe, take care of yourselves, and I’ll see you in 2026! Can use an existing database to authenticate users and/or… Supports simple email/username signups. Ideally supports notifications and replies via email. Is lightweight and doesn’t require a ton of runtime dependencies. Does not require users to have JavaScript enabled . Does not overwhelm me with administrative features. Is somewhat easily themeable.

0 views
Ludicity 11 months ago

I Am Out Of Data Hell

I haven’t written anything in four months. This period of prolonged silence is best explained by the unexpected difficulties of the business that I started in 2024, on the assumption that it must be possible to make an ethical, human-centered business on the basis of how incompetent the general market seemed. In fact, one difficulty in particular has threatened to put an end to my writing in its entirety. This topic is embarrassing and I have no idea how to navigate it, so I’m just going to rip the band-aid off in the hopes of keeping the blog alive. Please brace yourselves, and I hope this doesn’t disappoint you too much. After a quiet start, the past few months have been going really well , it looks like I was approximately right about everything re: the median software nerd, median manager, and median [insert salaried profession here] being trivially easy to outperform for fun and profit. To my absolute dismay, it would appear that I am good at management and the much more dangerous L-word . 1 Hrrrrrrk. I think I’m going to be sick. People shouldn’t be writing things like that outside of LinkedIn. I am wracked by feelings of wrongness. The word “pallid” springs to mind. I fear that I’ve acquired one of those parasites that alters its host’s behaviour and then walked straight out into genpop. This sounds like a good thing, but I’ve been writing and deleting this post over and over because it turns out that I was totally prepared to write the “I was wrong about everything” post, but not the “I was right about everything” post. The first issue I’ve been faced with is that it’s hard to write about succeeding without feeling self-congratulatory, but I suppose the only solution to that is uh, not write about what I’m experiencing, which leads to aforementioned four months of quiet. And secondly, the nature of my day-to-day problems has shifted dramatically – the industry has not suddenly become sane, but my relationship to it is totally different, which runs the risk of threatening the nebulous concept of the “voice” of the blog. Nonetheless, it is what it is, so I’m going to talk a bit about what I’ve learned over the past few months, and maybe a bit about the future of the blog. In 2023, Egbert Teeselink at TalkJS wrote this about an article I put out on the importance of disregarding most language about improvements that corporate management uses with staff : It seems to me that there’s a class of programmer that will take an overpaid job at a terrible BigCo, spend their evenings writing ranty blog posts about how terrible it all is, culminating in the inevitable “I quit” article, only subsequently to accept a job at a different terrible BigCo. At no point does this article, or most articles like it, do any effort to realize that things are not always like this. Even if there is some unavoidable law that huge companies inevitably fill their ranks with idiots, like this article suggests, you do not need to work at a huge company . Most people do not work at huge companies. There’s lots of amazing tech companies with fulfilling jobs and they want you, now. But there’s this super prevalent idea that keeps getting pumped around the blogosphere that it’s absolutely impossible to not work at a terrible huge company and therefore you cannot possibly escape, and I quote, “going home and despairing”. Two years in, I agree with the thrust of this critique. I've matured a lot, especially in the last six months, with the painful corollary that I must have had a lot of maturing to do. In the article, I did not say that it was impossible to escape the soul-crushing vortex of the open-floor office, but what Egbert correctly detects and points out is that I wasn’t sure if it was possible . Or rather, it was obvious that it was possible, but I didn’t know how to do it, and to make matters worse there is no good social script for solving this problem. I didn’t have to announce my lack of direction for him to pick up on it. For example, consider the case where I decide to become a professional poker player. I don’t really know anything about playing poker, but a cursory browsing of a few forums will give me some results that will at least put me on the right track. I just had a go at this, and the first result seems pretty mediocre but it includes three books – I’ll be much better off after reading one of them than I am now. Now, what if you wanted to find a “good” job? That is considerably harder, and the pathway is littered with a huge number of what I think of as "traps", i.e, things that are prima facie plausible but are total wastes of your time, with the worst ones being those that put you in a loop where you don't realize you're trapped. For one thing, Egbert’s note that you don’t have to work at a large company is incomplete – I agree that a smaller organizations have more variance , so there is a higher probability that a given job will be decent, but there is also a higher chance that you’ll work with a fucking psychopath. I know of a CEO here in Melbourne who sues people who quit , which is insane, doesn’t work, and is nonetheless going to totally ruin your year. A fair amount of our current work is spent cleaning up the messes created by small consultancies that were founded by non-technical leadership who wanted to cash in on tech sector payouts, and their workers are not treated well. A small company improves your odds a bit, but is nowhere near enough to solve the problem. Secondly, if you look for advice on how to get noticed at a better organization, you will mostly get really bad advice , or the advice will be lacking enough that it’s hard to execute on correctly unless you already know what’s correct (in which case you didn’t need advice). Do you know how many people ask me to read their CVs, as if that makes even one lick of difference? An employer that’s asking for your CV as anything other than a formality is going to pay your mortgage, but they are probably not going to meet the standard of “genuinely good to work at”, and more importantly this is a terrible way to get a job . People who are good at their jobs can do this for 1-2 years at a time with no result, and when they fail to get jobs they assume their CVs are incorrect rather than that the approach is fundamentally misguided. Or you’ll be told to network. With who? Where? How? I can’t speak for the rest of the world, but most of the big meetups in Melbourne are absolutely flooded with people awkwardly job-seeking, and they’re generally pretty miserable. Nonetheless, many people keep attending them because they’ve been informed that this is what networking is, and when they fail to get results, they assume that they must have bad networking skills. There is a lot of advice out there, but the advice that you’re going to run into as an average person making a foray into the job search was mostly written by people at dysfunctional normal companies, so you are not only getting advice from people that have failed to make it out, but the person doing the writing may not actually understand that they’re in the “bad” part of the tech sector. After all, many people don’t realize that it’s a bad thing not to have any working tests in most codebases – why would you expect them to recognize other problems consistently, let alone be able to solve them? And similarly, if people who are in the “bad” part of the tech sector can’t clearly understand that there’s a better part to it (or cannot perceive how to get there), people in the “good” part of it similarly struggle to understand just how bad the bad part is, and are unable to understand why someone would stay there. Those reasons are myriad, but suffice it to say that from a “normal” company, the path to sanity is so thoroughly obfuscated that when I started the business I was quietly worried that maybe you couldn’t get paid for thoughtful work . This is absolutely not true, but it was really, really not obvious to me that this was the case, and I suspect that in another two years I will start to forget what it was like being in the Scrum hellscape, wherein I will be writing messages asking “why you just don’t quit lol?” Suffice it to say that there are totally reasonable companies out there, and they probably do want you as Egbert pointed out. The problem is that many of them probably only want you after you’ve quit the typical company because many people staying in that situation aren’t psychologically ready to be in a high-functioning team. Many of those teams are filled with lots of people who have strong networks and self-confidence, and that’s an important part of what makes them high functioning. Even on my small team, we can only handle the income variation gracefully because everyone knows that, in a serious pinch, they can generate high-paying work for themselves to stave off homelessness. (I also quietly suspect that there are so few good companies around that, if you have really high standards, starting one is easier than finding one, or I obviously wouldn’t have started one. A quick glance at Egbert’s company reveals that it is very similar to mine despite no cross-pollination – a small team, fully remote, with minimal ritual, high autonomy, all the perks are traded out for money, and we both inexplicably program in Elixir. I think I might just be in the sweet spot where I’ve matured enough to converge on that operating model while still remembering what I thought two years ago.) No, seriously, why? When you suggest people should “just quit”, there is inevitably someone that goes “Just quit ? And then what, starve to death? End up in another terrible job because we’re all at the mercy of employers? God, what a privileged take.” I have been that someone. In fact, if you look at Egbert’s comment, you will see that someone immediately responded with something close to that, and it doesn’t look ridiculous in context. If you have 2.5 children, no money saved up, a crippling mortgage, etc, let us agree that we will gently part ways here. You are an adult who knows the state of your finances, I don’t know you at all, and none of this applies to you. Go in peace. 2 For everyone else, oh man , I really should have quit earlier. It was such an absolute waste of time hanging around trying to do anything else. I had, of course, quit previous jobs and ended up at other terrible companies – largely the result of my network, because most people work at companies with grey-slurry workplaces, so most networks land you there! – but quitting into unemployment is totally different. Putting aside that you won’t have money coming in, the most salient obstacle to doing this isn’t actually a financial consideration for many people – especially if those people have been on even low-tier software salaries for more than a year or two. No, it’s actually the vague aura permeating society that if you lose your job, you will mysteriously die . Every time a friend gets depressed and wants to leave a job, they’ll run it by me, and the clear subtext is that their friends and family talk about it as if they’re in the midst of a psychological crisis, and maybe they should slow down, line up another job before quitting, and so on, because only crazy people unhook themselves from the misery-to-money device. The thing is they are in the midst of a psychological crisis, precipitated by their working conditions, and the advice to continue under those conditions can be just as irresponsible, if not more so, than quitting entirely. It’s not that there’s no risk, but the fear seems to outweigh the risk. For many of the people that talk to me about this, the real risk isn’t homelessness or whatever they are subconsciously imagining, but being forced to do a very embarrassing couch-surf. Every single person I know that has been fired or made redundant has managed to find another job. Every single one of them is fine now, and every single one of them got a raise at the new place. Those are the people that were forced out and looked for a new job. The people that deliberately quit into unemployment basically get jobs whenever they want now, and their main issue is that they want to conduct personal projects but are constantly being lured by the siren song of huge paydays. A lot of things change when you deliberately opt out, and then don’t waste that freedom throwing a million CVs into the black hole that is Seek 3 . For one, you can take some time to seriously get your life in order and wait until your brain is ready to start doing things again. You’ve got a hell of a lot more energy because now you don’t have to commute if you don’t want to, and when you do you can go back home to unwind pretty quickly. You can meet a ton of very interesting people because you’ve got the bandwidth, energy, and flexibility to swing by their office and spend $10 on coffee for the two of you. You’re totally free to read anything, write anything, and work on anything you want. In my experience this miraculously transmutes itself into money because it gives you fun things to talk about, which it turns out people love, and as an added bonus you have lots of fun things to talk about. There are plenty of ways to screw this up, but the main one (in my assessment) for programmers is following the deeply-embedded-and-seldom-introspected scripts like trying to spin up a SaaS offering, which is appealing for many irrelevant-to-success reasons like “I might be able to go months without having to think about rejection and get to feel productive tinkering”. But you will probably spend six months on this and earn $0, with some small probability of earning between $1 and $10,000,000, then be forced to go back to a bad job. Providing extremely boring services, like specialized contracting and education, removes much of both ends of the spectrum – you’re a lot less likely to earn $0 and $10,000,000 is out of the question. And listen, a few million dollars sounds great, I agree, but you probably need something closer to $100,000 to be in a sustainable situation, so aim for that! Of course, there is risk. There always is. A few of my high school peers have passed away from things like freak aneurysms in the past few years. There was a canoeing incident while I was at university that killed a classmate, the usual assortment of cancers and car crashes, that sort of thing. Some number of them would have spent their last year doing tedious bullshit that they could have otherwise avoided. Yes, we all have to go through some level of that – my taxes are boring but it seems a small price to pay to spend 2026 out of jail, presuming I make it there – but it’s worth considering that if all your risk is identical to everyone else’s risk, acquired on autopilot, then we aren’t even minimizing risk so much as not making a decision . This may surprise you but every single person that has ultimately decided to hire us has had no idea who I am, or about this blog, and so on. At most, we’ve gotten introduced by blog readers and the rest of the conversation has panned out as if we were any other consultancy, and we’ve just started work on developing a sales pipeline that is completely unrelated to the blog. One of our team recently managed to close a deal that way while working full-time at a pretty intensive day job, in a state he recently moved to, with a long commute, a mortgage, and two children. This problem is very tractable . Once we had some of that sales pipeline working, I had a dreadful moment where I realized that I would struggle immensely to participate in a conventional job search again because the idea that you need permission to earn money is sort of ridiculous, but it’s the only model that most people in corporate environments have ever experienced. In one sense you do need permission to earn money if you aren’t stealing it – someone has to agree they need something from you. But the insane theatre, the middle managers, the CVs and cover letters and recruiters, it’s all so fucking silly once you’re outside of it. It turns out that sales do not have to be much harder than going “Ah, you’ve got a problem? I could take a look at that for you and come up with a plan to fix it up” and then someone wires you $10,000 if they think it’s plausible that you could solve the problem 4 . It’s really not that different to selling someone plumbing, except your margin is almost 100% in software, you don’t need a professional qualification or to leave your house, and in fact it’s pretty amazing across basically every dimension, save that some people have such insane ideas about software that it’s too late to save them. 5 This is very empowering! If you’re competent and put some amount of effort into demonstrating that competence, you too can be unshackled from the dreadful grasp of recruiters, with their too-white smiles, gray handshakes, and inability to respond to emails on time. If you’re able to generate even half of what you need to live on solo, then you’ve doubled the amount of time you’ll survive without conventional employment. The “Fuck you!” 6 money was inside you the whole time! Everyone has to learn how to get along with other people and make concessions, but with a little bit of autonomy it has turned out that this is possible to do in an entirely reasonable manner, where each concession and conversation makes sense and isn’t ridiculous theatre performed by people who don’t even have an interest in the job being carried out correctly. And yes, you’ll have to compete against liars sometimes, but you have beautiful asymmetry in your favour – you can actually build a track record of shipping, and they will siphon away the people that you wouldn’t want to work with anyway . If someone thinks they can slap an LLM into their company 7 and it’ll solve their problems, and you can’t explain to them why the current generation of models won’t work, you don’t want them as a customer. They will be disappointed with your frail mortal delivery, being unacceptably tethered to cruel reality, and we must unfortunately leave them in the Desert Of Not Shipping, where the buzzards will sup upon their desiccated flesh or, worse, put them on Azure. When I was back in university, I used to make a small amount of money teaching statistics to psychology students. It was only maybe three or four thousand dollars a year, and the marketing consisted entirely of one comment on a university Facebook page per year. It is not that much harder to sell competent IT consulting. I’ve been worried a bit about whether the change in context will make my writing disinteresting. I’m sure that for some people it will be gratifying to see that a philosophy of ethics, competence, and passion can win out (at least at a scale that makes a small difference and feeds a small team). But perhaps it’s also unrelatable, or dissipating anger through action saps something from my voice. I have serious, effective outlets for frustration, and if I can’t make a difference then I probably haven’t closed the deal and I will soon be very far away from the feeling of helplessness. When someone is so incompetent that talking to them begins to drive me to annoyance, then we probably aren’t going to close the sale and the problem solves itself from an emotional perspective. (Also when a team is very competent and doesn’t need consultants, we also don’t close the sale, and the problem – me – solves itself. The system works!) I have a lot more time to read, but the nature of what I read is pretty different. I can afford to read much more complicated texts, especially now that we’re pulling in enough revenue that I don’t need to churn through so much sales material, which is generally effective but not interesting to write about for an audience with taste 8 . Because my days are mostly free when we aren’t actively engaged, I can take a lot of time to synthesize them into our working practice. I’m currently thinking very heavily about how to take some of the conversations I’ve had with Iris Meredith on Clausewitz and applying some of the thoughts from her to the design of software engineering delivery cadence. I would have never had that time months ago. I’m a lot more empathetic when people struggle with their jobs or are even totally driven by their egos, but also now get asked to weigh in on things like redundancies and project cancellations, 9 so my opinions feel less heated but also have the capacity to incinerate things rather than produce disgruntled essays. I see lots of crazy stuff, but usually under the cover of NDAs, so I can’t really write about them unless the stories are of a very precise nature wherein they’re still fun after obfuscation and wouldn’t upset a client even in anonymized form. When I do see crazy stuff, I now wield the Righteous Power of Consultant Positioning and lay about my surroundings with great force and holy vengeance. I can channel the anger into real results, and it is probably not an exaggeration to say that the outcomes I’ve managed in the past month trounce most of my accomplishments in the first five years of my career. I got a security department to whitelist Python for every analyst in a Big Corp. Do you understand how powerful I’ve become? Do you see what I have wrought? And, most importantly, even though I am some sort of borderline management consultant now, as pointed out by our lawyer Iain McLaren who I have yet to forgive for this label, an employee at a large corporation high-fived me two weeks ago and said that working with the team is the most fun they’ve had in years. We may have our sales collapse, especially when the likely AI bubble pops and does God-knows-what to IT budgets around the world, but I simply can’t imagine going back to a company where I don’t have a good time. In any case, I hope that explaining what has been going on for the past few months and the context for any upcoming writing will make it easier to write in the future and maybe be a little bit more entertaining than the same old stuff as always. I’ve grown past many authors over the years, and I hope that a shift in tone is going to be something that keeps things interesting for readers instead of being an unwelcome development. In 2023, someone wrote this to me: I'm sorry, but this needs a privilege/gratitude check. You are guaranteed your salary, and you're welcome to take on the same level of risk your company is by starting your own. If you think it's so easy go ahead. I want to take a moment to say “It really was easy, you impotent fool! Look upon my works and perish , nerd! You could have written the same thing to hundreds of people on the internet and they would have backed down, but you somehow picked the one person that would remember your smug comment and build all of this out of spite. It’ll be yeeeeeears before you’re ready to face me! Ahahahahahahaha–” Mira Welner, the first person that we hired to do some contracting, has written about the experience of working with our team here . Choice quotes include “I can't tell you that Nikhil is the best of software engineer I've ever met” and “And he's not quite as insufferable as his blog makes him seem”.  ↩ Unfortunately because of the healthcare situation in the U.S., you are possibly all in some unique category that has to calculate the probability of a serious medical condition cropping up before taking the leap. It’s interesting to me that a culture that does actually have an immense entrepreneurial drive also has the single biggest impediment to starting ventures that I’ve seen in any country, even some genuinely “third-world” ones.  ↩ The funny thing is that, by all accounts, Seek has a really good internal culture, especially around software engineering. All that power aimed at filling roles that Seek themselves would never post in a thousand years.  ↩ The art of both being and appearing plausible are very deep, and we consult with David Kellam when we want to get better. I tease David somewhat about the energy of his public profile, but he has a mind like a thousand knives and knows his stuff.  ↩ That might also be true of plumbing, but I sure hope it isn’t.  ↩ A phrase that I think Taleb coined, which he defined as “Enough money to say ‘Fuck you’ before hanging up the phone.”  ↩ For readers in the distant future, in 2025 we had something called the “AI bubble” and it was really funny, and in the corporate sales context largely consisted of all the people that didn’t understand the words “crypto” and “quantum” getting together to say “AI”, or in an astonishing two separate cases , “A1 (A-One)”. Whether or not AI is a big thing in 2070 is more-or-less irrelevant to how dumb it is right now.  ↩ That’s you, reading several thousand word long essays instead of being on TikTok, xoxo, sorry that you'll be unable to communicate with anyone in twenty years.  ↩ I once told a reader that I’m very opposed to layoffs. They cited one of my old blog posts, Flexible Schemas Are The Mindkiller , and asked me if I could honestly say that it wouldn’t be morally and ethically correct to recommend the guy that leaked all the patient records in that story be fired. I sort of just vaguely mumbled and accepted that he had caught me in a devious trap of my own devising.  ↩ Mira Welner, the first person that we hired to do some contracting, has written about the experience of working with our team here . Choice quotes include “I can't tell you that Nikhil is the best of software engineer I've ever met” and “And he's not quite as insufferable as his blog makes him seem”.  ↩ Unfortunately because of the healthcare situation in the U.S., you are possibly all in some unique category that has to calculate the probability of a serious medical condition cropping up before taking the leap. It’s interesting to me that a culture that does actually have an immense entrepreneurial drive also has the single biggest impediment to starting ventures that I’ve seen in any country, even some genuinely “third-world” ones.  ↩ The funny thing is that, by all accounts, Seek has a really good internal culture, especially around software engineering. All that power aimed at filling roles that Seek themselves would never post in a thousand years.  ↩ The art of both being and appearing plausible are very deep, and we consult with David Kellam when we want to get better. I tease David somewhat about the energy of his public profile, but he has a mind like a thousand knives and knows his stuff.  ↩ That might also be true of plumbing, but I sure hope it isn’t.  ↩ A phrase that I think Taleb coined, which he defined as “Enough money to say ‘Fuck you’ before hanging up the phone.”  ↩ For readers in the distant future, in 2025 we had something called the “AI bubble” and it was really funny, and in the corporate sales context largely consisted of all the people that didn’t understand the words “crypto” and “quantum” getting together to say “AI”, or in an astonishing two separate cases , “A1 (A-One)”. Whether or not AI is a big thing in 2070 is more-or-less irrelevant to how dumb it is right now.  ↩ That’s you, reading several thousand word long essays instead of being on TikTok, xoxo, sorry that you'll be unable to communicate with anyone in twenty years.  ↩ I once told a reader that I’m very opposed to layoffs. They cited one of my old blog posts, Flexible Schemas Are The Mindkiller , and asked me if I could honestly say that it wouldn’t be morally and ethically correct to recommend the guy that leaked all the patient records in that story be fired. I sort of just vaguely mumbled and accepted that he had caught me in a devious trap of my own devising.  ↩

0 views
Ginger Bill 1 years ago

Package Managers are Evil

n.b. This is a written version of a dialogue from a YouTube video: 2 Language Creators vs 2 Idiots | The Standup Package managers (for programming languages) are evil 1 . To start, I need to make a few distinctions between concepts a lot of programmers mix up: These are all separate and can have no relation to one another. I have nothing wrong with packages, in fact Odin has packages built into the language. I have nothing wrong with repositories, as that’s how a lot of people discover new packages—a search engine, something I think everyone uses on a daily basis 2 . Build systems are usually language dependent/specific, and for Odin I have tried minimize the need for a build system entirely (at least as a separate thing) where most projects will build with , which works due to the linking information being defined in the source code itself with the system. This leaves package managers ; what do they do? Package managers download packages from a repositories, handles the dependencies and tries to fix them, and then it downloads its dependencies, and its dependencies, and its dependencies… and you can probably see where my criticism is going. This is the automation of dependency hell . The problem is that not everything needs to be automated, especially hell. Dependency hell is a real thing which anyone who has worked on a large project has experienced. Projects having thousands, if not tens of thousands, of dependencies where you don’t know if they work properly, where are the bugs, you don’t how anything is being handled—it’s awful. This the wrong thing to automate. You can do this manually, however it doesn’t stop you getting into hell, rather just slow you down, as you can put yourself into hell (in fact everyone puts themselves into hell voluntarily). The point is it makes you think how you get there, so if you have to download manually, you will start thinking “maybe I don’t want this” or “maybe I can do this instead”. And when you need to update packages, being manual forces you to be very careful. That’s my general criticism: the unnecessary automation. Most packages managers usually have to define what a package is, because the language itself does not have a well defined concept of a package in the language. JavaScript is great example of this as there are multiple different package managers for the language ( being one of the most popular), but because each package manager defines the concept of a package differently, it results in the need for a package manager manager . Yes… this is a real thing. This is why I am saying it is evil , as it will send you to hell quicker. When using some languages, such as Go, most people don’t seem to need many third-party packages even though Go has a built-in package manager. The entrance to hell seems to far and hard to get to 3 . The reason such languages don’t fall into this trap as quickly is that those languages have a really good core/standard library—batteries included. When using Go for example, you don’t need any third-party libraries to make a web server, Go has it all there and you are done. Go even has Go compiler built into the standard library; in fact it has two, a high level one for tooling and one which is the actual compiler itself 4 . In real life, when you have a dependency, you are responsible for it 5 . If the thing that is dependent on you does something wrong, like a child or business, you might end up in jail, as you are responsible for that. Package dependencies are not that far different but people trust them with little-to-no verification. And when something goes wrong, you are on the hook to maintain it. It is a thing you should worry about and take care of. A common thing that people bring up about package managers are security risks. There are indeed serious problems, especially when you blindly trust things you have just randomly started depending from off the internet. However for my needs, those are not even the biggest worries for what I work on, but they might be for you! For me at work, we use currently use SDL2 for our windowing stuff at work, and we have found a huge amount of bugs and we hate it to the point that I/we will probably write our own window and input handling system from scratch for each Operating System we target. At least it is our code and we can depend on it and correct it when things go wrong; we are not having an extra dependency. I know SDL2 is used by millions of people, but we keep hitting all of the bugs. “But it’s great though!”. SDL3 might fix it all but the time to integrate SDL3 would be the same time I could write it from scratch. I am not advocating to write things from scratch. I wish there were libraries I could say that they “Just Work™”, but I still have to depend on them, and they are a liability; not just security liabilities but just bug liabilities. Each dependency is a potential liability. People rarely, if ever, vet their code, especially third-party code. Most people assume random code off the internet works . This is a societal issue where programmers are very high trusting in a place where you should have the least amount of trust possible. To put it bluntly, a lot of programmers come from a highly developed countries which are in general high trust societies, and then they apply that to the rest of their online world. This means you only need one person to do something malicious to something millions depend on to screw everything up. It doesn’t even have to be malicious but a funny bug, where if you clicked one pixel on the screen, it is Rick Rolling you. n.b. This argument was made by ThePrimeagen; not myself We’ve had an explosion of engineers over the past ten years, which have come just into the advent of all of these package managers coming out, for all of these languages, all at the same time. So programming felt very daunting; when you don’t know how something works, it feels very daunting, especially when you first start out. The thing that is confusing, especially the high-trust argument that was being made, there is this weird Gell-Mann amnesia effect going on. You read one page and it’s all about horses and you feel “man I know a lot about horses”. Then flip to the next page and it’s about Javascript and you go “man they got everything wrong about Javascript”. Then you flip the next page and “man I know a lot about beetles”. You’ve just forgot that they are super wrong on the thing you understood, but you think everything else is correct. You’ll find engineers who will go “some of my coworkers are so horrible, hey, let me download this library off the internet, this is going to be awesome”. It’s crazy as if they look and go “wow, one third of our staff cannot program anything, also I am going to trust every open source package I’ve downloaded”. So there is this Gell-Man amnesia in programming code, where people who do open source or open things are viewed as the best of the engineers when that isn’t true. Most people assume programming is like every other industry, like actual engineering which has been around for thousands of years, or modern science which has been around for about half a millenium. People trust who they perceive to be the “experts”, as you see all of these articles, books, conference videos, etc, and they all tell you stuff but for the most part which does not necessarily seem true. I remember trusting those who were perceived to be “experts” which were esposing “wisdom”. However, as I have programmed more over the years, I realized there is very very little wisdom in this industry. This industry is 70–75 years old at best, and that is not old enough to have any good evolutionary selection pressure. It is not old enough to get rid of the bad things—it hasn’t evolved quick enough. We will find out in a few HUNDRED years, and I mean hundreds , what is actual good wisdom is in programming. There are some laws we know like Conway’s Law , where “organizations which design systems (in the broad sense used here) are constrained to produce designs which are copies of the communication structures of these organizations”. Or to rephrase it in programming terms, the structure of the code will reflect the company that programs it. But that is one of the only laws that we know to exist. My general view is that package managers (and not the things I made distinctions about) are probably in general a net-negative for the entire programming landscape, and should be avoided if possible. Excerpt from the Odin FAQ: https://odin-lang.org/docs/faq/#how-do-i-manage-my-code-without-a-package-manager Through manual dependency management. Regardless of the language, it is a very good idea that you know what you are depending on in your project. Copying and vendoring each package manually, and fixing the specific versions down is the most practical approach to keeping a code-base stable, reliable, and maintainable. Automated systems such as generic package managers hide the complexity and complications in a project which are much better not hidden away. Not everything that can be automated ought to be automated. The automation of dependency hell is a case which should not encouraged. People love to put themselves in hell, dragging others down with them, and a package manager enables that. Another issue is that for other languages, the concept of a package is ill-defined in the language itself. And as such, the package manager itself is usually trying to define the concept of what a package is, which leads to many issues. Sometimes, if there are multiple competing package managers with different definitions of what a package is, the monstrosity of a package-manager-manager arises and the hell that brings with it. The term “evil” is being used partially hyperbolic to make a point.  ↩︎ I primarily use DuckDuckGo , but I also use Google and many others because they are pretty much all bad.  ↩︎ This sentence is a quote from ThePrimeagen from that video.  ↩︎ ThePrimeagen mentioned the “Klingon Approach” as a joke. This refers to Klingons (a species of humanoids in Star Trek) which have redundant organs. A very nerdy joke.  ↩︎ This comment was from José Valim (the creator of the Elixir programming language) and this was a very good point which I wanted to add to this article.  ↩︎ Package Repositories Build Systems Package Managers The term “evil” is being used partially hyperbolic to make a point.  ↩︎ I primarily use DuckDuckGo , but I also use Google and many others because they are pretty much all bad.  ↩︎ This sentence is a quote from ThePrimeagen from that video.  ↩︎ ThePrimeagen mentioned the “Klingon Approach” as a joke. This refers to Klingons (a species of humanoids in Star Trek) which have redundant organs. A very nerdy joke.  ↩︎ This comment was from José Valim (the creator of the Elixir programming language) and this was a very good point which I wanted to add to this article.  ↩︎

0 views
Emil Privér 1 years ago

About AI

For the last 1.5 years, I have forced myself to work with and learn AI, mostly because the future of software engineering will inevitably have more AI within it. I’ve focused on optimizing my workflow to understand when AI is a genuinely useful tool versus when it’s a hindrance. Now, 1.5 years later, I feel confident enough to say I’ve learned enough about AI to have some opinions, which is why I’m writing this post. AI has become a race between countries and companies, mostly due to status. The company that creates an AGI first will win and get the most status. The models provided by USA-based companies are heavy and require a lot of resources to operate, which is why we build data centers of GPUs in the USA and Norway . At the same time, China delivers models that are almost as good as Claude Opus but need a fraction of the resources to deliver a result. There’s a strange disconnect in the industry. On one hand, GitHub claims that 20 million users are on Copilot, and Sundar Pichai says that over 25% of the code at Google is now written by AI. On the other hand, independent studies show that AI actually makes experienced developers slower. The common thread seems to be that companies selling AI solutions advocate for their efficiency, while independent sources tell a different story. It’s also incredibly difficult to measure AI’s true efficiency. Most metrics focus on whether we accept an AI’s suggestion, not on whether we accept that code, leave it unedited, and ship it to production—mostly because tracking that is a profoundly difficult task. My experience lands somewhere in the middle. I’ve learned that AI is phenomenally good at helping me with all the “bullshit code”: refactoring, simple tasks that take two minutes to develop, or analyzing a piece of code. But for anything else, AI is mostly in my way, especially when developing something new. The reason is that AI can lure you down a deep rabbit hole of bad abstractions that can take a significant amount of time to fix. I’ve learned that you must understand in detail how you want to solve a problem to even have a fair shot at AI helping you. When a task is more than just busywork, AI gets in the way. The many times I’ve let AI do most of the job, I’ve been left with more bugs and poorly considered details in the implementation. Programming is the type of work where there often is no obvious solution; we need to “feel” the code as we work with it to truly understand the problem. When I work on something I’ve never worked on before, AI is a nice tool to get some direction. Sometimes this direction is really good, and sometimes it’s horrible and makes me lose a day or two of work. It’s a gamble, but the more experienced you become, the easier it is to know when you’re going in the wrong direction. But I am optimistic. I do think we can have a beautiful future where engineers and AI can work side-by-side together and create cool stuff. I’ve used IDEs, chat interfaces, web interfaces like Lovable, and CLIs, and it’s with CLIs that I’ve gained the most value. So far, CLIs are the best way to work with AI because you have full control over the context, and you are forced to think through the solution before you hit enter. In contrast, IDEs often suggest code and make changes automatically, sometimes without my full awareness. In a way, CLIs keep me in the loop as an active participant, not a passive observer. For everything I don’t like doing, AI is phenomenally good. Take design, for instance. I’ve used Lovable and Figma to generate beautiful UIs and then copied the HTML to implement in an Elixir dashboard, and the results have been stunning. I also use AI when I write articles to help with spelling and maintaining a clear narrative thread. It’s rare, but sometimes you get lucky and the AI handles a simple task for you perfectly. There are a couple of really nice things about AI that I’ve learned. I previously mentioned getting rid of the boring bullshit stuff I do in my daily work—this bullshit stuff is about 5% of everything I do on a daily basis. For example, today I managed to one-shot solve a new feature using AI. The feature was adding search to one of our APIs using Generalized Search Tree. I added the migration file with the create index, and the AI added query parameters to the HTTP handler and updated the database function to search on if we have added a search parameter. But the thing that AI has provided me the most value in my day-to-day work is that instead of bringing a design to a meeting, we can bring a proof of concept. This changes a lot because developers can now easily understand what we want to build, and we can save tons of time on meetings. Building PoCs is also really good when we enter sales meetings, as a common hard problem in sales is understanding what the customer really wants. When we can bring a PoC to the meeting, it’s easier to get feedback faster on what the customer wants because they can try it and give direct feedback on something they can press on. This is an absolute game-changer. Another really nice thing is when we have a design and we ask the AI to take a Figma design into some React code—it can generate the boilerplate and some of the design. It doesn’t one-shot it, but it brings some kind of start which saves us some time. The reason why AI works well with frontend is that the hard part about frontend is not writing the code from a design; it’s optimizing it for 7 billion people with different minds and thoughts, different devices, internet connections, and different disabilities. I use research mode from time to time when I have some bug that is super hard to find. For instance, I switched from to in Go a while ago. When I pushed to production, we got an error where the connection pool manager gave an error because the transaction ID already exists, so I rolled back. I did some quick DuckDuckGoing to find the issue but didn’t find it. So I asked Gemini to do research on the error, and a comment in some GitHub issue suggested changing the default query exec mode to cache describe from cache statement: By making this change, I solved the problem. I probably would have spent another 20 minutes debugging or searching for the solution, and research mode could point me in the correct direction, which helped me solve it faster. But it’s a gamble—sometimes what the AI suggests is the wrong direction and you lose some time. Vibe coding, for those who don’t know what it is, is when you prompt your way to write software. You instruct an AI to build something and only look at the result rather than using AI as a pair-programmer to build the software, where you check the code you add. There is a major problem with this, as AI is, for a fact, terrible at writing code and introduces security problems. It also doesn’t understand the concept of optimizing code—it just generates a result. For instance, I asked Claude in a project to fetch all books and then fetch the authors and merge the books and authors into a schema structure where the book includes the author’s name. This is a simple task, but Claude preferred to fetch (database query) the Author in a loop instead of fetching all books first, then their authors, and merging them together in a loop. This created an N+1 query. If we have 10,000 products, this would mean 10,001 queries, which is not a smart way of fetching data as this could lead to high usage of the database, making it expensive because we need to fetch so much data. Vibe coding will pass. I don’t think it will stay for much longer, mostly because we require developers to actually understand what we’re working with, and vibe coding doesn’t teach you—plus vibe coding creates too much slop. Instead, it will be more important to learn how to work with context and instructions to the AI. But there is one aspect of it which I think is great and dangerous. When you’re a new developer, it’s really easy to struggle with programming because it sometimes takes time to get feedback on what you’re working with. This is why Python or JavaScript are great languages to start with (even if JavaScript is a horrible language) because it’s easy to get feedback on what you’re building. This is also why it’s great to start with frontend when you’re new, because the hardest part of building frontends is not the design, and you can get feedback in the UI on all changes you make, which makes you feel more entitled and happy to continue programming. If you don’t get the feedback, it’s easy to enter a situation where you feel that you’re not getting anywhere and you give up. But with an AI, we can get a lot of help on the way to building a bit more advanced stuff. When you’re a new developer, it’s not really the most important thing to learn everything—it’s to keep going forward and building more stuff as you will learn more after a while. The only problem with vibe coding and learning is that it takes you more time to learn new stuff when you don’t do it yourself. There are some services that claim non-technical people can build SaaS companies using their services, which of course is a lie. For the non-technical people I’ve talked to regarding this matter, a tool like Replit can be good when what they sell is not a SaaS offering—for example, they are a barber and need a website. When we try to vibe code a SaaS company using a prompt web interface, these “non-technical” founders often throw their money into a lake because what they want to build doesn’t work and has tons of bugs and wrongly built solutions, because you need to understand software in order to write software. There is a saying in programming: the last 10% of the solution is 90% of the work. The last 10% is the details, such as how should we handle this amount of messages—should they be an HTTP handler or should we create a message queue? How should my system and the other system communicate? What is a product and what is an ingredient? This is the type of stuff that non-technical people don’t understand and what the AI doesn’t understand as well—the AI just generates code. AI is fundamentally the biggest tech debt generator I’ve worked with in my life. Tech debt, for those who don’t know what it means, is the trade-off created when we develop software. Tech debt can create situations where the system is too expensive or it becomes too much of a problem to get new features out in production due to limitations in other parts of the system. Every company has some kind of tech debt—some have higher and some have lower. Limiting tech debt is easy: you only need to say no to stuff or don’t write any code at all, or you need to focus on building software in a standard and simple way. The more simple (but not stupid), the lower the amount of tech debt. For example, the problem with choosing PostgreSQL over DynamoDB is that it requires more manual work to scale a PostgreSQL database, while a DynamoDB scales automatically as it’s managed. When we get traffic spikes, DynamoDB handles this better, but at the same time, DynamoDB forces us to structure our data in a different way, and you don’t really know what you will pay for the usage. It increases memory usage as you need to fetch entire documents and not just some specific fields, which is common for NoSQL databases. The reason why AI is the biggest tech debt generator I’ve seen is that it leads us into rabbit holes that can be really hard to get out of. For instance, the AI could suggest you should store data in big JSON blobs in the database due to “flexibility” in a relational database. Another problem is that AI loves complexity, and the problem is that we accept complexity. Complexity creates issues where code becomes harder to maintain and creates bugs. AI often prefers not to reuse code and instead writes the same logic again. Another issue with AI and tech debt is that AI creates tons of security vulnerabilities as it still doesn’t understand security well, and many new developers don’t understand it either. For instance, how to prevent SQL injections. A general thing that companies who sell AI services claim is that developers become more efficient, and some developers think they are, mostly because the only thing they look at is when they write code and not the part after writing code, such as debugging. For developers who use AI, some parts of their job switch from writing code to debugging, code review, and fixing security vulnerabilities. More experienced developers also feel that they are being held back by AI-generated slop code due to how badly the AI thinks, while more inexperienced developers feel the opposite. For instance, I a while ago created bulk endpoints to our existing API and asked AI to do the job. I thought I saved some time as most of this was just boilerplate code and some copying. The task was that we should take the existing logic for creating, updating, and deleting objects. I prompted Claude to do updates and creations in parallel due to the logic, so it created channels and goroutines to process the data in parallel. When I started to test this solution, I quite quickly saw some problems. For example, the AI didn’t handle channels correctly—it set a fixed length of the channel, which would create a problem where we create a situation where the code would hang as we’re trying to add more data to the channels while we at the same time use . This was an easy fix, but I spent some time debugging this. When we added deletion of objects, we didn’t build a solution where we deleted multiple IDs at once; instead we used goroutines and channels to send multiple delete SQL queries, which created a much slower and more expensive solution. This was also fixed. Instead of logging the error and returning a human-readable error, we returned the actual error code, which could create security vulnerabilities. After my try at offloading more to the AI, I stopped and went back to writing everything myself as it generally took less time. It’s also a really weird way of measuring efficiency on how much code we accept, especially when most of the code won’t even hit production. But I guess they need to sell their software in some way. So with all these tools built for developers, I realized that the people who gain the most from all these tools are not the developers—it’s all the people around who don’t write code. It’s easier for customers to show what they really want, we can enter sales meetings with a PoC which makes the selling part easier, and product owners can generate a PoC to show the developers how they think and get quicker feedback. Another good thing is when we can summarize text from different sources into one concise piece of text, which could help us prevent the need for reading multiple Slack channels, emails, and calendar entries. This saves us really nice time as well. As a manager, AI is really nice to get a summary of how everything is going at the company and what tasks everyone is working on and the status of the tasks, instead of having refinement meetings to get status updates on tasks. There are probably multiple things we do on our daily basis that AI can help us do to prevent needless meetings and organizing, removing the need for many managers. The most gain we get so far is all the easy tasks we repeat on our daily basis. This is a new part of this article added 7 August 2025. About 40% of NVIDIA’s revenue comes from 5 companies (Microsoft, Amazon, Meta, Google, and Tesla) , which means that if one of these companies—for example, Amazon—decides to cut its AI spending in half, NVIDIA would lose 50% of its revenue from Amazon. This would adjust the stock price, which would later affect the other big tech companies. Most of these AI companies are not even profitable. NVIDIA is 7.74% of the index value of the S&P 500 according to https://slickcharts.com/sp500 . The biggest tech companies represent about 35% of the US stock market, and NVIDIA is 19% of them. The difference between NVIDIA and other companies selling hardware is that other companies have a bigger offering. If one customer of NVIDIA says they are not interested in buying more GPUs, there’s a big chance that more companies will do this (like a domino effect), and NVIDIA would have little to fall back on. The chance of this happening is quite high, as most of these big tech companies are not profitable and they are all pressured to show returns on their AI investments. Imagine the cost this would have for the individual investor and our saving accounts. One problem with NVIDIA is that they need to sell more GPUs every quarter in order to keep up with the revenue. This fragility extends beyond NVIDIA to the entire AI service ecosystem. Most companies that sell services on top of LLMs price them at costs that aren’t sustainable long-term. Cursor is a good example—their rapid growth came from a business plan that didn’t generate profit, which means they needed to heavily restrict model usage and add rate limits to avoid losing money on customer usage. The irony is that the biggest income will come from the infrastructure to run LLMs and AI, not the actual LLMs themselves. The reason I added this is that the AI market is built on hyperscalers and has created a bubble more dangerous than the dot-com era. I think we will have a bright future with AI and that AI can help us in so many ways—for example, removing stress from our day-to-day tasks that might not even be related to jobs, such as repetitive tasks we do with our family. If an AI can replace these repeated tasks, I could spend more time with my fiancé, family, friends, and dog, which is awesome, and I am looking forward to that. It is also sad how companies use AI as an excuse to fire people to “optimize” while the real problem is the company structure, the ways of working, and the type of people working at the company.

0 views
Michael Lynch 1 years ago

My First Impressions of Gleam

I’m looking for a new programming language to learn this year, and Gleam looks like the most fun. It’s an Elixir-like language that supports static typing. I read the language tour , and it made sense to me, but I need to build something before I can judge a programming language well

0 views
Michael Lynch 1 years ago

A Simple Example of Calling an Elixir Library from Gleam

I’ve been experimenting a bit with Gleam and Elixir lately as part of my search for a new programming language . One of Gleam’s flagship features is that it can call Elixir code and libraries, but I couldn’t find any examples of how to do that. I wrote a simple example of calling an Elixir library from a Gleam project, based on my beginner’s understanding of the Gleam/Elixir/Erlang ecosystem

0 views
Ludicity 1 years ago

Get Weird And Disappear

Can't leave rap alone, the game needs me 'Til we grow beards, get weird and disappear Into the mountains, nothin' but clowns down here The most treasured object in my home is the cheapest bookshelf I could find. It has been overflowing for quite some time, despite my efforts to only buy books of exceptional quality and relegating the rest to my kindle. Many of the books are unread, but that hasn't stopped me from adding to the pile 1 . My partner has gracefully accepted that I will stop at every bookstore that looks remotely interesting, and patiently reminds me that my past-self has issued strict orders to my current-self to stop acquiring tomes until I have space for a second bookshelf. Over time, I have dropped from buying a book a week to one every month, which represents a spectacular effort for me. Despite the gradual development of what I'd like to think of as iron will, I caved upon stumbling across this particular edition of A Wizard of Earthsea . This was a repudiation of all that hard-won bookshelf space saving. I had read A Wizard of Earthsea when I was thirteen, and I didn't really get it. It was a book about wizards. So what? It was slow and ponderous, and my teenage self knew that an interesting wizard should be firing lightning bolts at dragons. But the illustration on this copy of the book was so gorgeous that I bought it anyway. It was only when I slotted it into my bookshelf, and it was fully juxtaposed against the others, that I realized how hideous so many of my other books were. I treasure their contents , but consider the first two books I pulled at random from the shelf. The Robin Hobb book on the left is generally beautiful, but it is done a disservice by the George R.R Martin quote splayed across the front cover, its marketing payload expended the moment I purchased the book, but which I am nonetheless obligated to look at for the next thirty years. Almost all my books are thus marred. And this is, sadly, a relatively tasteful example compared to much of the competition. Consider instead the copy of American Gods on the right, sweet merciful Christ, it is a truly repulsive abomination 2 . Just look at it . The quote at the top is George R. R. Martin once again, which says something about what marketing departments think is impactful. In the top right, we see Ian McShane, which is incredibly jarring because I know him only for his role in John Wick. Along the bottom we have the tedious "No. 1 Bestselling Author". And most offensively, thanks to the screaming red bubble, my descendants will forever know that this precious artefact handed down from their great grandfather once had a no-doubt mediocre second season on Amazon Prime, whatever the fuck an "Amazon Prime" is. Did they turn the rainforest into a cinema when they were done destroying it? Upon closer inspection, my bookshelf is riddled with tasteless advertising smuggled into my home by boring marketing departments, and I am not open to arguments that this meaningfully boosts sales. Some people might have differing taste, but this is bland to the point of nothingness. At least erotica has the decency to slap a shirtless hunk on there, and we can envision the target demographic. Not a shred of this crassness is evident in this copy of A Wizard of Earthsea , and some part of me thought "This is Quality", and amidst the shrieking cacophony of the other titles ("New York Times Bestseller! Twilight Meets Game Of Thrones! Breathtaking!"), it is the one I walked away with. It is now one of my favourite books. A Wizard of Earthsea is a towering work of genius. Going back to it now that I'm older, I must simply accept that my teenage judgement was... well, a teenager's judgement. It is beautifully written, wise, and it does not feel the need to overstay its welcome with contemporary notions of how long a book should be. The writing speaks for itself: "You want to work spells,'"Ogion said presently, striding along. "You've drawn too much water from that well. Wait. Manhood is patience. Mastery is nine times patience." He spoke softly and his eyes were somber as he looked at Ged. "You thought, as a boy, that a mage is one who can do anything. So I thought, once. So did we all. And the truth is that as a man’s real power grows and his knowledge widens, ever the way he can follow grows narrower: until at last he chooses nothing, but does only and wholly what he must do..." While it was not a given, it is not surprising that the gentleness of the prose is matched perfectly with the elegance of the overall presentation. What was surprising is that cracking open the book reveals an introduction from Le Guin herself, specifically lamenting the immense difficulty she had over the years in simply getting artwork done which was not at terrible odds with the contents. ... until very recently, the books of Earthsea had no illustrations. This was partly my own decision. After Ruth's unique wraparound jacket for the first edition of A Wizard of Earthsea — with its splendidly stylized, copper-brown portrait face — cover art for the books mostly went out of my control. This is the beautiful cover to which Le Guin is referring. Le Guin continues: The results could be ghastly — the droopy, lily-white wizard of the first Puffin UK paperback;... ...the silly man with sparks shooting out of his fingers that replaced him... ... I was ashamed of the covers that gave the reader every wrong idea about the people and the place. I resented publishers' art departments that met any suggestion that the cover might resemble something or someone in the book by rejecting it, informing me loftily that they Knew what would Sell (a mystery that no honest cover designer would ever claim to know). Let us take a moment to appreciate how funny those covers are, and then take another moment to soak up some secondhand embarrassment — somewhere, alive or dead, there are people that told fucking Le Guin how to go about her business, to turn her characters from brown-skinned to white, who perhaps even rejected some early manuscripts. And at least some of them must be aware that Ursula K. Le Guin took a moment to personally light them the fuck up in the foreword of one of the most famous books of all time. This is not the first time that I've seen people in the publishing industry complain about the narrow-mindedness of their peers. Consider the afterword from my copy of Zen and the Art of Motorcycle Maintenance , which includes a letter from Pirsig's original editor, James Landis, who went to immense lengths to have the book published: The book is not, as I think you now realize from your correspondence with other publishers, a marketing man's dream, and if our particular variety of that species... tries to make trouble over the book, I'll just have to supply what pressure I can to overrule him: I'm determined to fight this thing through to the end you and I desire... No sense in trying to kid you by pretending that the book is 'commercial' in the way that term is understood by most people in publishing... However, I have ultimate faith in the book's being good, valuable, which gets us into standards beyond commercial and gets us also into what, to my mind, publishing is all about. That was from the 40th anniversary reprinting. We could speculate about the unknown unknowns — perhaps there are thousands of James Landis' out there, and we are witnessing the one who survived. It is surely possible to publish a phenomenal book and have it be ignored. But for my money, what happened is that Landis has real taste, and that while the modern economy sometimes appears to be filled entirely by bullshit, that only makes it easier for quality to win out if you're willing to stake something on it. But it doesn't come easily, because if you want to do anything interesting , and probably weird, you will immediately be besieged, from within and without, by tedious people and tired ideas. Getting permission from people without soul in the game is oppressive. Consider the final line from Landis above: However, I have ultimate faith in the book's being good, valuable, which gets us into standards beyond commercial and gets us also into what, to my mind, publishing is all about. If you run into Landis and his ilk, perhaps you can do something interesting. You have someone in your corner, who will perhaps take a risk on your behalf, because while they may preserve the skin they have in the game by refusing to be weird in front of their peers, they have to choose between that and their soul. But you will probably not run into Landis. You will probably meet the typical editor, who would be delighted if you offered to write Grit meets The Subtle Art of Not Giving A F*ck 3 . You will meet the real estate agent who will never be swayed by considerations other than the size of your bank account, or the government bureaucrat who will decide that you cannot immigrate because you are two days below the age of 23, or some other tasteless and impotent cog. Even consider startups, a community replete with people who jerk themselves off to the label of entrepreneur so hard that they're at risk of permanent groinal damage. And because we do work with some startups, I will make apologies to the twelve sane startup founders out there. 4 It's distressing how many young people are under the impression that startups are where you go to innovate and assert yourself. You're young, full of optimism, and have minimal obligations. Your only real weakness is your lack of experience, and it is with that inexperience that you... you sign a deal with people who are possibly dozens of years your senior in cunning, law, and finance? To play games on their terms, where you're a total loser if the business earns just enough money to live comfortably on your own terms? Where you have to justify your decisions, where they can pressure you to sell more of your equity, and ultimately appoint a new CEO if you make a misstep? But I digress. To continue the theme of publishing, last year I was approached by an editor at a mid-tier publisher. I made sure to read some of the editor's short stories before our first meeting, and it turned out they were themselves a talented writer with a real passion for the craft. In fact, while looking up the link to the article above, I discovered they published a book as recently as 2024. And they wanted to help me get published! My name, on a book! I knew that it was unlikely to do any significant sales, as blog-writing and book-writing are two very different skills, but still! A book! Things progress well over the next few days, and it turns out the editor shared many of my frustrations with how companies run. Just as it looked like I was going to sign on, the sales team comes with their Opinions, and I must sadly say that they were not sophisticated Opinions. To begin with, they wanted to stipulate that swearing was absolutely not allowed before knowing anything else about me or what we wanted to write. I wasn't actually intending to swear much, if at all, but I also saw no need to limit myself up front. Why agree to play left-handed before we've even tried out the game? More conditions are added. They will not even guarantee that they'll try to get it on bookshelves. I reach out to some other people who have published with them, they reveal that they were forced to write their entire book in Microsoft Word templates. Communications were confused and unclear. Even the payments were described as "opaque". Basically everything you would expect out of the typical tech project. A picture begins to emerge, of a serious editor trying to do their best, surrounded by colleagues who are about as unserious as anything I've described in the tech world. Nothing surprising, but still, why not self-publish? An editor and an artist will only run a few thousand dollars, a tiny investment compared to the time it takes to write a book, and whatever I lack in experience can be compensated for with giving a shit. To quote one of my co-founders, Jordan Andersen, it's only arrogant if you're wrong 5 . In the meantime though, I frequently find myself reflecting on how bizarre the knee-jerk "no swearing" rule was. Sure, there are always going to be some concessions to be made to marketing. I don't just label all my posts "Unnamed Post #7", because I would like to be read at least a little bit. But swearing? Who gives a shit? Is that really going to be the difference between a good book and a bad book? Why on earth would you hear that you've got someone interested in writing a book whose writing is doing millions of hits that very week and then immediately tell them that you know what will sell better than they will? In fact, the only other book they've got that's well-known enough for me to recognize it has swearing in it , so I know it isn't even a principled brand stance. A midwit just went "I am slightly self-conscious about how I appear to my boss" and that was enough to tank the whole thing. I will never stop citing Sturgeon's Law . Ninety percent of everything is crud. That is to say, ninety percent of everything is crud to a practitioner who has taken the time to develop judgement. That is to say there is some distribution of judgement and bravery, and a whole lot of people are clumped in one not-very-good batch. These days, when people become concerned about the seeming arrogance in that statement, I point them at real estate agents and say: "That's how bad many professionals are, but you can only tell when the issue is as blatant as a broken window in your living room." I still have not met a professional whose competence I've been able to verify that will not endorse something close to Sturgeon's Law. The best I've been able to manage is that, if you ignore all secondary attributes required for a job such as empathy, communication skills, maturity, and restrict yourself purely to the mechanical task at hand, some medical subspecialties are consistently okay... but with all those caveats, you'd still never want them to be your physician. In all fields, very weak practitioners seem to cling to a series of practices or principles without a nuanced understanding of why they're doing those things. Consider, for example, a junior engineer. The trajectory of the typical junior engineer is that they will hear a principle like Don't Repeat Yourself, and immediately begin writing functions to generalize two pieces of code that even look similar. This is normal and even admirable — it is a single-objective optimization problem, and they're optimizing! Junior engineers are supposed to be weak practitioners, but this person is trying . They will then become slightly more sophisticated, and learn about premature abstraction, and suddenly we have two principles that are in conflict. Collapsing duplicated code means you are Not Repeating Yourself, but you are probably introducing an abstraction, and now you have a tradeoff between two principles 6 . And if you keep going, you will pick up dozens to hundreds of these, implicit and explicit, and must become comfortable with the fact that you're going to have to balance them all simultaneously via the application of judgement, and you will be wrong sometimes. With all that nuance comes the boon and burden of being able to disregard principles. For example, we use Test Driven Development , but I was recently working on something that required spinning up a headless Chrome browser to convert a HTML document to a PDF. Unfortunately, this is very slow in a test suite and annoying in some CI/CD products for various reasons . Because I have an idea about why I test, and what the value of it is, and how likely this is to break, I made the decision to just not test that bit of the application when running tests in GitHub Actions. Will I regret that? Maybe, probably, but the point is that my rules are there for reasons and sometimes those reasons don't feel like they apply. 7 Truly weak practitioners will come to you with totally degenerate rules or principles. This is the "no swearing" publisher, but it is also the "no dogs" landlord, the "no risk" security guy, the "must have ten years of Git experience" recruiter and I've no doubt that everyone reading this can think of at least one system where they've thought "I can't believe I'm at the mercy of a person that isn't even thinking ". Frequently their rules are not even rules, they're just phrases that competent people used to say, and eventually they became well-known enough to become fake-able signal. Data-driven. Garbage in, garbage out. Agile. All of these may have previously signalled novel thought, and now anyone that's really savvy does their best to not use the phrases because they know how tacky it makes them look, even if they actually use them in meaningful ways . This can even happen when you've bypassed the large, visible, external gatekeepers, such as recruiters. All it takes is one person within an organization that loves talking and again lacks that baseline competency. One of my co-founders, Ash Lally, the infamous Business Jaguar 8 , was on a contract outside the context of our consultancy recently. They are tasked with pulling data from one system into another system, which is about the most boring and typical thing that a data engineer can do. The employer is not technologically advanced and this task is not their core competency, so as is the case for almost all work in this category, the solution is to schedule some code to run once every ten minutes to pull it in. They are working with someone that, to steal the words of a reader, has had a very impressive career in numeric terms, i.e, they're terrible at their job, but boy oh boy have they been doing it for a long time. Ash's counterpart considers pulling the data in every ten minutes, then comes back with: "Event-driven is good for internal system integration." Event-driven means that instead of writing something very simple to grab all new data every ten minutes, you instead write a very bespoke system that detects new events in real-time and copies it over to the target system. This is way more complicated and totally unsuitable for a company that hasn't figured out version control yet. And also totally unproductive, because the data can't be used without another piece of data which has a hard limit of being calculated... once every ten minutes. So the company still has a ten minute delay before anything can be used, plus all the insane overhead. But still, we should hear them out, why is event-driven good for system integration? I can think of some reasons, but I'll never know if they're the reasons this person had in mind, because that information is not forthcoming! They heard those words somewhere from someone authoritative-sounding and they now feel comfortable regurgitating them at other people. But we're going event-driven now! Bonus points, this infrastructure supports emergency services, so you can commit stochastic murder this way! It's like serial killing but you've tricked a whole department into being your co-conspirators! Even when you've passed all the normal gatekeepers, you will sometimes need permission to do the engineering equivalent of tying your shoelaces correctly. 9 Above, I talk about people who have even gotten as far as misapplying principles. As frustrating as that is, that isn't even the worst you can do . Some people never reflect on their craft at all, or do not view it as a craft. The person that says "event-driven is good for internal systems integration" has at least taken a moment to read something, even if it's a terrible LinkedIn post or a Medium blog. Things can get so, so much worse. Remember all your peers at university that would say "I dream of working in HR"? No, probably not, because that sentence has possibly never been uttered in all of human existence. The field is laden to the point of breaking with people that drifted into it because it was the first job they could get that paid white-collar wages for a qualification no more sophisticated than knowing where some of the buttons are in Workday. Some of them, by dint of personality and ethics, are good at their jobs, but they are few and far between, and perhaps most importantly are not systematically produced. Is it actually a surprise, under those conditions, that HR seems to be useless whenever you interact with them? Could we expect any other outcome? It's a testament to the power that so many of them have relinquished to the economy, by dint of being unemployable on the grounds of technical skill or competence , that they bother to get out of bed at all. They don't want to be there any more than you want them there. Jordan, the same co-founder responsible for "it's only arrogant if you're wrong", wrote a single blog post on our company website. During a meeting where Jordan asked for some feedback, we spent some time caught up on this little snippet. I just want to pause and note how fucked this was. While I was an accredited allied health professional at the time, it's fucking ridiculous that a bunch of recreationists were put in charge of dictating how anyone should respond to a medical emergency... And here's what the team was split on — do we swear on the company blog? You can probably figure out where I set on the issue. Go for it! What are you, some sort of coward? But the team wasn't sure, and the truth is that I wasn't entirely sure either. Maybe I can get away with that on my personal blog, but is that too crass? I mean, some people feel it's already too crass here. Will people be scared to take us to their managers? And most importantly, other consultancies aren't doing this , should we be more like them? Eventually, Ricky Ling chimes in (I'm sorry there are so many of us), and his take for Jordan is the Quality-focused take. "If you think it makes the writing better, do it". We still aren't sure, but we know that we didn't quit our jobs and give up all that sweet, sweet salary to be cowards, so we do it. That single post has generated us a huge amount of outreach from cool people despite not going viral or being widely circulated as far as I know, and we almost started prematurely neutering it because a group of people who already deviate wildly were still worried about doing something even a little bit different. Being seen to deviate is extremely hard, in large part because to deviate is to be seen , and the gaze of others is deeply uncomfortable. There's an exercise that actors do, where they sit in two chairs opposite each other, make eye contact, and then one of them just starts saying things that they're feeling. "I am aware that my hands are warm." "I am aware that I am uncomfortable with you staring at me." "I am aware that I want to burst out laughing." This is very, very uncomfortable to do. I've done it three times, for about ten minutes each time, and I cannot emphasize how much we all hated it. From this we can infer that actors are total fucking weirdos, but also that acting as a field has identified that there is something deep to unpack in reflecting on what it is like to be perceived without the luxury of flinching or breaking eye contact. This aversion to deviation and subsequent perception is enough to make some people totally impotent. In the earlier days of the consultancy, I envisioned that our sales pipeline would be something like this: This sometimes does happen, but it has never happened at a company without a really spectacular culture. What happens at the average company or government agency is something to the effect of: "I am working on a project, and we're being scammed by some other consultants. The whole thing is months late, and it isn't going to finish any time soon. Is there any chance you can help out?" I reply, "Yeah, of course, we'd love to. But we'd need an introduction to someone inside the company who might be willing to admit that something is wrong." And this is where, every single time, the person that initially reached out has decided that they don't want to take the risk of being visible. I understand — a lowly engineer suggesting an external consultancy with hot takes is really, really unusual, especially so if they present hard-to-ignore and inconvenient evidence that a project is failing. It has possibly never happened to the average CTO across their entire career. The engineer would be as visible as it is possible to be, perhaps moreso than if they hauled off and punched someone in the face, and during the next round of layoffs they will either be promoted for being weird or fired for being weird. In either case, redundancy is no longer dependent on who is at the top of the list when the CFO sorts the salary column on a spreadsheet by descending. In the past year, I've seen people have huge impacts at companies by exerting some bravery. This sometimes means getting a bully fired, sometimes it means getting the first tech project to work smoothly in a decade. In some of these cases, the person involved was made redundant shortly afterwards for interfering with people's selfish political aims, and other times they were promoted for actually giving a damn (and yes, sometimes for unknowingly helping someone else's selfish political aims). But all of them went in willing to put something on the line. A huge number of employees will venture nothing for their beliefs, and while that is fine or whatever, bla bla bla, they have kids, they're also going to be largely ineffective because they won't change anything big, because big change is novel and weird, and novel and weird might get you noticed, and getting noticed means something is going to happen to you. Our company is really into weird stuff. Our favourite TTRPG is Burning Wheel, an extremely niche product, hundreds of pages long, whose creator refused to release a PDF for years because he felt that it watered down the experience. Our favourite traditional board game is Twilight Imperium , a sci-fi masterpiece that takes thirteen hours to play. For internal projects, we program Elixir, a language with near-zero name recognition amongst engineering genpop that compiles to Erlang, a language developed in the 80s by Ericsson of all places. We're an Extreme Programming shop. Most of my spend this year has been learning material and one-on-one coaching for engineers in the company. We started with six co-founders, something that caused several banks a great deal of consternation when we tried to open our account, and I am now realizing makes it very hard to write blog posts without introducing what must feel to a reader like an endless stream of new names. It gets stranger. We pair program on all work 10 . We have no network drive, just Git repositories where all documentation is written in Markdown, and the proposals that must go to clients are converted to HTML so we can style them with CSS. AI? Not only do we not turn on AI-assistance during programming, we'll crucify each other for copying a StackOverflow example if we can't explain the behaviour with reference to code or core documentation. We contracted Mira Welner this year, internet famous for frontpaging Hackernews a few times as a fresh grad, who described our work as "the opposite of vibe programming", which will make different people have wildly different opinions about whether we're competent. We invite our clients to sip from the Chalice of Madness, wherein we pray that they will find that the Kool-aid is delicious and refreshing, amen. If there is a conventional script for something, we have probably thrown it out the window or seriously considered it. I'm obviously convinced that these practices are good or I wouldn't be doing them, but they have a secondary characteristic that may be more valuable than their originally envisioned purpose. They filter out shoddy, default thinking, and otherwise shake people into vivid awareness. Everyone is constantly immersed in weirdness, and that makes the next piece of weirdness less scary. I mean, we may still fail as a business for many reasons, but it won't be because we weren't thinking , it'll be because we had stupid thoughts , which at least has the virtue of meaning we actually tried. If you had a company policy of "we only hire people that can pass an interview about Burning Wheel rules", I suspect you would hire a stronger team than what the average company manages. At least they'll have something in common and they've demonstrated refined taste in one area. I don't even care if they can program, they'll figure it out. Iroh: What do you plan to do now that you've found the Avatar's bison? Keep it locked in our new apartment? Should I go put on a pot of tea for him? Zuko: First, I have to get it out of here. Iroh: And then what? You never think these things through! Some years ago, I knew a man that was laid up in hospital for a chronic illness. He was not having a good time, but it must be admitted that some of this bad time was the product of a long-running pattern of poor life decisions. C’est la vie. In any case, one day he is having a particularly bad day, and he messages me from the hospital. He's going to leave and pick up a pack of cigarettes. This perplexes me to no end. "But you don't smoke and are almost out of money, right?" And yes, it turns out I had not misremembered. He wanted to start smoking at that moment in time. Now, this is actually a fairly extraordinary amount of effort. Imagine, you're comfortable in a hospital bed, in a terrible mood, and all the normal pressures that lead to smoking are absent, such as peers to pressure you. But ex nihilo , you now grudgingly swing to your feet, fish out your last bit of money (cigarettes are supremely expensive in Australia, approximately a whopping USD $20 per pack), and march off to try and form a financially crippling addiction. Surely you have enough problems, and don't need to go through the trouble of leveraging your executive function to make more of them? After mulling this over for a while, I come to the realization that this fellow felt the need to inhabit the role of the hard-done-by man on the street. And what does the hard-done-by man do? Well, on the television or a play, he would light up a cigarette of course! Probably take a big, long drag off it, then close his eyes and exhale a stream of smoke into the sky as he sighs. It's real cool , yeah, he's a real cool dude. It's all symbolic, y'see? He's destroying his body as if to say "the world is wearing me down so quickly, the cigarettes won't have time to get me, heh". This is practically understandable, in a strange, catharsis-y way. We all know social scripts exist. There are rough guidelines on how much to tip a waiter in the U.S. In Australia, you should stand on the left side of an escalator if you're not intending to walk up it. What I hadn't really thought about was that some of them are so strong and latent that they can spontaneously cause a person to self-inflict a nicotine addiction. I hadn't even really noticed that I had an archetype of the put-upon smoker in my mind, but there it was, and you likely knew exactly what I was talking about. These scripts are adaptive in some situations — for example, well-adjusted men should be aware that the comradely arm on the shoulder is a great deal more appropriate with a male colleague than a female one, and an even better-adjusted person will just not touch their colleagues. But other scripts, especially the ones that are too small to think about or too large to see, drive insane life decisions. When I was studying psychology, almost everyone, including me , picked the course because we weren't sure what we wanted to do with our lives, but the script says finish high school and then take on ten of thousands of dollars in debt. That script is so large that it basically encompasses your whole upbringing. When you try to look at it, you see grey, and think "there is no elephant here, just a cement wall". Or when someone looks for a job they just start sending CVs out, even though that basically doesn't work at all. The assumption that this is how you get a job is so quiet that people don't even realize they're making a decision. They think the decision is whether you should have a one-page CV or a three-page CV, not whether they should have a CV at all. And again, when you interface with a sufficiently large system, they will have their own scripts, and you will be expected to abide by them. Your counterparty will usually unthinkingly apply them, and if you find someone that is willing to actually look at the situation at hand, treasure them. The other issue with going off-script, or otherwise deviating, is that you have to accept that you're going to look extremely silly if things don't work out. Someone, usually someone that isn't taking any risks themselves, will be a dick about it. Let's say that my company does well enough for me to draw a full tech-tier salary in 2026. I'll be praised for whatever I did differently. Six co-founders! My God, he's a genius! You can keep most of them working contract roles, which means your revenue requirements are low, but it only takes them about an hour a week to mobilize their networks for sales! His mind is a thousandfold blade, so keen is his thinking! Sure, now let's say that it collapses in June because a co-founder wants to pivot to a SaaS offering and I don't. Six co-founders! My God, what a fool! Everyone knows there are reasons that VCs prefer two to three founders and no more! A huge risk for a startup are the founders having a falling-out, and the risk increases exponentially with each node in the fully connected network! His mind is being passed around by stoned college grads, so blunt is his thinking! I can skip the embarrassment by doing what everyone else does. If I failed the normal and boring way, by running a non-Extreme Programming consultancy, with one co-founder, that writes Python because everyone else does, selling GenAI bullshit 11 , I would be beyond critique. I'd just say "Gosh golly, sales are hard", and everyone would nod sympathetically. Guess what though, whether or not people point and laugh, they certainly aren't going to pay your rent for you. Who gives a fuck about embarrassment? LaRusso: Do you think I stand a chance at the tournament? Miyagi: Not matter what Miyagi think. Miyagi not fighting. What most of this comes down to, fundamentally, is whether you trust your own judgement, what you're willing to stake on it, and whether that conviction is strong enough to throw away the guarantees that you'd get from conforming. Because you could take the guaranteed money for writing the book, and if you don't really believe that your judgement is superior, perhaps the money is worth it. Readers call me with some frequency to ask me if I think their startup idea is good. Could it sell? Would it work? Should I do it? How the hell would I know, I'm just some guy with a blog and a business that may explode in the next year. But if I were the owner of a $100M company, would I really know any better? If anyone knew, then when you asked them they would steal your idea. But no one knows, so the idea is safe and sound, and deep down the asker must be aware of this or they wouldn't be asking . Or take last week, when a student visited me from a local university to ask if they should drop out of university to run a startup. They really didn't like the idea of a corporate job, found university extremely tedious and their peers uninspired, so on, so forth. They were clearly very intelligent, but also clearly very young. What do you say there? Because the knee-jerk impulse, for fear of being irresponsible, is to say the boring thing without thinking about it. Finish your degree first. Get a few years of corporate experience. Or even, I know you're depressed, but don't quit your job without something else lined up. None of that is necessarily bad advice, but we should view ourselves with suspicion when we think about saying them, because they also absolve ourselves of guilt if things go awry. Who could blame us for giving the conservative line, even if you can easily play the tape forward and see it results in damnation? The advice that people are too scared to give, but which they sometimes really believe, with the usual caveats for a mental health event that has crippled the recipient's ability to see clearly: "What does it matter what I think? Do what you think is best for yourself, and yes, you might lose your life savings. What's the alternative, doing what other people say is best for the rest of your life?" Thank you for reading all the way through. For this kindness, I leave you with a parting gift. Penguin, what the fuck is going on over there? PS: Oh yeah, I put this in to justify writing, hire my nerds or whatever. PPS: Reader and now close friend Phil Giammattei could use some help with a horrible brush with cancer in the family. You can support him here . PPPS: You all crushed Phil's goal, thank you so much for your generosity. Things are obviously Extremely Bad in many parts of the world right now, and it's wonderfully refreshing to see people pull together, even if it is for a degenerate like Phil. I'd also like to take a moment to curse the person that dethroned me from my spot of Top Donation, and to double-curse them for doing it anonymously, embarrassing me further. Nassim Taleb calls this the "antilibrary" , the collection of books that one owns that one has not yet read. Taleb argues that an antilibrary is there to educate and humble, while a library of already-read books has much of its usefulness shifted to signalling erudition. If you were to point out that the optimal antilibrary strategy is to constantly buy books and never read any, Taleb (and I) would call you a nerd.  ↩ It was also a gift. Sorry Ricky, the Red Rising copy you got me alongside this was much better!  ↩ It is endlessly entertaining to me that the cover of The Subtle Art of Not Giving A F*ck , a book so desperately trying to be edgy, censors the the word fuck, achieving a flaccidity that goes beyond simply deciding not to swear at all.  ↩ 100% of startup founders nodding at this.  ↩ It is a custom that whenever I or Jordan call our shot and mess up, we call the other person and loudly declare "I was arrogant and wrong", then accept ruthless mockery.  ↩ It is remarkable how many companies seem to forget that the ultimate value is arguably Working Software, and while all the subsidiary principles may fall out of Working Software when you shake it very hard, you can't do away with it. Why calculate velocity if you know you aren't shipping, scrumlords?  ↩ The scary thing about messing with long-running, almost-mystical practices like testing is that their second-order effects run so deep that I get many benefits that I haven't even noticed, so making these decisions also means I may have thrown away some things I don't understand. It's like becoming convinced that I can leave a candle out of the summoning ritual.  ↩ I am slowly realizing that my company is comprised entirely of fucking lunatics, and I am the cracked porcelain mask of sanity that attends sales calls.  ↩ Our scathing disdain for this has not prevented us from chanting "event-driven is good for internal system integration" whenever we need to annoy Ash, which is probably not bullying.  ↩ We incidentally allow anyone to pair program with us over a four hour session, where you can choose to be amazed at our great team energy, or if you'd prefer laugh at how bad we are at programming, so long as you'd consider writing a nice review if you have fun and see strong evidence of competence. Just email me!  ↩ I am bored just describing this.  ↩

0 views
Ludicity 1 years ago

Ludic's Guide To Getting Software Engineering Jobs

The steps in this guide have generated A$1,179,000 in salary (updated 13th April, 2025), measured as the sum of the highest annual salaries friends and readers have reached after following along, where they were willing to attribute their success actions in here. If it works for you, email me so I can bump the number up. I currently run my business out of my own pocket. If I don't make sales, I lose savings, and it's as simple as that. I am all-in on creating work I love by force of arms, and I'd sooner leave the industry than be disrespected at a normal workplace again. The impetus to run that risk comes from two places. The first is that my tolerance for middle managers jerking themselves off at my expense is totally eroded, and I realized that I either had to do something about it or stop complaining. I'll happily go to an office if I think it will produce something I care about, but I will not do it because someone wants to impress a withered husk who thinks his sports car makes him attractive to young women. The second , and what this post is about, is that I am really good at getting jobs, and have friends with a very deep understanding of how the job market works. In Australia, when you apply for a job without permanent residency, you are filtered out of all applications immediately. It is the first question on all online application forms, and the reason is that companies do not want to deal with visa renewals and they have far too many candidates. This leads to a situation where any characteristic that is remotely inconvenient but not noticeably correlated with suitability for the business is grounds for rejection. It is not uncommon for immigrants to take months, sometimes over a year, to find their first job actually writing code. Despite being a non-white with no professional network in the country and an undesirable visa, I had my first paid programming engagement lined up before finalizing the move off my student visa. I had a full-time job on A$117K lined up for the same day my full work visa kicked in. I continued to dig up work whenever a contract was expiring, even landing a gig mid-COVID, and while most of these jobs left much to be desired , I believe this has more to do with the state of the industry in Australia than anything that I did. And I have only gotten better at this over the past two years, because while I despaired about the state of software in general, I never stopped thinking and experimenting about how to regain some control over how I'm treated. Almost everyone I spend time with now has walked away from a job without flinching. I've done it . I once caught up with a friend, and he said "Work is stupid, I'm going to Valencia for a year." I said, "W-what? Valencia? When? For a year ?" "Yeah, a year. I'm going in two weeks." And then that glorious son of a bitch did it . Came home. Had a job waiting for him. Quit that job, got another job at more money. Quit that job, got one interstate because he felt like it at similar pay for half the work. All in a "weak" market. I get a lot of emails from people who despair about the state of the industry or who otherwise can't find jobs, and I always end up giving the same advice. I don't have the time to keep doing that. So in this post, I'm going to attempt to convince thousands of people that you should have much higher standards for what you tolerate, that you can build up the reserves to do your version of going to Valencia (this could just be staying home and playing with your kids for six months), and that it is immensely risky not to have this ability in your back pocket. Along the way, we will answer questions like "How long should a CV be?", "What should go on it?", and "When will this suffering end?" From Scott Smitelli's phenomenal The Ideal Candidate Will Be Punched In The Stomach : What was the plan here? Why did you leave a perfectly sustainable—if difficult and slightly unrewarding—job to do this crap? What even is this crap? You are, and let’s not mince words here, you are a thief. That’s the only way to make sense of this situation: You are stealing money from somebody, somehow. This is money you have not earned. There is no legitimate way that you should be receiving any form of compensation for simply absorbing abuse. These people, maybe the whole company but certainly the people in your immediate management chain, are irredeemably damaged to be using human beings that way. They will take, and take, and smile at you like they’re doing you some kind of favor, and maybe throw you a little perk from time to time to distract you from thinking about it too hard. But you? You can’t stop thinking. You can’t stop thinking. You can’t stop thinking. If you're in this photo and don't like it, this blog post is for you. We have one end-goal. A career where you're paid well, are treated with real respect, and we will not settle for less. And I mean real respect, as in "we will not proceed on this major project without your professional blessing, and you can fire abusive clients", not "you can work from home two days a week if the boss is feeling generous". I had a brief email exchange with Erez Zukerman, the CEO of ZSA last year, and asked how their customer support is so good — it's the best customer support I've ever experienced and there's no close second. He replied: For support, the basic understanding is that support is the heart of the business. It is not an afterthought. Support is a senior position at ZSA, with judgment, power of review over features and website updates before anything is released (nothing major goes out without a green line from every member of support), the power to fire customers (!), real flexibility when it comes to the schedule, etc. There are also lots of ways to expand, like writing (Robin has been writing incredible blog posts and creating printables), recording (Tisha recorded Tisha Talks Switches which thousands of people enjoyed), and more. Anything short of that isn't real respect. Not a special parking spot. Not the ability to pick up your kids sometimes . Not a patronizing award on Teams. Most places fall short of this, and because we have all agreed to demand better for ourselves, we are going to consider all of these places as mildly abusive. A lot of office jobs seem like a slow death of the soul — better than the swift death of the body that careers like construction work offer, but that isn't a reason to stop striving. Shoddy work. Hour long stand-ups. The deadlines are somehow always urgent and must be delivered immediately, but are also always late and everyone knows they'll be late from day one. This is delightful at times — office scenes in improvised theater get funnier the straighter you play them — but many people eventually feel that something vital is missing from their work lives. I really enjoy David Whyte's The Three Marriages as an antidote to the tedious objection of "Work to live, don't live to work". It's a part of life, and while it isn't all of life, being bored and treated like a disposable cog for eight hours a day shouldn't be any part of your life. If you're happy to coast, adieu, catch you later. This a no judgement zone for the next five minutes. Here is a quick reality check. I have, by virtue of hundreds of people reaching out to me over this blog, seen the "I want to leave my bad job" story play out far more times than a typical person does in a lifetime. It always plays out in one of two ways. The first is that the person immediately and aggressively looks for new jobs. This usually goes well. If it does not go well, they can always find a new job again. When the job is pursued through "normal" mechanisms, such as cold Seek applications, these jobs almost never meet the standard I set above: great pay, great team, great interview process, and whatever office arrangement you prefer. But they've always been doing better along at least some of the four measures. The other story is much more typical, and it goes something like this: I'd love to leave, but there's something keeping me. One more year and I'll get a new title, and then I'll be so well-placed for a new job. I've heard the market is bad, so I should wait until it picks up again. I'll get a raise soon, then I'll negotiate for a new job. I'm scared of keeping up with mortgage repayments. I just need a year to finish up this project, it'll look great on my CV. My network is terrible , so I don't have the same options open to me. I think I can make a difference if I'm given a few more months. In two years, this second approach has never gone well. Never, ever, ever. Consider this real exchange, copied verbatim and redacted. May, 2024: Me: I'm a little bit concerned that the pathway above leads to delaying indefinitely (there's always going to be a risk of moving then getting laid off - so what risk level do you actually tolerate, and how is that balanced against [COMPANY] being run so badly that you can get laid off there too?) but you know your situation better than me. Reader: Well, the company was bought out and seems to be stabilizing. July, 2024: Reader: Got some fantastic news! Gonna get a raise at [COMPANY], 20%! It came as a surprise, apparently they think I earn too little so they're giving me a raise because of that. November, 2024: Reader: Wanted to let you know I got news, I'm gonna be fired next month. This happens so often that it's actually boring for me . I've had exchanges like the above often enough that I know the person is finished months before they do. Play stupid games, win stupid prizes. They will either be let go, burn out and quit, or burn out and stay there as their health deteriorates. No one, at any level short of executive, has managed to have the impact that would be required for them to feel it was worth the cost. The thing that is missing, to my eyes, is some sense of confidence and self-respect. I hear lots of supposed barriers to getting a better job, but almost none of them are convincing, especially from people in the first world, so what I'm actually hearing about are psychological barriers. It takes a certain degree of confidence to know that you have worth, because a great deal of our society, whether by coincidence or design, causes people to feel like they're not desirable. If you don't have confidence, you feel trapped at the current situation, because what if you can't find something else ? What if you're not good enough? This is a real risk, but guess what, life's risky! Two months ago, one of my high school classmates, one of the fittest people I know, died of an aneurysm at age 29. Think about it this way: enough people read this blog that if you are reading this sentence , you have just drawn a ticket in the "heart attack kills me by December" lottery. This isn't hypothetical, this will happen — someone reading this will die having spent a few hundred hours on spreadsheets this year, and perhaps even have time to think "I wish I had listened to Ludic, he is so smart and wise." 1 And on self-respect, I will concede that you're getting the vestiges of my time spent in psychology, but why would anyone respect you if you let someone do Scrum at you for hours ? No one respected me when I let people do Scrum at me, and that was my fault . "It what der street trolls make when dey is short o' cash an' ... what is it dey's short of, Brick?" The moving spoon paused. "Dey is short o' self-respec', Sergeant," he said, as one might who'd had the lesson shouted into his ear for twenty minutes. So where do we start off? Well, the first thing to do is bury the idea that you need this particular job, or that you are otherwise unworthy. And we're going to do that by getting really good at getting mediocre jobs, and we're almost always going to want to be doing day-rate contracts. We are going to do a lot of things that I do not endorse when going for a good job, like sending your CV anywhere, talking to recruiters, etc. Regular full-time jobs obtained through mass-market channels have dysfunctional social dynamics that are too complex to get into here. Patrick McKenzie writes : Many people think job searches go something like this: See ad for job on Monster.com Send in a resume. Get an interview. Get asked for salary requirements. Get offered your salary requirement plus 5%. Try to negotiate that offer, if you can bring yourself to. This is an effective strategy for job searching if you enjoy alternating bouts of being unemployed, being poorly compensated, and then treated like a disposable peon. Working jobs like the above comes at a real cost, even if you can get them at-will. I had an episode of intense burnout which resulted in a year of recovery , and I had to think very hard about how to not feel trapped in a bad situation again, even if the business fails. I do not want to attend hour-long stand-ups anymore. This section is about how to get the above jobs as effectively and painlessly as possible, but they will still not be great , and if you do them forever then I will be very disappointed in you. In any case, if one must engage with the market in this way to build confidence and a reputation, then day-rate contracts are amazing. I am heavily in favour of contracting. The day rate is much higher. You are forced to continue searching every few months, which means you are also forced to always be aware that you have options, and we will discuss how to minimize the pain of this. You will meet far more people because you will be at a new workplace every few months. Here in Australia, a weak contracting job will pay A$1K per day, which is approximately double what a permanent employee earns. I.e, for every six months of contracting, you can afford six months of unemployment, and you're still as well off as you were if you had been permanently employed over that period. Contracts are terminated more frequently, but you're also in a much better position because you've saved way more money per day worked, you've met tons more people, and your CV is always up-to-date. And you also knew it was going to expire in six months, so having it end three months early isn't a horrible shock to your planning. You are also excluded from the most mentally draining practices in a corporate environment, and afforded a higher status than regular employees. You will usually not be asked to attend pointless meetings, and instead be left free to execute on technical work, particularly if you indicate that you can manage scope independently. If someone does ask you to attend a pointless meeting, you can recite the Litany of a Thousands Dollars a Day in your head over and over as the project manager attempts to flay your mind. You know that delightful period after you've submitted a resignation and you're about to get out? That's the whole contract . A six month contract feels like handing in a resignation with six months of notice. When the CEO says "Can we put GenAI and blockchain in the product?", you can close your eyes, my God you are so happy, and whisper "Inshallah, I will not be on this train when it derails". None of these jobs will be great. This is not a good way to get jobs in the long-term. This is a boring, soulless way for someone that does not have any appreciable career capital or networking ability to generate adequate jobs on high pay. We only bother with this so that you know that if your business explodes, or the cool non-profit you find fires you, or if a new boss comes in and abuses you, you'll know deep down that you can walk right out that door and tell them to get fucked. I should note that the advice in this section was heavily contributed to by a friend who wishes to remain anonymous, but let us all send them silent thanks. Anyway, we take Sturgeon's Law very seriously on this blog. Ninety percent of everything is crap. It is with this understanding that we must proceed. There is a pathway to navigate that relies entirely on the broad understanding that: Let us begin with recruiters. Recruiters are an unfortunate reality of the industry. I still haven't worked out why they exist when a company can just post a job ad themselves, and their talent team has to filter out the candidates themselves anyway, but whatever. They're here, and I've learned enough about the world to accept that it's 50/50 on whether their existence is economically rational. In 2019, about a month into my first full-time programming job, I received a call from a recruiter. They were looking for someone with Airflow experience to work a contract with Coles, a massive Australian grocery chain. I had no idea what to really say to this, being inexperienced and hugely underconfident, so I just listened to his questions and answered them. Most of my answers were a sad: "Ah, no, sorry, I know what AWS is but I've never used it before at a real business. I know what Airflow is , but I've never..." Until finally we come across the fateful question: "And do you know Linux?" Why, yes, I do know Linux. At that stage of my career, it never even occurred to me to ponder what knowing Linux is. Do I even know my keyboard if I can't construct one from scratch? What does he mean know ? How deranged would I have to be to say I know Python, without qualification, without being a core contributor? But none of that occurred to me, I just said yes. He is delighted, we get to chatting, and we quickly realize that we're both working our first jobs! He is a year younger, also nervous about his job, and is so happy to be talking to someone that just sounds like a normal person. He is soon comfortable enough to ask me a very vulnerable question: "So what is Linux?" I answer, and I've been doing nothing but teach psychologists-in-training statistics for a year, so the explanation is good. Each good explanation leads to another, until I'm fielding questions like: "What is Airflow? What is AWS?" We hang up, on good terms, and I stare at the wall for a long moment. There are people out there just like, calling around and functionally asking "Have you used FeebleGlorp for eight years?" with no internal model of what FeebleGlorp might be? That can't be right. Everyone at school told me that affairs would be very serious in the real world. Affairs are not very serious in the real world. Affairs have never been less serious. I told myself for a while that this must have been because he was so young, but no, they're actually almost all like this. I have only ever met two recruiters with intact brains 2 . To quote a reader with extensive HR experience who attempted to explain this dysfunction to me: While there are professionals that specialize in tech and with time develop enough depth to understand the discipline and move the needle in the right direction, for most recruiters it is not an economic advantage to do so; as the winds of the market are ever changing, recruiters are always the first ones to go into the chopping block when there are layoffs. Better to be a generalist recruiter and keep your job options open. I.e, the recruiters you are talking to probably go out of their way to avoid learning anything, because they may be recruiting in a different industry next month. This means a few things. I normally do not send CVs anywhere and decry them, but I've reversed my stance. They're a terrible way to get good jobs, but a heavily optimized CV will demolish most other candidates, who are about as unserious as the recruiters. So how do we optimize? Well, we're trying to get past recruiters. On your CV, quality indicators only matter if the recruiter can understand them, and as per the above they do not understand anything . At 12:34 PM today, while writing this blog post, I got a call from a recruiter and I asked them a question for blog material. "Hey, question about my CV, would it be better if I mentioned that I'm well-regarded by Linus Torvalds?" (This is not true, we don't know each other.) And they said, "Uh, I'd leave that out, these are very busy people and need technical credentials." Recruiters are only looking for one thing. They are looking for the number of years of experience that you say you have in buzzword, and possibly that you've worked somewhere like Google — but I've never seen a Googler compete for open-market contracts, so don't feel too disadvantaged. Years of experience with buzzword is the only thing that matters. Delete everything else. Link to your GitHub profile? Goodbye, none of these people are going to read that. I've been assured that the typical talent team spends five to ten seconds per CV . I am a passionate front-end developer with a drive for — no one cares, and if you reflect on how you felt even writing that sentence, of course no one cares. You didn't care. My CV used to say things like "deployed a deep learning project in collaboration with Google engineers" and it had sections like this: Some of the most talented people I know in Australia have told me that that this would qualify me for an instant interview on their teams, but this CV does not work because the person reading your CV will not care about the craft. If someone that does care reads it, it will be after four untalented people decided it was allowed to land on their desk, and at that point they're going to interview you anyway so your CV doesn't matter. The ideal CV starts with lines like this: Five (5) years expert skills in cloud database development and integration (Databricks, Snowflake) using ETL/ELT tools (dbt and Airbyte) and deploying cloud computing with AWS (EC2, RDS) and Azure (VM) cloud platforms The rest of the CV should be more lines like that , nothing else matters. A senior talent acquisition nerd at McKenzie told me that CVs should be one page, because it shows that the candidate is concise. Their counterpart at another agency said that you need three pages or you can barely get to know the candidate. Which of them is right? Both of them had no idea what they're talking about, because both of them are just eyeballing it, coming up with post-hoc rationalizations for their behavior that ignores the real hard question of why they specialize in hiring talent in fields that they cannot describe. I now trend towards a three page CV for no reason other than it looks like I must have more experience if it won't fit on one page, and it gives me more space to put buzzwords in. And when I say buzzwords, I mean you need the room to write things like "Amazon Web Services (AWS)" because some of the people reading the CVs do not know they are the same thing . Act on the principle of minimum charity, and accept that this version of your CV will never get you a great job. We know what we're optimizing for at this stage, and it isn't amazing colleagues, it is the ability to refill your coffers very quickly and with minimal pain . Okay, but which buzzwords do you pick? If you hop onto a job search platform, you are going to see many jobs that are essentially asking you to cosplay as a software engineer. For example, I have just hopped onto Seek and punched in "data engineer", my own subspecialty. This immediately yields this job from an international consultancy whose frontpage reads: GenAI is the most powerful force in business—and society—since the steam engine. As software and code generate more value than ever, every worker, business leader, student, and parent is now asking: Are we ready? Wow, that sure is something! I think speak on behalf of all of us when I say "please stop, you're hurting us". Also it looks like there isn't a single mention of AI on their website in 2022, so I'm really impressed that they've become experts in a novel technology just in time to cash in on over-excited executives . But what does the actual job listing entail? Proficient in Azure Data Factory, Databricks, SQL Server Integration Services (SSIS), and SQL Server Analysis Services (SSAS). And from this, by mental force, I can tell you everything you need to know about the job. My third eye is fully open, and the recruiting department's pathetic attempts to ward off my psychic intrusions are but tattered veils before a hurricane. They are almost certainly recruiting data engineers for a company with a very weak IT department, probably a government client, that is in the middle of a failing cloud migration. SSIS is the phylactery of a millennia old lich-king, a piece of software that runs out of SQL Server on old government data warehouses everywhere. The first time I had to fix an SSIS production outage, the senior engineer on my team told me to "untick all the boxes on that screen, then re-tick them all and click save", and that actually solved the problem . The entire point of a cloud migration is to stop using SSIS and use something better, but that would require you to be good at your job, so instead consultancies sell Azure Data Factory. Azure Data Factory is notable for having been forged in the hottest furnace in Hell. The last time I used it, I clicked "save" on a slow internet connection and it started to open and close random panels for five minutes before saving my work, which I can only assume means that the product has to open every component on the front-end to fetch data from the DOM to populate a POST request... which is, you know, is certainly one way that we could do things. Why use something so bad? It's because Azure Data Factory can be used to run SSIS packages! So now you're on the cloud, and have a new bad service running your old bad service, all without actually improving anything! And of course, they are both tools that do not require programming , so the consultancy can sell you a team of non-programmers for $2,000 per day. I've worked alongside one of these teams. They had one good developer who desperately tried to handle all of their work at once, and I shit you not, four "engineers" that spent eight hours a day copying-and-pasting secrets out of legacy SSIS packages into Azure Data Factory's secret manager for weeks on end. With a bit of experience, most job listings are simply an honor roll of dead IT projects. And because many executives hop onto the same bandwagons at the same times (but call it innovation), there seem to be specific patterns for the type of cog that companies are pursuing at any given moment. The friend who gave me most of the tips in here has an "Azure Data Engineer" CV, where he removes all mention of AWS work he has done so that government recruiters don't hurt their pretty little heads, and vice versa. Companies on Azure want Databricks because you can spin it up from the Azure UI, and companies on AWS similarly use Snowflake because of groupthink. Just smash those words onto the page. Every field can think of some variation of this. If you're a data scientist, it'll be a few common patterns to try cram LLMs into things. If you're a front-end developer, it's probably going to be a soup of React and its supporting technologies. Again, no one reading your CV until the final stage will know anything. Once, a recruiter had coffee with me, and they asked me why Git is such a popular programming language. I write my CV in Overleaf because I can make faster edits during the early phases of figuring out which patterns work, and fidgeting with layout is probably the most annoying part of any sort of CV-writing. This is a tough situation. What I did was looking up a few "easy" jobs, like data analyst, hop onto LinkedIn, navigate to that company's page, then navigate to someone that looks like they might be leading the relevant team. Do not message HR. They, as a rule, do not have human frailties like mercy and kindness while they are at work. Go straight to someone that actually cares if you are good at the job, and impress upon them that you are a real person, who either has a very cool life story about changing career pathways late in life, or who is an adorable graduate UwU. If you are super, super, super desperate, my company has an unpaid internship for graduates that really, really, really think that they just need a tiny bit of experience to get taken seriously. Once you're done scrubbing all signs of personality or competence out of your CV, leaving only eight (8) years of experience, what then? If you're going to be doing this all the time, how do we make it relatively painless? The first thing is to hop onto a bunch of job platforms and upload the CV. That's simple enough. This means that recruiters will start reaching out to you every week or so, and some of them will have jobs for you. 90% of them will fail to secure you an interview. I start the conversations with something like "In the interest of making sure we're making good use of time, what's the expected compensation for the job?". They'll say a number. If the number isn't high enough, thank them for their time and hang up. Don't waste your time. They will not present you to the candidate if you ask them to do any work beyond sending a CV and collecting a commission — from their perspective, you are cattle to be sold. If I was desperate, I would take the first job offered to me at any pay, then not slow my search down at all. Most contracts allow you to quit on very short notice, so use that against the employer instead of having it used against you for a change. The second thing is that you can start testing out the CV in the lowest-effort manner possible. The recommendation from my friend that has experimented the most is to grab a job platform's app on your phone, and to apply to maybe three or four jobs every morning. Don't bother with ones that ask you to make accounts on new job platforms, or write cover letters, or anything like that. Save a filter that removes anything you aren't willing to do, whether it's pay that's too low or a long commute. Err on the side of being picky, and do this every workday , even if you already have a contract. If the list of jobs becomes empty, then you must either relax your constraints or move to a new area. Sorry! If you get as far as a call with a human and are later rejected, ask them, especially recruiters, what employers want to see. They will tell you which buzzwords are good. If there is any conceivable way that you can claim to have experience in an important buzzword, write it down. This is incidentally how the strain of doing this is best managed — by not doing anything more arduous than reading a few jobs and clicking "apply", then not thinking about it until the next day. Don't apply to so many that it feels like even a bit of an ordeal. Do not let rejection affect you, most of the people involved in this process do not deserve your respect in this instance. I am sure they are lovely husbands and wives and sons and we don't care right now . It has taken up to two months before calls have started rolling in, and that is why I'd suggest doing this more-or-less constantly, even when settled into a contract role. You want to know if jobs have suddenly started to dry up, or if you need to make adjustments to your buzzword soup. A fair number of these jobs won't do any sort of diligence. The interview will be fine. Questions will sometimes be on the level of "Do you know Python?", a real question that a real director asked me before paying me hundreds of thousands of dollars. I've done a few more unpleasant interviews, detailed here , but at this point they don't bother me. If I found myself in another one of these situations, I would hang up mid-call. Eat your assertiveness vegetables, they'll put hair on your chest. Quit. You got this job, you'll get another job. Don't quit, duh. Listen man, I didn't design the industry, but I rolled brown skin and an Indian name at character creation. I'm just doing what I've gotta do. All those jobs will be mediocre, but you won't feel like any particular person has too much power over you. But still, the second job market is where you actually want to be. This is the promised land where people have functioning test suites, the executives know something about the work being undertaken, your colleagues are not Senior Void Gazers who have been so thoroughly beaten down by the industry that they dully repeat "it's a living and I have kids", and as a bonus you're probably paid about 50% more. It is so totally divorced from the first job market that people in it sometimes do not understand that the first job market exists. Famous Netflix-guy-turned-Twitch-streamer, the Primagen, has never even heard of PowerBI , what is probably the most popular analytics tool on the planet. These people are blessed . It is not accessible via Seek. It is accessible entirely through having well-placed friends and a reputation for being a cool person with a modicum of self-respect. You can't generate these by pulling the "apply for job" lever over and over. This way you don't have to pray that your friend's companies are hiring at any given moment, you'll just always know that you've got an interview every few weeks. Because getting in here isn't very predictable, this section is general advice in no particular order. If the company asks you to do Leetcode stuff, my opinion right now is that they're probably at least a bit serious, but I don't think a place that asks you to grovel before entry is a great place to be along non-technical dimensions. Erik Dietrich calls this type of interview "carnival cash", rewarding compliant employees and middle managers with the opportunity to terrorize their fellow humans instead of with money. I'm not that sure about this point. I'd probably be bad at a Leetcode interview, so I'm biased against them. Maybe they're correlated with high quality programming performance in some way that I don't understand. People often say "I don't have any connections" or "My network is terrible". This was a 0% judgement zone earlier. It is now a 0% sympathy zone. There is a phenomenon I refer to called "trying to try". It can be broadly summarized as any set of behaviors where someone has not seriously engaged their brain, does not really believe that they're doing anything with a serious chance of success, and are more-or-less just looking for reasons to say that they tried but failed. This happens in subtle places — for example, when training with beginner sabre fencers, you can stand perfectly immobile and they will very consistently hit your blade instead of you. They are so panicked and upset that their body is not trying to win , simply going through the motions of what fencing looks like. This manifests in all sorts of ways that I'll talk about one day, but it's so apparent in the job search. "I've applied for fifty jobs and no one responded". A good indicator that you're trying to try is that you are: Most people tell me they've applied for jobs and didn't get responses. Slightly savvier people tell me they've sent some cold emails out. Some people beyond that say they've started attending Meetups but had no luck. None of them have done anything remotely interesting or otherwise indicative of novel thought. I got my first programming job by emailing Josh Wiley from my psychology degree, a man who did not know me at all, but I had been in one lecture with him, and his wife was the only senior academic honest enough to tell me not to undertake a PhD. I still have the original email. We had a brief back-and-forth, and two weeks later one of his colleagues said "One of my PhD students is freaking out because they can't process some data in R", and that got me my first paid programming job, processing microsaccade data in sleep-deprived drivers. A few weeks later, I saw that a data analyst job was up for grabs at a nearby university. The smooth brained thing to do would have been to apply via Seek and get ignored. I instead went on LinkedIn, looked up the company, look up the word "lead" and cold messaged someone who seemed like they might have something to do with the job. This led me to Dave Coulter, who I still catch up every few months, and a job offer that let me skip straight to being a mid-level engineer. During the interview, when they asked "Have you programmed professionally?", I described the microsaccade project and they hired me. I didn't mention that it was about thirty hours of work in total, and they didn't ask. I actually lost the original position to someone with six more years of experience, who was offered the original data analyst role or a much more highly-paid contract. They wanted the stability, so they took the permanent role, leaving me with a massive pay bump for the contract role, and we both quit at the same time anyway. And they do not conceptualize it as losing tends of thousands of dollars, but they were functionally unemployed for months relative to what they could have earned. Score one for contracts. Those still ended up being mediocre jobs, but I just wanted to illustrate that there is a level of trying that looks more like "there is a gun to my head and I'm willing to do unorthodox things to survive", and the people that email me for jobs have never reached the unorthodox part of that. Presumably the people that do reach this point do not need to email me for jobs. I woke up this morning to an email from Dan Tentler from Phobos about safe ways to run Incus with NixOS images pre-loaded with Airflow and an overlay VPN to client sites. Dan learned about Phobos from a group of hackers in Oslo. I learned about overlay VPNs in December from the CTO of Hyprefire , Stefan Prandl, when asking for advice on network security. I have a discussion about something like this every day, even if it isn't in the tech space. Before that, at a relatively "decent" engineering company, the most complex discussion I had was trying to explain to someone that their Snowflake workload was crashing because they were trying to read 2M+ records into a hash map and that this takes a lot of memory. I have learned more in the last three months than I have in the previous three years, basically along every dimension of my profession. I'm trying to catch up for years of working with mediocre performers, and it's hard . It's definitely doable, and remember that I'm doing this while spending half my time on sales so you can do it faster than me, but there is a real cost to not working with really great people. I've studied hard over the past few years, but nothing comes close to just having awesome people around you. This matters because really good teams don't hire total scrubs that haven't taken control of their education. The first job market does not reward skill or personal development. The second one does actually require you to be good. The best offer I've received from a good company (A$185K) was obtained not through Seek, but by meeting my current co-founder Ash Lally during the preparation for a game of Twilight Imperium IV where I absolutely smoked everyone . The only other place that I've considered might be acceptable to settle down, much better than the offer I received through Ash, was the result of getting coffee with a local reader, then eventually being invited to drinks with their team a few times. We mostly talked about split keyboards and Star Citizen. It has been a few months since I quit my last job, and I used to say all sorts of conciliatory things like "Sure, that engineer is terrible, but most of them are good!", but money talks. I only offered one of them a job with me. In retrospect, most of them had the potential to be good, but enough years in a typical corporate setting will ruin this. When I was 20, people were happy to hire me because I had potential. Now, potential is still important, but it's important that I've at least demonstrated that some of it is manifesting . Many engineers have pathologies that I think make them unsuitable for work on a healthy team, in the same way that some people need to do some self-work to enter a healthy relationship. For example, I know many people who feel guilty taking time off, so they'll burn themselves out without someone constantly getting them to slow down. I'm sympathetic, but a team as small as mine doesn't have time to walk someone through that level of self-harm and still deliver for clients reliably. We help each other through lots of little quirks we need to deprogram out of corporate contexts, but we need to be starting from a place of some progress. An example that Modal's Jonathon Belotti sent my way is that Modal's most high-performing team members will get a two week deadline, then confidently spend the entirety of the first week reading a book on the technology they're about to use. Most engineers I know, including myself a few years ago, would rather hack incompetently for two weeks. The essential reason for this is being too underconfident to act on our beliefs about how engineering should be done (or worse, not having those beliefs at all), and we'd rather fail in the approved, was-working-visibly fashion than risk looking unorthodox. "I programmed the whole two weeks and failed!" feels easier to justify than "I read a book for one of those weeks and failed!". But team members should be picked for their judgement, and if they are good for the team in proportion to the quality of that judgement and their willingness to exercise it in the face of orthodoxy . People are awful at asking for work. Here is how I advise people do it. If you don't have a good time, just leave it be. You're here to rekindle old relationships and meet interesting people, and maybe they can help you out. The moment you start asking people for help that you don't even want as friends is the moment that the entire endeavour becomes sleazy. I think of each person I know (in the context of job searching) as some sort of machine that randomly spits out jobs in a uniform distribution over a year. Let's say each person has a 5% chance of turning up a job every month, maybe more or less depending on the market. If you want an 80% chance at a job every month, you have to have enough people with you in mind that you're rolling the dice enough times to get that number. Many people tell me that they attended a few Meetups and had no results, even though that's what you're supposed to do. It's good that they tried, but most large Meetups seem to be populated by people who are ineffectually looking for jobs. Don't be ineffectual. Large Meetups were frustrating when I was a student because everyone interesting was swarmed by students trying desperately to look employable without being needy, and it is frustrating as a non-student because now I get swarmed. People go "Oh, I am a data scientist, I will go to the Data Science Meetup". That's better than not going out at all, but strictly inferior to going to a tiny Meetup with ten nerds that are deeply into Elixir or some other niche bit of technology. You will form real connections with the latter, and the fact that you know what Clojure is will be enough to make many people at such a place want to work with you. If you are in a city with a functioning tech industry and can't think of any interesting technology, then it's going to be really hard to justify why you deserve a spot on a good team, so maybe solve that problem first. If you're decent at writing and have opinions on something... write. It's amazing for meeting people. I have several readers that have sent me their writing, and without any intervention from me , about 30% of them hit the front-page of Hackernews on the strength of their material. There are surprisingly few people putting out good material on almost all topics, especially in the age of LLM slop. Reader Mira Welner wrote about something as generic as Git checkout and hit the front-page. Bernardo Stein, mentioned in various places on the blog as the guy that coaches me through my worst engineering impulses from my corporate career, has front-paged by writing about NixOS . Nat Bennett, who I've been getting advice from for months and am now hiring to coach my team, front-paged Hackernews writing about the notebook they keep at new jobs . Even Scott Smitelli, who I quoted earlier for having wrote this fantastic piece emailed it to me, and before I could finish reading it people were already recommending it to me through other channels. It's super easy to meet people through writing if you aren't afraid of pushing out your real opinions and indeed, you will see extremely stupid comments on all of the above writing, so you will need to be unafraid. Fine. Tell people that you, personally, are ChatGPT. Someone else may lose their job and think "I wish I hadn't listened to Ludic, he is so stupid and foolish", but I refuse to acknowledge them.  ↩ The main one is Gary Donovan who I didn't even meet in the wild. I met a reader for coffee, and that reader worked with a really nice engineering company. That company said Gary is their favourite recruiter. The first time I called him, I said something about Lisp and it turned out he had a copy of The Little Schemer in front of him at that very second, and we later had a great talk about engineering culture in F1 over ramen. I am still reeling at the implications in neuroscience of a recruiter that can read — is it possible that some of them are sentient?  ↩

0 views
Lambda Land 1 years ago

Should Programming Languages be Safe or Powerful?

Should a programming language be powerful and let a programmer do a lot, or should it be safe and protect the programmer from bad mistakes? Contrary to what the title insinuates, these are not diametrically opposed attributes. Nevertheless, this is the mindset that underlies notions such as, “macros, manual memory management, etc. are power tools—they’re not supposed to be safe.” If safety and power are not necessarily opposed, why does this notion persist? The problem—I think—is that historically you did have to trade safety for certain kinds of power: if you wanted to write a high-performance device driver, C—with all its unsafe behavior—was your only option. This founded the idea that the “power tools” of the industry were fundamentally dangerous. There’s a few things wrong with this though: Power is relative to the domain of interest. Both Haskell and C are powerful, but in completely different ways. So, when judging whether an aspect of a language is powerful or not, consider its application. Expressive languages get you power without sacrificing safety. New advances in programming language research have found ways to express problem domains more precisely. This means that we have less and less reason to breach safety and reach into the unsafe implementation details to get our work done. It’s good to add safety to power tools. A safe power tool is more trustworthy than an unsafe one. This holds for real-world tools: I will never use a table saw without a functioning saw stop. Specifically in the case of macros, there’s been an evolution from powerful-but-unsafe procedural macros in Lisp to safe-but-less-powerful pattern macros in Scheme, and finally to powerful-and-safe macros in Racket. More safety means higher reliability—something that everyone wants. And with advances in making languages more expressive, you can have a language perfectly suited to a particular domain without sacrificing safety. A language that lets you do more of what you want to do is more powerful than a language where you can’t do what you want. But what does “what you want to do” encompass? If you want to write device drivers, then C is great for you. However, C is not as expressive in some of the ways that, say, Haskell is. For example, in Haskell, I can write lazy, recursive definitions. Here’s a list of all Yes, all the Fibonacci numbers. Haskell is lazy; this will compute as many as you ask for. the Fibonacci numbers: Before you tell me that that’s just a useless cute trick, I actually had to use this when I was building the balancing algorithm in my rope data structure for my text editor written in Haskell . Haskell is incredibly powerful in an expressive sense: a single line of code can elegantly capture a complicated computation. The purpose of abstraction is not to be vague, but to create a new semantic level in which one can be absolutely precise. Edsgar Dijkstra Power is closely related to the domain of interest: a language is powerful in a particular realm of problems. C is powerful for working with memory directly. Conversely, Haskell or Racket is more powerful than C in pretty much every other domain because these languages give the user tremendous ability to match the program to the domain . This is a meta-power that sets high-level languages apart from lower-level ones. Safe languages can be just as powerful as their unsafe counterparts—in many cases, they are more powerful because the abstractions they create better fit the domain. Whenever a tradeoff between power and safety must be made, that is a sign that the language is not the right fit for the domain. Consider how immutability gives you local reasoning power . At one of my industry jobs, our codebase was a mixture of Ruby and Elixir. Both are safe languages, but Elixir is immutable. When I was working on some Elixir code, I could read: and I didn’t have to worry about getting modified in the call to . To understand the output of this function, I didn’t have to worry too much about the implementation of . In contrast, if you did the same sort of thing in Ruby: the method could do something sneaky like set name to if it didn’t exist. You might think, “well, just document that behavior.” Now I need to read the documentation of every function I encounter—I might as well go read the code to be sure the documentation isn’t out of date. Local reasoning means to understand what is passed, I don’t have to worry in the first place if will do somethig to the result of . In this case, I did have to understand what every method call did to understand the function. This made it harder to track down errors because I had to account for all the side effects that could happen at every method call. Certain things like immutability might seem constraining, but constraints can liberate you by allowing you to rely on particular behaviors. Elixir doesn’t let you modify things in-place, but you can rely on this, which makes understanding and composing code easier. Haskell forces you to express side-effects in the type system, but this lets you know that calling a function with a signature like won’t do any IO or throw an exception. Rust doesn’t have like in Java, but you know when you get a pointer, you can safely dereference it and you don’t have to do all the null checking that you have to do in Java. The evolution of syntax macros in Lisp, Scheme, and Racket provide an interesting real-world instance of how safety and power can start off as a trade-off, but with better language design, become complimentary. I don’t have the space here to do a deep dive into Lisp macros, but here’s the short of it: Lisp macros are just functions that receive code as data. This code is represented as nested lists of symbols. All a macro needs to do is return a new list of symbols that will be spliced right into the call site. The problem with this is that these macros are unhygienic : if I introduce a new variable, as I did with in , that is just a bare symbol that can be inadvertently captured producing unexpected output: This is very bad! To use a macro safely, you need to be sure that it’s not introducing variables that you might accidentally capture. Lisp provides a mechanism Lisp has a function called which makes a fresh symbol for you to use. Some other languages such as Julia have a function; is a poor substitute for proper hygiene. to avoid some of the pitfalls with variable capture, but that’s not the end of the danger. If I have a macro that expands to a call to a function, e.g. , I would expect this to be the in scope at the time I defined the macro. However, this might not be the case—a user might inadvertently redefine a function, and then the macro would not behave in the expected way. Scheme has a faculty called , which lets you define transformations between a pattern and a template: Rust’s form is essentially from Scheme, but a little fancier with some syntax classes like and such. This is safe; the examples from the Lisp run as expected: However, we’ve lost some of the power because we can only define transformations between templates. We can’t, for example, write a macro that does some deep inspection of the code and makes decisions on how to expand. Furthermore, there’s no way for us to intentionally break hygiene when we really want to. Racket resolves the dilemma between having to choose between powerful Lisp-like procedural macros, and safe Scheme-like hygienic macros by giving us fully hygienic procedural macros! I have another blog post discussing macros in Lisp, Scheme, and Racket and I go into some detail about the evolution of those macro systems. And if you want to dive deep into macro hygiene, see Matthew Butterick’s excellent explainer on Hygiene from his book Beautiful Racket . The upshot of it is that Racket uses a combination of features (scope sets, syntax objects, etc.) to give the user a richer way of specifying syntax than simple dumb lists of symbols. This avoids inadvertent variable capture as well as keeps function references lined up nicely. However, macros can still do arbitrary computation, which means that we’re not constrained in the way that the pattern-transformation macros in Scheme are. And just to prove that Racket is just as powerful as Common Lisp, here’s the classic macro: This example is inspired by Greg Hendershott’s fabulous tutorial Fear of Macros . The bit lets us introduce new bindings intentionally , whilst still keeping us from accidental breaches of macro hygiene. Consequentially, Racket’s macro system is far more useful than Lisp or Scheme’s systems, and this because of Racket’s safety and expressiveness. You can actually build trustworthy systems on top of Racket’s macro system because you’re not constantly foot-gunning yourself with hygiene malfunctions, and the macros are expressive enough to do some rather complicated things . Safe systems let us build software that is more capable and more reliable. Unsafe power is something to improve, not grudgingly accept—and much less defend as somehow desirable. Languages like Rust and Zig have made systems programming immune to whole hosts of errors by being more expressive than C, and languages like Racket are leading the way in making metaprogramming more useful reliable and less like dark magic. If you want to learn more about writing macros in Racket, check out Beautiful Racket by Matthew Butterick and Fear of Macros by Greg Hendershott. I highly recommend listening Runar Bjarnason’s talk at Scala World, Constraints Liberate, Liberties Constrain , wherein he discusses how constraining one part of a system can open up freedoms of later components that build on that constrained part. Power is relative to the domain of interest. Both Haskell and C are powerful, but in completely different ways. So, when judging whether an aspect of a language is powerful or not, consider its application. Expressive languages get you power without sacrificing safety. New advances in programming language research have found ways to express problem domains more precisely. This means that we have less and less reason to breach safety and reach into the unsafe implementation details to get our work done. It’s good to add safety to power tools. A safe power tool is more trustworthy than an unsafe one. This holds for real-world tools: I will never use a table saw without a functioning saw stop.

0 views
Lambda Land 2 years ago

Skills That I Needed When I Started My PhD

I’m starting my third year as a PhD student. I thought it would be good to look back on some of the things that have helped me to this point. I study programming languages, but I imagine these things will help anyone in computer science—and some might have application to other STEM fields as well. There are many softer skills that you need as a PhD student: curiosity, good work ethic, organization, etc. These are essential and nothing can replace them. (Note: that was not an exhaustive list.) I’m going to focus on some of the tools and hard skills that made the ride a little more comfortable. These compliment, rather than compete with, the softer skills that one develops as a beginning researcher. This is a rough list, and not a how-to article. This is mostly just a collection of things I’ve seen other people lacking that have caused them to struggle. If you are considering doing a PhD, you might want to pick up some of these skills as you get ready to start to help you hit the ground running. I recommend reading The Pragmatic Programmer (Thomas, David and Hunt, Andrew, 2019). It’s written primarily for industry programmers, but there’s a lot in there that applies to anyone in CS research. All of the things I mention in this section are covered in detail in there. You have got to know Git. If you cannot wrangle versions of your software and papers (yes, put the papers you write under version control) you will waste much time shooting yourself in the foot and trying to recover work you lost. You will also be laughed to scorn should you ever depart academia for a stint in industry if you do not know Git. In all of the papers I have worked on, we have used Git to collaborate. We’ve typically used GitHub, which is fine as forges go, but I’ve also worked with a self-hosted GitLab instance, and that was fine too. It is incredibly helpful to know a scripting language. I grew up on Perl, which makes munging large amounts of text a piece of cake. You don’t have to learn Perl; you should get really comfortable with a language that makes it easy to manipulate text and files. Makefiles are also super helpful. I like using Makefiles to simply give names to a particular workflow. A Makefile for building a paper might look like this: Now, instead of remembering all the incantations necessary to do some task, I have given that task a name by which I can call it. You must become proficient with the command line. If you are doing research, you will likely need to run software that other researchers have produced. And more likely than not, this will be rough software with bugs and sharp edges that is meant to demonstrate some research concept than be some practical tool ready for developers who only know how to code through YouTube videos and ChatGPT. That this software is rough is a feature of research software , not a bug. There is rarely , if ever, a GUI available. You are going to have to do stuff on the command line, so get used to it. Getting used to the command line helps with Scripting as well. Any task you do on the command line, you can write a script to automate. Building little scripts to e.g. build your paper, your homework, your experiments, etc. will save you time in the long run. Emacs or Vim—pick one and learn it really well. VS Code is flashy and all, but it doesn’t have the same depth and breadth of customizations that Emacs and Vim give you. Also, Emacs and Vim are free software. You are in control! I, of course, love Emacs and I even made a starter kit called Bedrock to help some of my friends in my research lab get started with Emacs. I use Emacs to program, write papers, take notes, manage email, track tasks, and more. I made a list of my top Emacs packages a few weeks ago if you’d like more ideas on what is possible. Vim is fine too and I will still respect you if you choose to go that route. ;) Familiarity with LaTeX has definitely helped me. Fighting with LaTeX is no fun, but you will have to do a little bit of it at some point. Lots of people like using Overleaf; I prefer the command line. Don’t get me wrong: Overleaf is awesome and makes collaborating in a Google Docs sort of way possible, but you loose some flexibility, and if something goes wrong on Overleaf right before your deadline, you’re toast. There is a lovely computer science bibliography hosted at dblp.org . When I was going through the bibliography for my last paper I was able to find lots of missing DOIs simply by putting in the title of the paper into the search bar; DBLP found all the bibliographic information that I needed. Take notes whenever you learn how to do something that wasn’t obvious to you when you started out doing it. I like the Zettelkasten method for taking notes: whenever I learn how to e.g. do some complex layout in LaTeX or learn a neat Makefile trick, I write it down. You can think of it as writing your own personal pages If you don’t know what a page is, this is the standard manual system available on UNIX-like systems (e.g. FreeBSD, macOS, and Linux). Open a terminal and run to read the manual page for itself. You really need to get comfortable with the Command line . Some of these notes I rarely look back at. Others I revisit regularly. But even though I might not review some notes that frequently, there are cases where something on my system will break and a years-old note comes to my rescue from the last time I had to solve that problem. For example, I took notes on how to upgrade my language server for Elixir. I don’t upgrade that thing very often, but there is a little tweak I need to do just because of how my system is set up that is not obvious. It took me a few hours of debugging the first time, but, because I took notes, it now only takes me a few minutes. Academics generally love email. It’s simple, robust, and doesn’t change its UI every few weeks, unlike some popular chat platforms. Unfortunately many universities are forcing everyone to move to Outlook. This is a very bad thing. Fortunately, there are some workarounds that you can use to reclaim some control over your email. I have a sweet workflow with my email. That’s right, I do it all from Emacs. Now, while I do recommend you learn how to use Emacs, I understand that not everyone will start using Emacs. Everyone should get proficient with their email client and know how to use it well. I recommend anything that you can control entirely from the keyboard. You should also get comfortable with editing replies. You know how, when you reply to an email, you usually see something like this: Some mail clients will make the at the beginning of the line pretty with different colored lines and whatnot. It’s all angle brackets under the hood, and you can still edit it as described here. Just typing your reply above the email is called “top-posting”, and it’s considered bad form. You can actually edit the bit that was sent to interleave your reply with bits of the prior email. This makes it easier for people to know what you’re replying to. When used appropriately, this makes emails much more pleasant to read. It doesn’t break the email thread either; you can still see the chain of replies. You need some way to keep track of tasks. I have a workflow based off of Org-mode , which I will not detail here. The short of it is that you need to be spending at least a little time with some regularity “sharpening the saw” 1 by making sure that whatever tool you use to keep track of tasks is working for you. Thomas, David and Hunt, Andrew (2019). The Pragmatic Programmer , Addison-Wesley. https://en.wikipedia.org/wiki/The_7_Habits_of_Highly_Effective_People   ↩︎ https://en.wikipedia.org/wiki/The_7_Habits_of_Highly_Effective_People   ↩︎

0 views
Lambda Land 2 years ago

I Probably Hate Writing Code in Your Favorite Language

The Tao gave birth to machine language. Machine language gave birth to the assembler. The assembler gave birth to the compiler. Now there are ten thousand languages. Each language has its purpose, however humble. Each language expresses the Yin and Yang of software. Each language has its place within the Tao. But do not program in COBOL if you can avoid it. The Tao of Programming I probably hate writing code in your favorite programming language, whatever it may be. This is because I get frustrated by basically all of the top 10 languages you’ll find listed anywhere for various reasons. Do I hate programming in Python? You bet I do. “But it’s Python ! Python is the best programming language on earth!” I can hear you say. I grant that it has its place. Python wins because its ecosystem of libraries is so huge and because there are so many resources for new users. It’s also garbage collected, which means memory safety is not an issue. It the current hot thing, because there is so much support for machine learning in Python. I don’t consider Python quite as boring as Java ! But my problem is that Python is a boring language. This isn’t a bad thing necessarily. If you’re interested in solving a problem with a known solution and you’re doing it for business, the a boring language is probably better for you than, say, Haskell. Why do I think Python is boring? In part because of its philosophy: There should be one—and preferably only one—obvious way to do it. The Zen of Python Python has a model of how it wants you to solve problems. That’s right: it wants you to solve problems with objects, classes, and explicit loops. Got a problem that’s the perfect fit for a functional paradigm? Well, I guess you can use and , but you only get a single expression inside of lambdas, data structures are all mutable, and you can’t use recursion to handle lists. Ugh. I could tell similar stories for other languages that I don’t like programming in. These languages include JavaScript, Go, Java, and C++. Go and Java seem to have been made with huge teams of programmers in mind: make the language and syntax as simple as possible, and then even simpler at the expense of expressivity! This guards against programmers coming up with a clever way to express their problem in a domain-specific way—that’s probably a virtue in large companies. But that’s not how I like to program. The thing I hate about all of the languages I listed is their emphasis on mutation. When I call a function and pass it a list or object or whatever, I have no guarantees about that thing’s value when the function returns. That means, to understand some code, I have to understand all of the functions that get called. In contrast, when I write in a language like Elixir or Haskell, which have immutable data structures, I can look at some code like this: and I don’t have to know what or do to their arguments; I just know they return a value of some kind; I’m free to continue using , , and as much as I like because their value has not changed. It might not seem like much in this example, but it is a big deal when you’re neck-deep in a debugging session. I once worked on a codebase that was half in Elixir and half in Ruby. I spent most of my time on the Elixir side. One time when I had to do some debugging in Ruby, I found it so difficult to trace the execution of the program because data was being changed in method calls. If this doesn’t make much sense to you, you might have to experience it first: once you’ve worked in a large functional codebase, you will find yourself bewildered by all the spooky-action-at-a-distance that goes on inside a large OO codebase. Other things that frustrate me in programming languages include: That last one is something that really bothers me about Python: stack frames in Racket cost 2 words. Source: I asked Matthew Flatt about Racket’s stack frame size once. Either do proper tail-call elimination or , if you really absolutely must have all of your precious stack frames performance and elegance be darned , then allocate your stack frames on the heap and stop worrying about it already! (I seem to recall a conversation where someone with knowledge of these things implied that this was in the works. I don’t know any details about it though.) Seriously though: some solutions lend themselves really well to a nice tail-recursive solution. But can you rely on such an implementation to be performant or even run in Python? Nope. Argh!! Clearly, I like functional programming: it fits how my mind works, and I think it is in a lot of ways objectively better than other paradigms for software engineering. Immutability gives you the ability to reason locally about your code—not to mention not having to worry about race conditions when mutating data in concurrent environments! To parallel the first list that I wrote, here are things that I like in a language: Macros can be a two-edged sword. That said, a lot of the danger around macros has largely been ameliorated. Elixir is a great example of this: Elixir has a small core and uses macros a lot to define basic things like in terms of simpler constructs. What languages do I enjoy programming in? Racket is my favorite: it’s designed to be flexible and give the programmer maximum ability to express their intent to the computer. Racket is a programmable programming language . Other languages I enjoy include Haskell, Elixir, and Rust. Haskell is the ur-functional language, and it’s really fun to use the type system to describe your domain. Pretty soon the compiler starts keeping you from making all sorts of mistakes that would be hard to catch with basic testing. In Elixir, you get lots of nice functional data structures, proper TCO, pattern matching, and soon gradual typing! Rust is great because it has a phenomenal type system with good type inference; its metaprogramming story could be improved though. I want to make it clear that I am not attempting to start a flame-war or saying that Python, Java, et al. are useless: they have their place and are very respectable works of engineering. All I am saying is that, given a choice of language for a hobby project, I will pick something else because I don’t want to be frustrated by the language when I work. Anyway, that’s the end of my griping about languages. (For today, at least.) There will always be things we wish to say in our programs that in all known languages can only be said poorly. A language that doesn’t affect the way you think about programming, is not worth knowing. Alan Perlis Automatic type conversion (looking at you JavaScript). No type inference (if you’re gonna be statically typed, don’t make me write out the type every time Java). No structural typing (type is determined by the shape, not the class name). No good functional data structures. No metaprogramming. No TCO/limits on stack depth. Easy, explicit conversions between different types of data. Dynamic typing or powerful type inference or gradual typing! Structural typing (having nominal typing too can be nice when needed; but given one or the other I’ll take structural over nominal any day). Functional data structures like cons cells, maps and sets supporting functional updates, and RRB trees ! Powerful macros that let me extend the language. Proper TCO.

0 views
Lambda Land 2 years ago

Chorex: Guaranteeing Deadlock Freedom in Elixir

Chorex is a brand-new Elixir library for choreographic programming [ 3 ]: Chorex provides a macro-based DSL that lets you describe how processes communicate to perform a computation. This top-down description of interacting processes is called a choreography . From this choreography, Chorex creates modules for each process that handle all the message-passing in the system. The interactions performed by the generated code will never deadlock by construction because the choreographic DSL ensures that no processes will be waiting on each other at the same time. This is a research project; if you like experimenting with new things, please try this out! The best way to leave feedback is by opening an issue on the Chorex repository. Chorex is still in active development, and we would love to see whatever you make with Chorex. Chorex is available on hex.pm . Development is on GitHub . Try it out! Chorex enables choreographic programming in Elixir. A choreography is a birds-eye view of communicating parties in a concurrent system: you describe the different actors and how they send messages to each other. From this choreography you can create an endpoint projection , which just means you create some code for each of the concurrent actors that handles all the communication. Choreographic programming ensures deadlock freedom by construction . That means you will not be able to accidentally create a system of actors that accidentally deadlock. It’s still possible to have other kinds of bugs that freeze the system (e.g. one of the actors hangs on an infinite loop) but it eliminates an entire class of bug that is difficult to track down in real applications. Additionally, Chorex implements higher-order choreographies [ 1 ] which let you treat choreographies as first-class citizens in your language. This improves the modularity of code built with choreographies. Chorex does all this by leveraging Elixir’s macro system: you write down a choreography using the macro provided by Chorex. The macro expands into several modules: one for each actor in your system. You then create another module for each actor in the system which ​s the respective macro-generated module; the macro-generated module handles the communication between the different parties in the choreography, and your hand-written module handles all the internal bits to that node. Let’s look at an example. Here’s a simple, classic example: someone wants to buy a book, so they ask the seller for the price. The seller responds with the price. Here’s a diagram of that communication: And here is the corresponding choreography describing that: The macro will create (roughly) the following code: along with a macro. Now we create modules for each of our actors ( , ) and we use the generated module to handle the communication: To kick off the choreography, start up a process for each actor and send them everyone’s PID: Now you can wait for the processes to send you (the parent that started the choreography) their return values. From the choreography, we expect the actor to finish with the price . We can get that like so after sending the actors the config for the network: In sum, this is how you use Chorex: Choreographies can get a lot more complicated than this puny example here. See the Chorex README and module documentation for more extensive examples with Chorex. Lugović and Montesi built an IRC client and server in Java with a choreography [ 2 ]—I’m excited to see what’s possible in Elixir! Chorex is a research project , meaning that its primary function is to prove out new ideas. Development speed takes priority over stability of features and API. This is a scout and a trailblazer, not a surveyor and road-laying machine. We would like to make Chorex as useful as possible; historically choreographic programming libraries have been cutting-edge research projects. Chorex is still research-oriented, but if we can make it useful to people other than ourselves, then that’s a big win. :) Moreover, no one has done choreographic programming with Elixir-style concurrency, where processes have mailboxes and where there are existing idioms around processes and communication. This is not intended to be a production-grade system. Maybe some day, but not today. Please don’t use this to build a production system then blame us when your system goes down. Please do use this in hobby projects and let us know what you manage to build! Please send us any feedback you have! You can contact me directly or open an issue on the Chorex repository. We would love to see anything you make with Chorex. While building the macro, I realized I needed to walk an AST and gather up a list of functions an an endpoint would need to define. This inspired me to create a writer monad; I documented how I stumbled upon a pattern that a monad solved quite elegantly earlier on my blog . Write a choreography to describe your system The macro will create modules for each endpoint Implement each endpoint’s derived behaviour Fire of the choreography Await replies

0 views
Lambda Land 2 years ago

My Top Emacs Packages

If you ask anyone what the best Emacs packages are, you’ll almost definitely hear Magit (the only Git porcelain worth using) and Org Mode (a way to organize anything and everything in plain text) listed as #1 and #2. And they’re right! I use those packages extensively every day. Besides those two powerhouses, there are a handful of packages that make using Emacs a delight. If I had to ever use something else, I would miss these packages most: Jump around your screen crazy fast. Teleport to any character with ~5 key strokes. See https://karthinks.com/software/avy-can-do-anything/ for more reasons why it’s awesome. I almost exclusively rely on and have it bound to . Kind of like a super-charged right-click for Emacs. Works beautifully in dired, when selecting files in the minibuffer. There’s an easy way to make it play well with Avy which is just the best. Eat is a terminal emulator that’s faster almost all the other terminal emulators for Emacs. The only emulator it’s not faster than is Vterm, which is pretty dang speedy. Eat has been more than fast enough for all my needs however. Additionally, it can make a terminal emulator in a particular region , so if you use Eshell, you can get a little terminal emulator for every command you run. Normally, if you run, say, , you see the ugly terminal escape characters printed as text. With Eat, however, those terminal escape characters get interpreted correctly. Interactive programs (e.g. the Julia and Elixir REPLs) work flawlessly with it. Best spellchecking ever. It can spellcheck based off of the fontlock face; I keep this on when I program to get on-the-fly spellchecking of code comments and strings. I keep bound to à la flyspell because it is so darn helpful. Supports checking documents with mixed languages. This is one of the packages I miss most when I’m editing text outside of Emacs. The best way to add citations in Emacs, hands-down. Reads bibtex, inserts in org-mode, LaTeX, whatever. These next packages are all by Daniel Mendler . These packages improve selecting commands, buffers, files, etc. from the and interfaces. These make Emacs insanely ergonomic and excellent. These replace packages like Helm , Ivy/Counsel/Swiper , and Company . In comparison to these packages, Vertico + Consult + Corfu are lighter-weight, faster, less buggy (in my experience; I’ve tried them all!), and work better with other Emacs packages because they follow the default built-in APIs. Lighter-weight, less buggy vertical completing-read interface. Replaces Ivy. Incredibly flexible. Works out-of-the-box with everything that has a interface, so you don’t need special packages to make it play nice. Recommend adding Marginalia as well by the same author to add extra infos. Better than counsel. The live preview is amazing; I use instead of , instead of Swiper. is :fire: for searching large projects with instant previewable results. Pairs well with Embark to save results to a buffer. Lightweight pop-up library. Pairs well with Cape by the same author. See also Orderless which enhances everything from to to the Corfu popup. Vertico + Consult + Orderless + Marginalia + Corfu + Cape + Embark is sometimes called the “minad stack”. Embark and Orderless are both developed by Omar Camarena (oantolin) who frequently collaborates with Daniel Mendler. When I asked Omar on Reddit about the name, Omar replied that “minad stack” is fine; another name they’ve tried for the stack is “iceberg”, which I think is a good name too. It’s the new hotness—that said, it’s gotten really really stable over the past two years. If you like these packages, consider sponsoring their maintainers! These are some of my favorite open-source projects and I try to support them when I can. If you like these packages, you might like my Emacs Bedrock starter kit which, unlike many other starter kits, is meant to be a no-nonsense no-fluff no-abstraction bare-bones start for you to fork and tinker with to your liking. The stock configuration only installs one package ( which-key , which is amazing) but includes some extra example configuration. The extras/base.el file includes sample starter configuration for most of the above packages. (I should add to it, come to think of it…) Avy Jump around your screen crazy fast. Teleport to any character with ~5 key strokes. See https://karthinks.com/software/avy-can-do-anything/ for more reasons why it’s awesome. I almost exclusively rely on and have it bound to . Embark Kind of like a super-charged right-click for Emacs. Works beautifully in dired, when selecting files in the minibuffer. There’s an easy way to make it play well with Avy which is just the best. Eat Eat is a terminal emulator that’s faster almost all the other terminal emulators for Emacs. The only emulator it’s not faster than is Vterm, which is pretty dang speedy. Eat has been more than fast enough for all my needs however. Additionally, it can make a terminal emulator in a particular region , so if you use Eshell, you can get a little terminal emulator for every command you run. Normally, if you run, say, , you see the ugly terminal escape characters printed as text. With Eat, however, those terminal escape characters get interpreted correctly. Interactive programs (e.g. the Julia and Elixir REPLs) work flawlessly with it. Jinx Best spellchecking ever. It can spellcheck based off of the fontlock face; I keep this on when I program to get on-the-fly spellchecking of code comments and strings. I keep bound to à la flyspell because it is so darn helpful. Supports checking documents with mixed languages. This is one of the packages I miss most when I’m editing text outside of Emacs. Citar The best way to add citations in Emacs, hands-down. Reads bibtex, inserts in org-mode, LaTeX, whatever. Vertico Lighter-weight, less buggy vertical completing-read interface. Replaces Ivy. Incredibly flexible. Works out-of-the-box with everything that has a interface, so you don’t need special packages to make it play nice. Recommend adding Marginalia as well by the same author to add extra infos. Consult Better than counsel. The live preview is amazing; I use instead of , instead of Swiper. is :fire: for searching large projects with instant previewable results. Pairs well with Embark to save results to a buffer. Corfu Lightweight pop-up library. Pairs well with Cape by the same author. Eat is not the fastest terminal emulator, Vterm is. Thanks to a Redditor who pointed this out.

0 views
Lambda Land 2 years ago

Boilerplate Busting in Functional Languages

This is the story of how I solved a problem (ugly, cumbersome boilerplate code) that I ran into while writing a program in a functional language (Elixir). Functional programming languages often pride themselves on expressiveness and elegance; but occasionally they are not amenable to the most obvious solutions to the problems we wish to solve. In this case, the simplest solution to my problem would have been to have a global mutable variable. But no one likes those. The solution most programmers would have found obviates mutation, but the code ends up being rather clunky. This inelegance stems from two intertwined issues pulling the code in different directions. However, the two concerns are so intertwined that it can be difficult to see them as separate issues at all! In this blog post, I hope I can show you a new way of looking at this class of problem that will let you see what those two issues are, and how to cleanly split them apart to get nice, maintainable, functional code. You take your analytic knife, put the point directly on the term Quality and just tap, not hard, gently, and the whole world splits, cleaves, right in two… and the split is clean. There’s no mess. No slop. No little items that could be one way or the other. Not just a skilled break but a very lucky break. Sometimes the best analysts, working with the most obvious lines of cleavage, can tap and get nothing but a pile of trash. And yet here was Quality; a tiny, almost unnoticeable fault line; a line of illogic in our concept of the universe; and you tapped it, and the whole universe came apart, so neatly it was almost unbelievable. Zen and the Art of Motorcycle Maintenance , Robert M. Pirsig I will use a concrete example to describe my specific problem, though the issue is general enough that you likely have encountered it. After I walk through the example I’ll cover the essential elements that make my solution work, and how it generalizes to similar problems. Suppose that you are writing an application that lets people track their workout habits. Every time that they succeed in meeting a goal, they register what they accomplished. Now you have a database full of logged events like “went to the gym” or “swam 1000 m” or “ran a mile”, etc. Now you need some way to convert this set of events into reward points—preferably in a way that the user finds motivating. But every user is different, so let’s say that you make it so that users can customize exactly how goal completions translate into reward points. Somewhere in your app you let users write a little equation that your program will then evaluate against the events that they have logged. In the end, the user gets a single point value. This is, in essence, a little interpreter. I will not go over how to build an interpreter here, but the gist of it is that you walk down the AST of the equation and evaluate the leaves and nodes recursively until you wind up with a single number of points at the end. Now let’s say that you are processing a large number of such requests, and you would like to batch all of the database calls. In the previous example, there were four database queries, one of which ( ) was a duplicate. (Each in the surface syntax or node in the AST induces a database query.) To improve performance, you could batch all of these database calls—thereby also eliminating duplicate queries—and then have this data on hand as you walk the AST. So here is the new operation we would like to perform: we want to walk through AST, collect all of the database calls into a set, and replace each instance of a database query ( nodes) in the expression to a reference ( nodes) that we can link to the batched results. We will create a fresh identifier every time we encounter a new query; duplicate queries will use the first reference. Now we need to implement it. We could just create a variable that we can mutate as we walk down the tree: every time we encounter a node that looks like , we generate a fresh identifier (or look up an old one if it’s a duplicate query) and replace it with . Once we’re done walking the tree, we have a new AST with nodes instead of nodes, and a list of queries that we can execute in one go. This could work, but the fact that we are using global mutable state should ring alarm bells for anyone —not just functional programmers. Whenever we call our transform function, we would have to ensure that we clear out the old list of accumulated information. Don’t forget about all the other problems that global mutable state brings. There must be a better way. How might our function be more pure? Instead of just returning a modified tree, we can return a tuple of the new AST node plus a list of queries. (I’ll call this specific shape an AST-queries-tuple throughout.) This eliminates the need for a global variable, and now every call to our optimization function is pure. It’s easier to test and reason about. However, this means that we have to take care to combine this information whenever we do a recursive call. It becomes even more cumbersome when we recur over elements in a list and we need to combine all their results together. A well-crafted makes things work OK, but I think you can agree the following code isn’t the most straightforward to read and understand what’s going on. This is quite a bit of boilerplate. It’s not the worst code ever—it’s certainly better than our first solution with a global variable—but we seem to be saying more than we need to here. How can we clean up this code? The code is messy because there are actually two competing concerns here: we have some main computation that we care about (transforming the tree) and some side information (the set of database queries) that we’d like to collect in parallel. If we can separate these concerns, our code will improve. Now that we see the two intertwined issues, how do we go about separating them? We will still carry around the AST-queries-tuple, but we are going to pull out the logic that governs how we keep track of the list of queries and keep it separate from the AST transformation logic. First, let’s define a module, a type to help us keep track of an AST-queries-tuple, and a function that takes some AST and pairs it with an empty list of queries: Second, the clever bit: we write a function that lets us manipulate the AST value inside the tuple without worrying about how to combine the sets of queries . We’ll call this function , and it takes an AST-queries-tuple and gives it to a function argument that expects just the AST bit. That function argument should return a new AST-queries-tuple. Our function will then merge the two lists of queries together without the function parameter ever having to worry about it. Now we can use this to write our function! Before we get there, remember that Elixir has a handy set of customizable infix operators that we can use as shorthand: It would be nice if we could use for this shorthand… but I’m getting ahead of myself. That means that instead of writing this: We can just write: You might be able to see now how this would make writing this little optimization pass a lot cleaner. We can go a step further on the syntax though: with a little metaprogramming imagination, we can write some shorthand for the notation that looks more like variable assignment in a . It’s not that hard to do. Now we can write the handler for like so: And that gets transformed into the nested anonymous function notation we saw previously. Now we don’t have to think about merging the list of queries any more: the operator handles all that for us. Bigger savings come if we think about making a version of that works with our AST-queries-tuples. We’ll call it since it’s like a that we’re mashing the results together: Here we map over a list of values, collect all the resulting sets of queries, and merge them together. This gives us a big savings when we write something like the function: Notice that all the handling of the extra information has been lifted out of our code. Not only does it make it clearer what the core intent of the functions are, but we also get some added flexibility around how we structure that extra data. If we wanted to use a map or a set instead of a list of tuples as the second element in the AST-queries-tuple, then with this refactoring we only have to modify the , , and functions. The rest of the code can stay the same! So, with a little bit of work, we’ve gone from a solution using global mutable state 🤢 to passing around a AST-queries-tuple 😐 to abstracting out the tuple entirely, gaining clarity and flexibility along the way. 🤩 Our threading-related functions are actually generic enough that they don’t need to be about ASTs and lists of queries—as long as we are doing some main computation with a little extra data gathering on the side, this pattern should apply. Wouldn’t it be nice if this pattern had a name? Surprise! This is exactly the writer monad ! This whole post has been a monad tutorial in disguise! If you’ve been exposed to monads before, you might recognize as and as or—as the Haskell programmers like to call it— . The macro is just notation . (I couldn’t think of a clever name for , so I just pretended the stood for mash instead of monad .) “Monad” is just an interface. There’s a subtle difference between interfaces and typeclasses, and I’ll get to that shortly. This is meant to build intuition. That’s all there is to it. To make your data structure (like our AST-queries-tuple) conform to the Monad interface, you need functions like and . Once you have those, you have a monad. OK, there are certain properties (called the “monad laws”—don’t worry, they’re not that scary even though the name sounds ominous) that these functions need to satisfy, but they’re pretty easy to hit. If you don’t satisfy these laws, your monad won’t behave predictably in certain circumstances. If you’re getting started with monads, don’t worry about it right now. That’s pretty much all there is to it. There isn’t a fixed number of monads; there are however a set of more-or-less “standard” monads which have been discovered to be generally useful; the Haskell Wiki has a nice list here . Among these is the “maybe” monad, which lets you focus on the happy-path of computation and abstracts away the failure path. In Elixir, you can see this pattern with the idiom. (The notation commonly seen in this idiom closely follows Haskell’s notation.) There are many other useful monads besides the writer and maybe monads. Some of these (like the IO monad ) are pretty specific to Haskell and other pure functional languages that don’t support the same kinds of control flow or constructs; others have wider application. Most of the value (I think) of monads is not having and , but all the helper functions like , notation, and friends. While I was writing some Haskell , I got to know all the monad-related functions and how useful they were. These helper functions are what make programming with monads natural and powerful . The core functions and are all you “need” to make a monad, but no one would actually program with just those. If you ever find something that you think would work well modeled as a monad, be sure to implement additional functions beyond and . You can see a list of functions Haskell implements for monads here if you want some inspiration. You hear a lot about monads with languages like Haskell, but not so much with other functional languages like Elixir or Rust. Part of this is need, and part is because of ergonomics. Haskell needs monads to implement effectful computation. Effects include exceptions (modeled by the maybe monad) or logging information (the writer monad). Languages that have these effects natively don’t strictly need these monads. (Though, as we’ve seen, writing a monad can help other languages, even when they have uncontrolled side-effects, like Elixir.) Haskell makes using monads ergonomic through its typeclass mechanism. Other languages have more constrained method dispatching mechanisms, so you have to jump through some hoops to get monads to work as seamlessly as they do in Haskell. If you’re familiar with an OO language, you’ve almost certainly come across the idea of an interface: it’s just a specification of methods an object needs to implement. Typeclasses are similar: they specify a set of functions needed for a type to belong to a typeclass. There are a few key differences between typeclasses and interfaces however: Interfaces are closed , meaning, if an object doesn’t implement the interface, you can’t do anything about it, unless you modify the definition of the object itself. In contrast, typeclasses are open , meaning that I can implement the requisite functions to turn a datatype into a monad, even if I can’t modify the definition of the datatype itself. Interfaces specify methods that dispatch on their object. If I call , then I will look up the method in whatever class belongs to. Typeclass functions can dispatch on the return type of the function. For example, the (i.e. ) function needs to dispatch on whatever type it’s expected to return. If I said something like: would dispatch to the version specified for the type for , and ’s implementation for . This makes the monad functions really generic; with a that can dispatch on the expected return type, you can write without thinking much about which monad exactly you’re using. In languages that don’t have typeclasses, you need to take special steps to ensure that you dispatch to the proper variant of the monad. Racket has a monad library that works via a generics interface system, plus a few tricks to teach (called in this library) what type it should return. I am sure the same tricks would apply to Elixir. Indeed, Elixir has protocols , which are like interfaces, but they are open. They still dispatch on the shape of the first argument passed to them, you would need to pull a trick like Racket’s function and pass an argument to ignore just to get the dispatch right. Elixir has less need for monads than Haskell because its functions are impure. (A function can do arbitrary IO, send messages, throw exceptions, etc.) but there are still cases (as we have seen) where a monad can make life easier. Consider using a monad library the next time you need to avoid ugly side-effects! Functional languages are not immune to sprouting boilerplate. And while most of the design patterns in a certain OO cookbook are “invisible or simpler” in functional languages, Quote from Peter Norvig. See: https://norvig.com/design-patterns/design-patterns.pdf some patterns crop up when similar problems arise. Monads are a powerful tool for dividing the essential from the incidental in a program. Exactly what constitutes the essential versus incidental parts—along with how to separate them—can be tricky to see at first. I think this is because separating these concerns in mainstream functional languages get less visibility, and not because of any inherent difficulty of the problem. Perhaps if everyone started out programming by learning Racket and got comfortable with functional idioms, monads would be as natural as the Visitor pattern. Certainly it would be more comfortable than the ! I was surprised and delighted when a monadic solution appeared as the most natural solution to a problem I was working on. Now, you might say that’s because I work as a programming languages researcher. However , the last two times I was working in industry, we had some sort of language interpreter, and I had to walk an AST. Knowing the writer monad would have saved me a lot of time and effort. I hope you can start seeing some monadic patterns in your code, and that you’ll be able to make a monad to make it easier to reason about and refactor your code as well. Thanks to Scott Wiersdorf for the initial impetus to write this, as well as some thoughtful feedback on the prose and outline. Thanks also to Mark Ericksen for some additional comments. Interfaces are closed , meaning, if an object doesn’t implement the interface, you can’t do anything about it, unless you modify the definition of the object itself. In contrast, typeclasses are open , meaning that I can implement the requisite functions to turn a datatype into a monad, even if I can’t modify the definition of the datatype itself. Interfaces specify methods that dispatch on their object. If I call , then I will look up the method in whatever class belongs to. Typeclass functions can dispatch on the return type of the function. For example, the (i.e. ) function needs to dispatch on whatever type it’s expected to return. If I said something like: would dispatch to the version specified for the type for , and ’s implementation for . This makes the monad functions really generic; with a that can dispatch on the expected return type, you can write without thinking much about which monad exactly you’re using. https://wiki.haskell.org/All_About_Monads https://learnyouahaskell.com/a-fistful-of-monads

0 views
Lambda Land 2 years ago

Towards Fearless Macros

Macros are tricky beasts. Most languages—if they have macros at all—usually include a huge “here there be dragons” warning to warn curious would-be macro programmers of the dangers that lurk ahead. What is it about macros that makes them so dangerous and unwieldy? That’s difficult to answer in general: there are many different macro systems with varying degrees of ease-of-use. Moreover, making macros easy to use safely is an open area of research—most languages that have macros don’t have features necessary to implement macros safely. Hence, most people steer clear of macros. There are many ways to characterize macro systems; I won’t attempt to cover them all here, but here’s the spectrum I’ll be covering in this post: Figure 1: A spectrum of how easy macro systems are to use safely If you’ve done any C programming, you’ve likely run into things like: That bit is a macro—albeit a C macro. These operate just after the lexer: they work on token streams. It’s a bit like textual search-and-replace, though it knows a little bit about the structure of the language (not much: just what’s a token and what’s not) so that you won’t run into problems if you do something like this: because that in the string is not a token—it’s just part of a string. C macros can’t do very much: you scan the token stream for a macro, then fill in the variables to the macro, and then replace the macro and the arguments its consumed with the filled-out template that is the macro definition. This prevents you from doing silly things like replacing something sitting inside of a string literal, but it’s far, far from being safe, as we’ll see in the next section. In contrast to C’s macros, Lisp’s macros are much more powerful. Lisp macros operate after the lexer and the parser have had a go at the source code—Lisp macro operate on abstract syntax trees —or ASTs, which is what the compiler or interpreter works with. Why is this a big deal? The ASTs capture the language’s semantics around precedence, for instance. In C you can write a macro that does unexpended things, like this: The macro didn’t know anything about precedence and we computed the wrong thing. This means that, to use a macro in C, you have to have a good idea of how it’s doing what it’s intended to do. That means C macros are leaky abstractions that prevent local reasoning: you have to consider both the macro definition and where it’s used to understand what’s going on. In contrast, Lisp macros are an improvement because they will rewrite the AST and the precedence you’d expect will be preserved. You can do this, for example: Lisp macros are also procedural macros , meaning you can execute arbitrary code inside of a macro to generate new ASTs. Macros in Lisp and its descendants are essentially functions from AST → AST. This opens up a whole world of exciting possibilities! Procedural macros constitute a “lightweight compiler API”. [ 4 ] “Same except for variable names” is also called alpha-equivalence. This comes from the λ-calculus, which states that the particular choice of variable name should not matter. E.g. \(\lambda x.x\) and \(\lambda y.y\) are the same function in the lambda calculus, just as \(f(x) = x + 2\) and \(g(y) = y + 2\) are the same function in algebra. Lisp macros aren’t without danger—many a Lisp programmer has shot their foot off with a macro. One reason is that Lisp macros are not hygienic —variables in the macro’s implementation may leak into the context of the macro call. This means that two Lisp programs that are the same except for different variable names can behave differently: The fact that the macro implementation uses a variable named ( tmp-leaky ) has leaked through to the user of the macro. ( tmp-capture ) This phenomenon is called variable capture , and it exposes this macro as a leaky abstraction! There are ways to mitigate this using , but those are error-prone manual techniques. It makes macro writing feel like you’re writing in an unsafe lower-level language. Scheme’s macros introduce a concept known as hygiene , which prevents variable capture automatically: In this case, the variable that the macro introduces ( tmp-intro-macro ) is not the same thing that the variable from the calling context ( tmp-intro-let ) refers to. This separation of scopes happens automatically behind the scenes, so there’s now no chance of accidental variable capture. Breaking hygiene has some utility in some cases—for example, one might want to add a form inside the body of a loop. There are ways around hygiene, but these are not without some problems. For more details see [ 2 ]. If you’d like to know more about hygiene, [ 1 ] is an excellent resource. Since Scheme macros (and Lisp macros more generally) allow running arbitrary Scheme code—including code from other modules—the dependency graph between modules can get so tangled that clean builds of a Scheme codebase are impossible. Racket solves this problem with its phase separation , which puts clear delimiters between when functions and macros are available to different parts of the language. This detangles dependency graphs without sacrificing the expressive power of macros. I wrote a little bit about phase separation ; you can read more on the Racket docs as well as Matthew Flatt’s paper [ 3 ] on phase separation. Racket also has a robust system for reasoning about where a variable’s definition comes from called a scope set . This is a notion makes reasoning about where variables are bound sensible. See a blog post as well as [NO_ITEM_DATA:flattBindingSetsScopes2016b] by Matthew Flatt for more on scope sets. Phase separation and scope sets make Racket macros the safest to use: Racket macros compose sensibly and hide their implementation details so that it is easy to write macros that are easy to use as if they were built-in language constructs. Racket also goes beyond the form that it inherited from Scheme; Racket’s macro-building system makes generating good error messages easy. There’s a little bug in the macro we used earlier, and that is the form only takes an identifier (i.e. a variable) as its first argument. We don’t have any error checking inside the macro; if we were to call with something that wasn’t an identifier, we’d get an error in terms of the the macro expands to, not the macro call itself: This isn’t good because there’s no in our code at all! We could add some error handling in our macro to manually check that and are identifiers, but that’s a little tedious. Racket’s helps us out: Much better! Now our error is in terms that the macro user will recognize. There are lots of other things that can do that make it easy to write correct macros that generate good error messages—a must for macros that become a part of a library. Many modern languages use macros; I’ll only talk about a few more here. If something’s missing, that’s probably because I didn’t want to be exhaustive. Julia macros have a lot of nice things: they operate on ASTs and they’re hygienic, though the way hygiene is currently implemented is a little strange: all variables get ’d automatically Meaning, they all get replaced with some generated symbol that won’t clash with any possible variable or function name. whether or not they come from inside the macro or they originated from the calling code. Part of the problem is that all variables are represented as simple symbols, which [ 1 ] shows is insufficient to properly implement hygiene. Evidently there is some ongoing work to improve the situation. This is a good example of research ideas percolating into industry languages I think. Elixir has robust AST macros, and its standard library makes heavy use of macros; many “core” Elixir constructs like , , , , and others are actually macros that expand to smaller units of Elixir. Elixir actually gets hygiene right! Unlike Julia, variables in Elixir’s AST have metadata—including scope information—attached to them. This and other aspects of Elixir’s macro system open it up to lots of exciting possibilities. The Nx library brings support for numerical and GPU programming to Elixir, and it works essentially by implementing a custom Elixir compiler in Elixir itself , and macros play a big role in this. Me thinking that Elixir is a big mainstream language should tell you something about the languages I spend my time with in my job as a PhD student. I think Elixir macros are really neat—they’re the most powerful I’ve seen in a “big mainstream” language. Rust supports two kinds of macros: macros-by-example, and procedural macros. Macros-by-example are a simple pattern-to-pattern transformation. Here’s an example from The Rust Book : This macro takes a pattern like and expands it to a pattern like Notice how the marks a part of the template that can be repeated. pattern-repeat This is akin to Racket or Scheme’s repetition form. Macros-by-example work on AST, but you can’t perform arbitrary computation on the AST. For that, you need procedural macros. Rust’s procedural macros (called “proc macros”) work on a token stream, and you can perform arbitrary computation, which puts them in a bit of a funny middle ground between C and Lisp. There is a Rust crate that you can use to parse a Rust token stream into Rust AST, but you don’t get any nice source information from the AST nodes, which makes producing good error messages a challenge. I personally find Rust macros to be disappointing. There’s a wide variety of macro systems. The best macro systems: Different languages have different features in their macro systems; some languages make it easy to use macros sensibly, while for others macros are a formidable challenge to use properly—make sure you know what your language provides and the trade-offs involved. Turns out you can do a lot with functions. Powerful function programming languages let you do so much with first-class functions. If you can get access to first-class continuations , as you can in Racket and Scheme, then you can create powerful new programming constructs without having to resort to macros. I came across the JuliaCon 2019 keynote talk, where Steven Johnson explains how many of the things that you can do with macros can be solved just with Julia’s type dispatch. If you can do something with functions, you probably should: functions are first-class values in most languages these days, and you’ll enjoy increased composability, better error messages, and code that is easier to read and understand by your peers. Macros introduce little languages wherever you use them. For simple macros, you might not have any constraints on what you may write under the scope of a macro. As an example, consider a macro that adds a -loop construct to a language by rewriting to another kind of looping mechanism: you shouldn’t have any restriction on what you can write inside the body of the loop. However, more complex macros can impose more restrictions on what can and cannot be written under their lexical extent. These restrictions may or may not be obvious. Examples: accidental variable capture limits what can be safely written, and grammatical errors (e.g. using an expression where an identifier was expected) can lead to inscrutable errors. Better macro systems mitigate these problems. It’s not enough to just have a macro system that uses ASTs; you need a macro system that makes it easy to write correct macros with clear error messages so they truly feel like natural extensions of the language. Few languages do this right. Macro systems have improved since the 1960s. While Lisp excluded many of the pitfalls of C macros by construction , you still had to use kluges like to manually avoid variable capture. Scheme got rid of that with hygienic macros, and Racket improved matters further by improving macro hygiene through scope sets and introducing phase separation. It is so much easier to build robust macro-based abstractions. Macros are good—anyone can write macros and experiment with new syntactic constructs. This makes development and extension of the language no longer the sole domain of the language designer and maintainer—library authors can experiment with different approaches to various problems. We see this a lot with Elixir: Elixir’s core language is really rather small; most of the magic powering popular libraries like Ecto or Phoenix comes from a choice set of macro abstractions. These and other libraries are free to experiment with novel syntax without fear of cluttering and coupling the core language with bad abstractions that would then need to be maintained in perpetuity. Macros can be powerful when used correctly—something made much easier by modern macro systems. \(\lambda x.x\) and \(\lambda y.y\) are the same function in the lambda calculus, just as \(f(x) = x + 2\) and \(g(y) = y + 2\) are the same function in algebra. Lisp macros aren’t without danger—many a Lisp programmer has shot their foot off with a macro. One reason is that Lisp macros are not hygienic —variables in the macro’s implementation may leak into the context of the macro call. This means that two Lisp programs that are the same except for different variable names can behave differently: The fact that the macro implementation uses a variable named ( tmp-leaky ) has leaked through to the user of the macro. ( tmp-capture ) This phenomenon is called variable capture , and it exposes this macro as a leaky abstraction! There are ways to mitigate this using , but those are error-prone manual techniques. It makes macro writing feel like you’re writing in an unsafe lower-level language. Scheme’s macros introduce a concept known as hygiene , which prevents variable capture automatically: In this case, the variable that the macro introduces ( tmp-intro-macro ) is not the same thing that the variable from the calling context ( tmp-intro-let ) refers to. This separation of scopes happens automatically behind the scenes, so there’s now no chance of accidental variable capture. Breaking hygiene has some utility in some cases—for example, one might want to add a form inside the body of a loop. There are ways around hygiene, but these are not without some problems. For more details see [ 2 ]. If you’d like to know more about hygiene, [ 1 ] is an excellent resource. Racket macros: phase separation and scope sets # Since Scheme macros (and Lisp macros more generally) allow running arbitrary Scheme code—including code from other modules—the dependency graph between modules can get so tangled that clean builds of a Scheme codebase are impossible. Racket solves this problem with its phase separation , which puts clear delimiters between when functions and macros are available to different parts of the language. This detangles dependency graphs without sacrificing the expressive power of macros. I wrote a little bit about phase separation ; you can read more on the Racket docs as well as Matthew Flatt’s paper [ 3 ] on phase separation. Racket also has a robust system for reasoning about where a variable’s definition comes from called a scope set . This is a notion makes reasoning about where variables are bound sensible. See a blog post as well as [NO_ITEM_DATA:flattBindingSetsScopes2016b] by Matthew Flatt for more on scope sets. Phase separation and scope sets make Racket macros the safest to use: Racket macros compose sensibly and hide their implementation details so that it is easy to write macros that are easy to use as if they were built-in language constructs. Racket also goes beyond the form that it inherited from Scheme; Racket’s macro-building system makes generating good error messages easy. There’s a little bug in the macro we used earlier, and that is the form only takes an identifier (i.e. a variable) as its first argument. We don’t have any error checking inside the macro; if we were to call with something that wasn’t an identifier, we’d get an error in terms of the the macro expands to, not the macro call itself: This isn’t good because there’s no in our code at all! We could add some error handling in our macro to manually check that and are identifiers, but that’s a little tedious. Racket’s helps us out: Much better! Now our error is in terms that the macro user will recognize. There are lots of other things that can do that make it easy to write correct macros that generate good error messages—a must for macros that become a part of a library. Other languages # Many modern languages use macros; I’ll only talk about a few more here. If something’s missing, that’s probably because I didn’t want to be exhaustive. Julia # Julia macros have a lot of nice things: they operate on ASTs and they’re hygienic, though the way hygiene is currently implemented is a little strange: all variables get ’d automatically Meaning, they all get replaced with some generated symbol that won’t clash with any possible variable or function name. whether or not they come from inside the macro or they originated from the calling code. Part of the problem is that all variables are represented as simple symbols, which [ 1 ] shows is insufficient to properly implement hygiene. Evidently there is some ongoing work to improve the situation. This is a good example of research ideas percolating into industry languages I think. Elixir # Elixir has robust AST macros, and its standard library makes heavy use of macros; many “core” Elixir constructs like , , , , and others are actually macros that expand to smaller units of Elixir. Elixir actually gets hygiene right! Unlike Julia, variables in Elixir’s AST have metadata—including scope information—attached to them. This and other aspects of Elixir’s macro system open it up to lots of exciting possibilities. The Nx library brings support for numerical and GPU programming to Elixir, and it works essentially by implementing a custom Elixir compiler in Elixir itself , and macros play a big role in this. Me thinking that Elixir is a big mainstream language should tell you something about the languages I spend my time with in my job as a PhD student. I think Elixir macros are really neat—they’re the most powerful I’ve seen in a “big mainstream” language. Rust # Rust supports two kinds of macros: macros-by-example, and procedural macros. Macros-by-example are a simple pattern-to-pattern transformation. Here’s an example from The Rust Book : This macro takes a pattern like and expands it to a pattern like Notice how the marks a part of the template that can be repeated. pattern-repeat This is akin to Racket or Scheme’s repetition form. Macros-by-example work on AST, but you can’t perform arbitrary computation on the AST. For that, you need procedural macros. Rust’s procedural macros (called “proc macros”) work on a token stream, and you can perform arbitrary computation, which puts them in a bit of a funny middle ground between C and Lisp. There is a Rust crate that you can use to parse a Rust token stream into Rust AST, but you don’t get any nice source information from the AST nodes, which makes producing good error messages a challenge. I personally find Rust macros to be disappointing. Conclusion # There’s a wide variety of macro systems. The best macro systems: Operate on the AST rather than on a stream of tokens Avoid leaking implementation details through inadvertent variable capture by being hygienic Produce good error messages that are in terms of the caller’s context (Bonus) have good phase separation to enforce clear separation between complex macro systems

0 views