Latest Posts (20 found)
Evan Hahn 5 days ago

Prefer STRICT tables in SQLite

In short: I prefer strict tables in SQLite because they avoid some datatype problems, such as putting text in number columns. SQLite has a feature that I think is underrated: strict tables . Strict tables help enforce rigid typing, preventing mistakes like putting text into integer columns. I like them, and wrote this post to promote their use! To make a strict table, add to the end of its definition. Like this: That’s it! But what does it do? Broadly, strict tables help enforce rigid types, like other SQL engines do. Most significantly, strict tables keep you from inserting the wrong type into a column. For example, SQLite normally lets you put text into an column, but not with strict tables. Personally, I think it’s a mistake to try to put text in an integer column, or vice-versa. I don’t want SQLite to let me make this error! The same validation happens for s, too. Notably, if a value can be losslessly converted, it will still be accepted. For example, the string can be perfectly converted to an integer, so it’s allowed. These two lines are equivalent, even for a strict table: By default, you can create columns with bogus types. For example, all of these work even though they aren’t valid SQLite datatypes: I think these aren’t what the developer intended. Some of these are typos, some of them are misunderstandings of which datatypes SQLite supports , and some are egregious mistakes. Appending to any of these statements makes them error. In my opinion, that’s the correct behavior! Only , , , , , and are allowed. Strict tables also require a column type, so you can’t do . If you still need a column to be flexible, you can use the datatype. As the name suggests, it allows anything—even in a strict table. I haven’t found a use for this, but maybe you will! I prefer strict tables but I must share a few cons. Not everything is better! I think it’s best to use strictness from the start, but that’s not always possible. Unfortunately, I don’t think there’s a way to a table to make it strict. I think you have to copy the data out of the non-strict table into the strict one. Something like this: Note that this could be tricky if the non-strict table has invalid data! For example, if the old data accidentally contains text in an integer column, you’ll get errors when doing the migration. You’ll probably need to clean the data or cast it . You could make a rule for your codebase that all new tables are strict. That might be useful—at least some of your tables are valid! But it might also mean you have inconsistent validation across your tables, which might be more surprising than having weak validation on all tables. It’s up to you to decide whether this is a good fit for you. SQLite has a whole page called “The Advantages Of Flexible Typing” , where they argue that SQLite’s flexible behavior is good, actually. I hesitate to wade into the controversy of static-versus-dynamic, but I disagree in most cases. I’ve personally encountered many bugs where an unexpected data type caused subtle headaches. I’d much rather these mistakes explode loudly. But it’s worth noting that SQLite’s developers seem not to share my preference for strict tables! They point out a few good uses for flexible tables, such as “a pure key-value store” or “a place to store miscellaneous attributes” of different types. They also mention that you might want to keep the invalid data in some cases, like if you’re directly importing a messy CSV and don’t want to lose any data. I still prefer strict tables, but acknowledge there are some reasonable cases for non-strict ones. (There’s also at least one comment in the SQLite source that calls non-strict tables “legacy” , but I trust that less than the official documentation.) SQLite introduced strict tables in version 3.37.0 , released November 2021. If you’re on an older version of SQLite, you can’t use strict tables. It’s worth noting that old versions of SQLite can’t read databases with strict tables. For example, if you create a strict table in the newest version of SQLite and then try to read that database in SQLite 3.36.0 (before strict tables were added), you’ll get an error—even if the strict table is already in the database. Strict tables are theoretically slower because they have to do a little extra work. For example, they check datatypes when doing an insert or update . But in practice, I don’t think this is an issue. I wrote a hacky script that inserted millions of rows into a table with 100 columns, and there was no obvious difference on multiple machines I tried. The file size on disk was also the same. I didn’t test this thoroughly, so maybe there’s something I missed, but I don’t think strict tables present a performance problem. In fact, one might expect better performance because you won’t be accidentally mismatching SQLite’s column affinities. But again, I haven’t tested this. Personally, I think the pros of strict tables outweigh the cons. I generally prefer when types are rigidly enforced. It squashes a class of mistakes, and help enforce good data integrity. They’re not a panacea, but they’re usually easy to add and go a long way. If there’s a SQLite feature you think is underrated, please tell me .

0 views
Evan Hahn 2 weeks ago

Notes from June 2026

Chicago’s weather is pretty lousy most of the year, but when it’s nice, it’s very nice. June blessed the city with dozens of idyllic days. But don’t worry—I still spent most of the time inside on the computer. I launched my first big project at Ghost: automations ! It’s still in beta, but it’s one of the biggest projects I’ve led. If you happen to be a Ghost publisher, please try it out and let me know what you think! For the sixteenth issue of the Taper online magazine, I visualized time by breaking each unit into sixteenths . For example, as I write this, I’m about 6 sixteenths through the hour. Like every Taper submission, my work had to be under 2048 bytes. Generative AI continues to, mostly, be a force for bad in this world: “If AI is going to 10x our productivity across the board, that means that I should be able to produce the same amount of output by midday on Monday that, in the before times, would have taken all week. So can I just take Friday off?” From “Can we have the day off?” . “AI’s PR Problem” has a partial, but scathing, enumeration of many of the problems caused by generative AI. Useful as a reference. New AI resistance strategy: get a religious exemption . I liked this quote from 1976’s Computer Power and Human Reason : “The myth of technological and political and social inevitability is a powerful tranquilizer of the conscience.” The Ladybird browser is no longer accepting patches from the public, due to AI . I think targeted advertising should be illegal, so I loved seeing “Why Don’t We Just Ban Targeted Advertising?” in a major publication ( WIRED ). Homebrew creator Mike McQuaid created a website promoting the practice of doing open source work on company time , a practice I agree with. “Open Source software is not a ‘hobby’ for your spare time. Literally every company you have worked for couldn’t run their business without any OSS. They extract value every hour and then ask maintainers to beg for a Friday afternoon, a donation button or a kind word in an all-hands.” Solidarity with Wiki Workers United. is a proposed environment variable that disables tracking. “Build a web application that works on a playstation portable on a 3G connection—if you do, it will work for all your users, and it will still work 30 years from now.” “At some point you have to actually weave the gossamer. You have to contribute to the infrastructure itself, not just advocate for it.” Hell yeah. After our fascist president shuttered Climate.gov, it was reborn by former members of the team . Good thread about hardware-based attestation . “The purpose of these systems is disallowing people from using hardware and software not approved by Apple or Google. This is wrongly presented as being a security feature.” (I actually think there are legitimate uses for hardware attestation , but not like this.) Mikhail Gorbachev, former Soviet Union leader, was in a Pizza Hut commercial !? Symlinking to is good chaos. How to make an HTTP request from the command line, without . The maximum size of a PDF page is about 150 square kilometers. Here’s what that looks like if it were placed over Germany. Hope you had a good June. “If AI is going to 10x our productivity across the board, that means that I should be able to produce the same amount of output by midday on Monday that, in the before times, would have taken all week. So can I just take Friday off?” From “Can we have the day off?” . “AI’s PR Problem” has a partial, but scathing, enumeration of many of the problems caused by generative AI. Useful as a reference. New AI resistance strategy: get a religious exemption . I liked this quote from 1976’s Computer Power and Human Reason : “The myth of technological and political and social inevitability is a powerful tranquilizer of the conscience.” The Ladybird browser is no longer accepting patches from the public, due to AI . I think targeted advertising should be illegal, so I loved seeing “Why Don’t We Just Ban Targeted Advertising?” in a major publication ( WIRED ). Homebrew creator Mike McQuaid created a website promoting the practice of doing open source work on company time , a practice I agree with. “Open Source software is not a ‘hobby’ for your spare time. Literally every company you have worked for couldn’t run their business without any OSS. They extract value every hour and then ask maintainers to beg for a Friday afternoon, a donation button or a kind word in an all-hands.” Solidarity with Wiki Workers United. is a proposed environment variable that disables tracking. “Build a web application that works on a playstation portable on a 3G connection—if you do, it will work for all your users, and it will still work 30 years from now.” “At some point you have to actually weave the gossamer. You have to contribute to the infrastructure itself, not just advocate for it.” Hell yeah. After our fascist president shuttered Climate.gov, it was reborn by former members of the team . Good thread about hardware-based attestation . “The purpose of these systems is disallowing people from using hardware and software not approved by Apple or Google. This is wrongly presented as being a security feature.” (I actually think there are legitimate uses for hardware attestation , but not like this.) Mikhail Gorbachev, former Soviet Union leader, was in a Pizza Hut commercial !? Symlinking to is good chaos. How to make an HTTP request from the command line, without . The maximum size of a PDF page is about 150 square kilometers. Here’s what that looks like if it were placed over Germany.

0 views
Evan Hahn 1 months ago

"Sixteenth of a year", a 1.8 KiB art piece

As I write this, we’re about 7 sixteenths through 2026, and it’s about 14 sixteenths through the day. For the sixteenth issue of the Taper online magazine, I split time into sixteenths to think about its passage in a different way. The code, which had to be under 2048 bytes, isn’t terribly complex. It does some date math and uses a Go server for minification. If you want, here’s the unminified source code . Go check out all the other entries from this issue ! My favorites include "[SIC]" , “Desperate Measures from a Dying Regime” , and "(un)done" . See also: my previous Taper entry .

0 views
Evan Hahn 1 months ago

Notes from May 2026

My blog turned 16 this month! I did nothing to celebrate, but made some little tools and clicked some links about tech ethics. I published four little tools this month: I also did some work on Helmet, my open source project: And like every month, I wrote a few articles at Zelda Dungeon . I don’t feel I wrote anything special this month, but my colleagues put together a feature about Zelda and mental health which was very affecting! “The vast majority of tech workers, at least those who I have encountered in my many years of reporting, are not vampiric Silicon Valley tech bro caricatures [… They] both like working with tech and ultimately want to see it serve the public good.” From “They just formed the biggest tech worker union in the US. They plan to rein in AI and curb layoffs” . This “love letter to Gnutella” is both an introduction to a P2P protocol and a celebration of the culture around it. From “Affordances for me, but not for thee” : “One of the oddest parts of the AI shift is that people are much more willing to do things for LLMs that they should have been doing for human beings all along.” Accessibility, specifications, documentation, and policies are better codified now. The author calls this “dystopian”, and I agree: our motivation to do this stuff is AI or productivity, not helping our fellow human. “More importantly, whereas accessibility affordances provide new abilities for vulnerable people, an AI affordance provides new abilities for people with power. And that’s probably the heart of it.” Looking forward to being surveilled because I’m an “anti-tech extremist” . I can’t tell you how exciting it was to watch Jira add 2 + 3 . “What can I do to resist AI?” asks the AI Resist List . “Tech companies like Google, Facebook and Microsoft are ignoring data controls mandated under California law, researchers say.” “Your AI Slop Bores Me” presents an interface that looks like an LLM chatbot, but it’s entirely powered by humans. A very cute idea. I’m a very bad “image generator”, at least according to the ratings I received. I continue to be amazed by “Lest We Forget the Horrors: An Unending Catalog of Trump’s Cruelties, Collusions, Corruptions, and Crimes” . It’s so thorough. RIP to a real one: Wikinews is shutting down after 21 years . Hope you had a good May. ZIP Shrinker , a web app that shrinks ZIP files with higher compression ratios A command line tool to do (completely offline) translation Open Link in Unloaded Tab , a Firefox extension to open links without loading them png-cmp , a command line tool to compare PNG pixel data After over a year of quiet maintenance, I released version 8.2.0 with some small new features and documentation updates. In a step toward dropping GitHub, I moved the docs from a GitHub URL to helmet.js.org . “The vast majority of tech workers, at least those who I have encountered in my many years of reporting, are not vampiric Silicon Valley tech bro caricatures [… They] both like working with tech and ultimately want to see it serve the public good.” From “They just formed the biggest tech worker union in the US. They plan to rein in AI and curb layoffs” . This “love letter to Gnutella” is both an introduction to a P2P protocol and a celebration of the culture around it. From “Affordances for me, but not for thee” : “One of the oddest parts of the AI shift is that people are much more willing to do things for LLMs that they should have been doing for human beings all along.” Accessibility, specifications, documentation, and policies are better codified now. The author calls this “dystopian”, and I agree: our motivation to do this stuff is AI or productivity, not helping our fellow human. “More importantly, whereas accessibility affordances provide new abilities for vulnerable people, an AI affordance provides new abilities for people with power. And that’s probably the heart of it.” Looking forward to being surveilled because I’m an “anti-tech extremist” . I can’t tell you how exciting it was to watch Jira add 2 + 3 . “What can I do to resist AI?” asks the AI Resist List . “Tech companies like Google, Facebook and Microsoft are ignoring data controls mandated under California law, researchers say.” “Your AI Slop Bores Me” presents an interface that looks like an LLM chatbot, but it’s entirely powered by humans. A very cute idea. I’m a very bad “image generator”, at least according to the ratings I received. I continue to be amazed by “Lest We Forget the Horrors: An Unending Catalog of Trump’s Cruelties, Collusions, Corruptions, and Crimes” . It’s so thorough. RIP to a real one: Wikinews is shutting down after 21 years .

0 views
Evan Hahn 2 months ago

Make ZIP files smaller with ZIP Shrinker

I built ZIP Shrinker, a little browser tool to shrink ZIP files. It also works with formats that are secretly ZIPs underneath, like APK, EPUB, JAR, and many more. Try it out! At a high level, this tool (1) re-compresses every file in the ZIP archive with higher compression (2) removes all metadata (3) removes entries for directories. ZIP files are typically compressed with an algorithm called Deflate . There are a few tools that can re-compress Deflate data and make it smaller, usually by spending more time on the computation. I took one of these tools, libdeflate , and applied it to each compressed entry in the ZIP. I chose libdeflate because of its performance; alternatives like Zopfli can achieve marginally smaller results but take much longer. I created libdeflate.js , a WebAssembly wrapper for libdeflate, as part of this work. (I always relish my time working with WASM!) Each entry in a ZIP file can contain additional metadata like comments. These aren’t typically used, and if they’re there, my shrinker removes them. This usually doesn’t save too many bytes, but it doesn’t hurt. Removing directories is a slightly spicier decision. Usually, the existence of a file entry implies the existence of the directory it’s inside. For example, implies the existence of the directory. Some ZIPs include separate entries for directories, but because most extractors don’t need them, I remove those. This has the side effect of removing empty directories— let me know if that’s a problem for you. If you want to see how the whole project works, check out the full source code . I tested several ZIPs to see what this tool could do. Some anecdotal results: Not particularly scientific, but useful to see. This proof-of-concept shows that you can make ZIP files smaller without sacrificing backwards compatibility. It could be useful for sending an archive to someone, but could also be useful to reduce bandwidth and server costs. For example, if Project Gutenberg re-compressed all their EPUB books with this method, they might be able to save some money. Of course, ZIP isn’t always the most efficient format. Typically, other archives like can be smaller. But those aren’t backwards-compatible! ZIP also supports compression methods other than Deflate. They’re atypical, but you could use them to achieve a smaller result, too. Give my tool a try if you want a smaller ZIP.

0 views
Evan Hahn 2 months ago

Open Link in Unloaded Tab, a little Firefox extension

In short: I just published Open Link in Unloaded Tab , a little Firefox extension that adds “Open Link in Unloaded Tab” to the right-click context menu. In Firefox, you can unload tabs to save system resources. But there’s no way to open a new tab in the unloaded state…until now! I built a very simple extension that adds a new option to do this. (It even has a cute icon which I paid ~$15 for.) I’ve built one-off extensions before, but this is the first one I’ve submitted to the Firefox Add-ons directory. Download the extension here or check out the source code .

0 views
Evan Hahn 2 months ago

Offline command line translation with TranslateGemma + Ollama

I wrote a simple script that translates text at the command line, completely offline. Here’s an example of how it works on my computer: It combines a few tools: Here’s the pseudocode of how it works: I built this because I couldn’t find anyone else who had done it. It’s written in Deno for my specific needs—for example, it only translates text into your system’s language—but could easily be adapted if you need something else. I like that I can do offline, private, automatic translation. It’s imperfect, but useful for me! Here’s the source code. TranslateGemma , a special-purpose language model for translation Ollama , a tool for running language models locally Efficient Language Detector , a library that detects the language for a piece of text

0 views
Evan Hahn 2 months ago

Notes from April 2026

After a busy March , April was a little quieter. But don’t worry, I still have a bunch of little links for you to click on. GitHub’s uptime hasn’t been great recently. Even though I dislike the Microsoft subsidiary, I wrote “In defense of GitHub’s poor uptime” , which argues that it’s not as bad as folks seems to be saying. See this Lobsters thread for some discussion. Published version 0.2.0 of , my JavaScript library for waiting a very long time . Like every month, I published a few articles over at Zelda Dungeon . We had a slew of shitposts for April Fool’s this year. I think my favorite (not written by me) was “Daily Debate: Do You Think Old Man Was Once Young Man?” . Ghost, my employer, was recognized as a digital public good . “You can’t advertise people out of reacting to their own experiences. This is a fundamental disconnect between how tech people with software brains see the world and how regular people are living their lives.” From “The people do not yearn for automation” . I subscribed to the RSS feed for internet RFCs . Most of this is over my head but it’s neat! This post argues that “any gains we do realize [from LLM coding] are likely going to be incremental and evolutionary, rather than the world-changing revolution many people seem to be expecting.” Matches my experience. Loved this piece about LLM-assisted development, talking about the real pros and cons for a production project. Bookmarked this list of books critical of AI , from 1976 to today. (I’ve only read 1.5 of the books on this list.) This pair of podcast episodes about driverless cars ends with a poignant thought: the haters need to “imagine visions of the future more vivid than the word ’no’.” Same applies to other technologies. “For the last 20 years, employees could accept the intense external criticism and awkward conversations with family and friends about working for a company named after J.R.R. Tolkien’s corrupting all-seeing orb. But a year into Trump’s second term, as Palantir deepens its relationship with an administration many workers fear is wreaking havoc at home, employees are finally raising these concerns internally, as the US’s war on immigrants, war in Iran, and even company-released manifestos has forced them to rethink the role they play in it all.” From “Palantir Employees Are Starting to Wonder if They’re the Bad Guys” . Spotted the Fuck Capitalism game jam . This is the kind of nerdy punk rock stuff I like to see!! Hope you had a good April. GitHub’s uptime hasn’t been great recently. Even though I dislike the Microsoft subsidiary, I wrote “In defense of GitHub’s poor uptime” , which argues that it’s not as bad as folks seems to be saying. See this Lobsters thread for some discussion. Published version 0.2.0 of , my JavaScript library for waiting a very long time . Like every month, I published a few articles over at Zelda Dungeon . We had a slew of shitposts for April Fool’s this year. I think my favorite (not written by me) was “Daily Debate: Do You Think Old Man Was Once Young Man?” . Ghost, my employer, was recognized as a digital public good . “You can’t advertise people out of reacting to their own experiences. This is a fundamental disconnect between how tech people with software brains see the world and how regular people are living their lives.” From “The people do not yearn for automation” . I subscribed to the RSS feed for internet RFCs . Most of this is over my head but it’s neat! This post argues that “any gains we do realize [from LLM coding] are likely going to be incremental and evolutionary, rather than the world-changing revolution many people seem to be expecting.” Matches my experience. Loved this piece about LLM-assisted development, talking about the real pros and cons for a production project. Bookmarked this list of books critical of AI , from 1976 to today. (I’ve only read 1.5 of the books on this list.) This pair of podcast episodes about driverless cars ends with a poignant thought: the haters need to “imagine visions of the future more vivid than the word ’no’.” Same applies to other technologies. “For the last 20 years, employees could accept the intense external criticism and awkward conversations with family and friends about working for a company named after J.R.R. Tolkien’s corrupting all-seeing orb. But a year into Trump’s second term, as Palantir deepens its relationship with an administration many workers fear is wreaking havoc at home, employees are finally raising these concerns internally, as the US’s war on immigrants, war in Iran, and even company-released manifestos has forced them to rethink the role they play in it all.” From “Palantir Employees Are Starting to Wonder if They’re the Bad Guys” . Spotted the Fuck Capitalism game jam . This is the kind of nerdy punk rock stuff I like to see!!

0 views
Evan Hahn 3 months ago

In defense of GitHub's poor uptime

In short: GitHub’s downtime is bad, but uptime numbers can be misleading. It’s not as bad as it looks; more like a D than an F. 99.99% uptime, or “four nines”, is a common industry standard. Four nines of uptime is equivalent to 1.008 minutes of downtime per week. GitHub is not meeting that, and it’s frustrating. Even though they’re owned by Microsoft’s, one of the richest companies on earth, they aren’t clearing this bar. Here are some things people are saying: According to “The Missing GitHub Status Page” , which reports historical uptime better than GitHub’s official source, they’ve had 89.43% uptime over the last 90 days. That’s zero nines of uptime. That implies more than 2.5 hours of downtime every day ! I dislike GitHub and Microsoft, so I shouldn’t be coming to their defense, but I think this characterization is unfair. I’m no mathematician, but let’s do a little math. Let’s say your enterprise has two services: Service A and Service B. Over the last 10 days: 3 of the last 10 days had outages. That’s 70% uptime total. (That’s how the Missing GitHub Status Page calculates it.) GitHub’s status page lists ten services: core Git operations, webhooks, Issues, and more. Sometimes they’re down simultaneously, but usually not. If all ten of those services have 99% uptime and outages don’t overlap, it’d look like GitHub had 90% uptime because some part of GitHub is out 10% of the time. That’s much worse! The numbers look better if outages happen at the same time. For example, if Service A and Service B go down on Saturday and Sunday, you’d have 80% uptime overall instead of 70%. Compared to the previous scenario, Service A is down twice as long, but the uptime number looks better. A downstream effect of this calculation is that your uptime numbers look worse if your services are well-isolated . I think it’s good that Service A doesn’t take down Service B! I think it’s good that a GitHub Packages outage doesn’t take down GitHub Issues! But if all you see is one aggregate uptime number, you might miss that. Things look rosier when you look at features individually. Over the last 90 days, core Git operations have had 98.98% uptime, or about 22 hours where things were broken. That’s still bad, but not as bad as some people are saying. D tier, not F tier. Also, an incident doesn’t mean everything is broken. For example, GitHub recently had an issue where things were slow for users on the west coast of the United States. Not good , but not “everything is broken for all users”. Again, the number doesn’t tell the whole story. I still think GitHub’s uptime is unacceptably low, especially because they’re owned by Microsoft, but I don’t think we’re being honest when we say that GitHub has “zero nines” of availability. To me, it’s more like: they have a bunch of unstable services which cumulatively have horrible uptime, but individually have not-very-good uptime. There are better reasons to dislike these companies. “GitHub appears to be struggling with measly three nines availability” “World’s First Enterprise Solution With Zero Nines Uptime” “Sure, they may have made the uptime worse, but remember what we got in exchange – when it’s up, the UI is slower and buggier.” Service A had one day of downtime. That means it has 90% uptime. Service B had two days of downtime on different days. That means it has 80% uptime.

0 views
Evan Hahn 3 months ago

Notes from March 2026

March always seems to be my life’s busiest month. “The two kinds of error” : in my mind, software errors are divided into two categories: expected and unexpected errors. I finally wrote up this idea I’ve had for a long time. “All tests pass” is a short story about a strange, and sorta sad, experience I had with a coding agent. Inspired by others, I published a disclaimer about how I use generative AI to write this blog . My main rule of thumb: the final product must be word-for-word what I would’ve written without AI, given enough time. And I have discomfort about its use. Built llm-eliza , a plugin for LLM that lets you use the ELIZA chatbot at the command line. I think this is my first satirical software project. (Also the first thing I’ve published to the Python package registry, PyPI.) Found the human.json standard , which is “a protocol for humans to assert authorship of their site content and vouch for the humanity of others.” I added it to my site this month. Scraped Rosetta Code and built a stupid little website that picks a random programming language . At work, I helped with a project to improve the editor for Ghost’s “welcome emails” feature . This month marked the one year anniversary of my first post on Zelda Dungeon . I celebrated by writing more articles, including a treatise the difference between 2D and 3D games and a personal piece about Ocarina of Time . I also wrote my first article that contained an interview , which was a skill I’m totally new to. It’s a small change, but I fixed a little bug in fzf . From a tale about vibe coding : “I’d be embarrassed to show it at a code review. I’d also be embarrassed to admit how many times I failed to ship the ‘clean’ version.” “Claude is the only AI model that has actually been deployed inside classified [American] military systems. So to the extent that AI is having an effect in Iran, it is probably Claude.” From a Hard Fork podcast episode . From “AI’s Enthusiasm Chasm” : “people—well, again, most people—don’t enjoy existing in a strict state of quantification. Pursuits and pastimes—joy—are underpinned by qualitative thought, and those considerations make people less likely to want to involve AI just to get something at a tenth of the cost or five times faster.” “The Cognitive Dark Forest” posits that AI forces us, socially, to close down the open web. “The sheer act of thinking outside the box makes the box bigger.” This post has a good—if incomplete—list of all the downsides of generative AI: perpetuation of bias, erosion of critical thinking, harm to artists, and more. Uber used to be inexpensive because it was subsidized by VC money. Now it’s more costly because they needed to stop losing money. “Don’t get used to cheap AI” posits that the same will happen with AI. Similar ideas are presented in “Is the Future of AI Local?” . From “It’s time to embrace climate conspiracy” : “the actual story of climate change—the one we’ve reported exhaustively—is one about coordinated power, deliberate deception, and a bought-off government that repeatedly acts to promote an industry that is poisoning humans and the environment for profit. It just so happens to be a real conspiracy.” Really liked this short piece about what’s lost when new technology becomes commonplace . Few people today remember what we lost when we switched from candles to lightbulbs. “we don’t need more ram, we need better software” had me whispering “hell yeah” to myself. I’ve long pondered a blog post called “Why I’m afraid of YAML”. This post from a former colleague says it better than I ever could. “Costs of War” highlights the costs, financial and otherwise, of the United States’s wars. The US FBI is buying location data for surveillance , as is our Secret Service . This review of the new Marathon shooter game was surprisingly poignant. “It’s just thoughts and if I don’t get them out, my tummy hurts.” As a Legend of Zelda fan and programmer, I was happy to discover YouTuber Skawo . Their videos explain Zelda quirks by delving into real source code. I especially liked this explanation of why some players were experiencing rumble in a game that shouldn’t have it . The US effectively bans foreign-made routers. Hope you had a good March. “The two kinds of error” : in my mind, software errors are divided into two categories: expected and unexpected errors. I finally wrote up this idea I’ve had for a long time. “All tests pass” is a short story about a strange, and sorta sad, experience I had with a coding agent. Inspired by others, I published a disclaimer about how I use generative AI to write this blog . My main rule of thumb: the final product must be word-for-word what I would’ve written without AI, given enough time. And I have discomfort about its use. Built llm-eliza , a plugin for LLM that lets you use the ELIZA chatbot at the command line. I think this is my first satirical software project. (Also the first thing I’ve published to the Python package registry, PyPI.) Found the human.json standard , which is “a protocol for humans to assert authorship of their site content and vouch for the humanity of others.” I added it to my site this month. Scraped Rosetta Code and built a stupid little website that picks a random programming language . At work, I helped with a project to improve the editor for Ghost’s “welcome emails” feature . This month marked the one year anniversary of my first post on Zelda Dungeon . I celebrated by writing more articles, including a treatise the difference between 2D and 3D games and a personal piece about Ocarina of Time . I also wrote my first article that contained an interview , which was a skill I’m totally new to. It’s a small change, but I fixed a little bug in fzf . From a tale about vibe coding : “I’d be embarrassed to show it at a code review. I’d also be embarrassed to admit how many times I failed to ship the ‘clean’ version.” “Claude is the only AI model that has actually been deployed inside classified [American] military systems. So to the extent that AI is having an effect in Iran, it is probably Claude.” From a Hard Fork podcast episode . From “AI’s Enthusiasm Chasm” : “people—well, again, most people—don’t enjoy existing in a strict state of quantification. Pursuits and pastimes—joy—are underpinned by qualitative thought, and those considerations make people less likely to want to involve AI just to get something at a tenth of the cost or five times faster.” “The Cognitive Dark Forest” posits that AI forces us, socially, to close down the open web. “The sheer act of thinking outside the box makes the box bigger.” This post has a good—if incomplete—list of all the downsides of generative AI: perpetuation of bias, erosion of critical thinking, harm to artists, and more. Uber used to be inexpensive because it was subsidized by VC money. Now it’s more costly because they needed to stop losing money. “Don’t get used to cheap AI” posits that the same will happen with AI. Similar ideas are presented in “Is the Future of AI Local?” . From “It’s time to embrace climate conspiracy” : “the actual story of climate change—the one we’ve reported exhaustively—is one about coordinated power, deliberate deception, and a bought-off government that repeatedly acts to promote an industry that is poisoning humans and the environment for profit. It just so happens to be a real conspiracy.” Really liked this short piece about what’s lost when new technology becomes commonplace . Few people today remember what we lost when we switched from candles to lightbulbs. “we don’t need more ram, we need better software” had me whispering “hell yeah” to myself. I’ve long pondered a blog post called “Why I’m afraid of YAML”. This post from a former colleague says it better than I ever could. “Costs of War” highlights the costs, financial and otherwise, of the United States’s wars. The US FBI is buying location data for surveillance , as is our Secret Service . This review of the new Marathon shooter game was surprisingly poignant. “It’s just thoughts and if I don’t get them out, my tummy hurts.” As a Legend of Zelda fan and programmer, I was happy to discover YouTuber Skawo . Their videos explain Zelda quirks by delving into real source code. I especially liked this explanation of why some players were experiencing rumble in a game that shouldn’t have it . The US effectively bans foreign-made routers.

0 views
Evan Hahn 4 months ago

How I use generative AI on this blog

Inspired by others, I’m publishing how I use generative AI to write this little blog. Generative AI, like any technology, has tradeoffs. I think the cons far outweigh the pros. In other words, the world would be better off without generative AI. Despite this belief, I use it. I’m effectively forced at work, but I also use LLMs to help write this personal blog. I think they can produce better writing if used correctly. Also: I want to be critical of this technology. Specifically, I want to change the minds of “AI maxxers”, not preach to those who already hate it. If I never used this stuff, AI lovers wouldn’t listen to me. These people are more likely to respect criticism from a daily user who’s sympathetic to the benefits. I think there’s space for critique from a user of a technology they wish didn’t exist. I feel discomfort and tension about this, which I hope comes through. With that, let’s get to the specifics. My main rule of thumb: the final product must be word-for-word what I would’ve written without AI , given enough time. I use it in two main ways: I prefer local models that run on my phone and laptop. I’ll keep this post updated. Like a thesaurus. For example, I recently asked, “What’s another way to say that a book was overly positive, not critical of its subject matter?” I used one of its suggestions, “flattering”, in my final draft. Quick brainstorming for specifics. For example, I was listing types of software error in a recent post and asked it for more examples. I plucked one of its many answers—null pointer exceptions—and discarded the rest.

0 views
Evan Hahn 4 months ago

Introducing gzpeek, a tool to parse gzip metadata

In short: gzip streams contain metadata, like the operating system that did the compression. I built a tool to read this metadata. I love reading specifications for file formats. They always have little surprises. I had assumed that the gzip format was strictly used for compression. My guess was: a few bytes of bookkeeping, the compressed data, and maybe a checksum. But then I read the spec . The gzip header holds more than I expected! In addition to two bytes identifying the data as gzip, there’s also: The operating system that did the compression. This was super surprising to me! There’s a single byte that identifies the compressor’s OS: for Windows, for the Amiga, for Unix, and many others I’d never heard of. Compressors can also set for an “unknown” OS. Different tools set this value differently. zlib, the most popular gzip library, changes the flag based on the operating system . (It even defines some OSes that aren’t in the spec, like for BeOS.) Many other libraries build atop zlib and inherit this behavior, such as .NET’s , Ruby’s , and PHP’s . Java’s , JavaScript’s , and Go’s set the OS to “unknown” regardless of operating system. Some, like Zopfli and Apache’s , hard-code it to “Unix” no matter what. All that to say: in practice, you can’t rely on this flag to determine the source OS, but it can give you a hint. Modification time for the data. This can be the time that compression started or the modification time of the file. It can also be set to if you don’t want to communicate a time. This is represented as an unsigned 32-bit integer in the Unix format. That means it can represent any moment between January 1, 1970 and February 7, 2106. I hope we devise a better compression format in the next ~80 years, because we can only represent dates in that range. In my testing, many implementations set this to . A few set it to the current time or the file’s modification time—the command is one of these. FTEXT , a boolean flag vaguely indicating that the data is “probably ASCII text”. When I say vaguely, I mean it: the spec “deliberately [does] not specify the algorithm used to set this”. This is apparently for systems which have different storage formats for ASCII and binary data. In all my testing, nobody sets this flag to anything but . An extra flag indicating how hard the compressor worked. signals that it was compressed with max compression (e.g., ), for the fastest algorithm, and for everything else. In practice, zlib and many others set this correctly per the spec, but some tools hard-code it to . And as far as I can tell, this byte is not used during decompression, so it doesn’t really matter. The original file name . For example, when I run , the name is set to . This field is optional, so many tools don’t set it, but the command line tool does. You can disable that with . A comment . This optional field is seldom used, and many decompressors ignore it. But you could add a little comment if you want. Extra arbitrary data . If the other metadata wasn’t enough, you can stuff whatever you want into arbitrary subfields. Each subfield has a two-byte identifier and then 0 or more bytes of additional info. That’s way more info than I expected! I was intrigued by this metadata and I’ve been wanting to learn Zig , so I wrote gzpeek . gzpeek is a command-line tool that lets you inspect the metadata of gzip streams. Here’s how to read metadata from a gzipped file: It extracts everything I listed above: the operating system, original file name, modification time, and more. I used it a bunch when surveying different gzip implementations. Give it a try, and let me know what gzip metadata you find. The operating system that did the compression. This was super surprising to me! There’s a single byte that identifies the compressor’s OS: for Windows, for the Amiga, for Unix, and many others I’d never heard of. Compressors can also set for an “unknown” OS. Different tools set this value differently. zlib, the most popular gzip library, changes the flag based on the operating system . (It even defines some OSes that aren’t in the spec, like for BeOS.) Many other libraries build atop zlib and inherit this behavior, such as .NET’s , Ruby’s , and PHP’s . Java’s , JavaScript’s , and Go’s set the OS to “unknown” regardless of operating system. Some, like Zopfli and Apache’s , hard-code it to “Unix” no matter what. All that to say: in practice, you can’t rely on this flag to determine the source OS, but it can give you a hint. Modification time for the data. This can be the time that compression started or the modification time of the file. It can also be set to if you don’t want to communicate a time. This is represented as an unsigned 32-bit integer in the Unix format. That means it can represent any moment between January 1, 1970 and February 7, 2106. I hope we devise a better compression format in the next ~80 years, because we can only represent dates in that range. In my testing, many implementations set this to . A few set it to the current time or the file’s modification time—the command is one of these. FTEXT , a boolean flag vaguely indicating that the data is “probably ASCII text”. When I say vaguely, I mean it: the spec “deliberately [does] not specify the algorithm used to set this”. This is apparently for systems which have different storage formats for ASCII and binary data. In all my testing, nobody sets this flag to anything but . An extra flag indicating how hard the compressor worked. signals that it was compressed with max compression (e.g., ), for the fastest algorithm, and for everything else. In practice, zlib and many others set this correctly per the spec, but some tools hard-code it to . And as far as I can tell, this byte is not used during decompression, so it doesn’t really matter. The original file name . For example, when I run , the name is set to . This field is optional, so many tools don’t set it, but the command line tool does. You can disable that with . A comment . This optional field is seldom used, and many decompressors ignore it. But you could add a little comment if you want. Extra arbitrary data . If the other metadata wasn’t enough, you can stuff whatever you want into arbitrary subfields. Each subfield has a two-byte identifier and then 0 or more bytes of additional info.

0 views
Evan Hahn 4 months ago

Track Zelda release anniversaries in your calendar

The original Legend of Zelda came out 40 years ago today. With other birthdays on the horizon, like Twilight Princess ’s 20th in November, I wanted a calendar that showed the anniversary of every Zelda game. So I made one. Subscribe to this URL in your calendar app: Once you do, you’ll get calendar events on the anniversary of each game’s release. For example, you’ll be able to see that the Oracle games turn 25 in less than a week…I feel old. If you want to build this file yourself, I wrote a little Python script that generates an ICS file from a CSV of release dates .

0 views
Evan Hahn 6 months ago

A mental math heuristic to convert between Fahrenheit and Celsius

I sometimes have to convert between Fahrenheit and Celsius. The actual formula is hard to do in my head, but someone once told me a useful approximation: For example, if it’s 12ºC, this heuristic would return 54ºF. (12 × 2) + 30 = 54. The actual amount is not far off: 53.6ºF. To convert the other way: 68ºF becomes 19ºC. (68 − 30) ÷ 2 = 19. Again, this is close to the actual answer of 18ºC. These are pretty close because the numbers we’re using (2 and 30) are pretty close to their counterparts in the real formula (1.8 and 32). This isn’t exact, of course. But it’s come in handy! Now if we could only get the US to use the metric system … To convert from Celsius to Fahrenheit, double it and add 30. To convert from Fahrenheit to Celsius, subtract 30 and halve it (the reverse).

1 views
Evan Hahn 6 months ago

I set all 376 Vim options and I'm still a fool

I set all of Vim’s configuration options. I still feel far from mastery. I first saw someone use Vim during an internship in 2012. I had been coding for many years and I fancied myself pretty good at shortcuts, but I was quickly humbled. I watched in awe as experienced users zipped around the code. A single keystroke could move the cursor halfway across the file to exactly the right spot. Code was ripped apart and reshaped like putty. “ Wow ,” I thought to myself, and probably said out loud. I vowed to master this editor but I was slow. When I wasn’t accidentally opening some unknown menu, I was taking an uneconomical path through the code. I pressed j twenty times instead of running 20j , or manually deleted code inside parenthesis instead of running di( . Sometimes I’d open another text editor to give my mind a break from all the key bindings! Fast-forward to 2025. After tons of practice, I felt much more capable. Code did feel more like putty. I was working closer to the speed of thought. I could get code where I wanted much more quickly. 13 years of practice paid off! But Vim still felt clumsy. I was still accidentally opening menus I didn’t recognize. I would do silly things like converting the whole file to lowercase, or trigger some scary error message. “Surely I shouldn’t be making these mistakes,” I thought. What could be done to finally master this editor? That desire for expertise led me on a quest to set all of Vim’s options . I would make an informed decision about all 376 of Vim’s settings and drop them in my . In other words, I wanted to 100% Vim. Surely, setting every Vim option would make me the fluent expert I wanted to be…right? I pored over every single Vim option and made a decision. What did the option do, and what did I want it to be set to? My goal was to be thorough; leave no stone unturned. I only set the option after I understood it. Eventually, after countless hours, I had done it. I had set every single Vim option. This exercise taught me plenty about day-to-day usage and the editor’s inner workings. I spent a lot of time with the documentation, Vim’s source code, and online forums. Here’s a grab bag of things I learned: How to use external commands. I didn’t realize I could send text from a Vim buffer to an external command, and vice-versa. Now I can write regular programs that operate on stdin. For example, I now frequently use a program to remove “smart quotes” , which I call without leaving the editor. See and for details. Vim’s docs are mixed. I frequently ran the command for something, read the docs, and came away clueless. Thanks to the great people of the internet for so many great explanations! One of the flags stumped me so much that I had to ask my own question on Vi Stack Exchange because nobody had really documented it before. How Vim writes files. When you run :w , it’s just saving the file—how hard could it be? Turns out, it’s complicated! If you’re overwriting a file, Vim (by default) creates a backup of the old version, then overwrites it with the new version. This can help avoid data loss in some situations. But there’s a lot of complexity here: in the fate of that backup file after a successful write , in how the backup switcharoo happens , and where the backup is saved . This is probably irrelevant for most people, but I had to understand the details in order to set every option. The command-line window—which I’d always open by accident—is super useful. The option taught me about the command-line window . I frequently opened this by running q: instead of :q , and didn’t know what I had done. Now I know: a useful way to search and edit the history of previous commands and searches. For example, if I run some long command and make a typo, I can open the command-line window and fix the error using regular Vim keybindings. Digraphs are an obscure feature for typing obscure characters. For example, you can enter “½” in Insert mode with CTRL-K 1 2 . There’s a big list in . I don’t use this much, except for typing fractions, but I use this more than I thought I would. How to set options conditionally. I knew about regular conditionals with things like . But I didn’t know that you could set a Vim option only if it’s supported with code like . Vim and Neovim have more differences than I thought. Among the many changes , I was pleased to learn that pasting is smoother in Neovim , that Q repeats the last recorded macro , it makes running arbitrary code safer , and has a much nicer default color scheme. Vim used to have a different name. Today, “Vim” stands for “Vi IMproved”, an old Unix editor from the 1970s. But it was originally called Vi IMitation ! This is just a sample of my many discoveries. For more, see my heavily-annotated . As of this writing, my configuration file is nearly 2900 lines long. This exercise was fun. It also taught me a lot about the editor I use every day. I definitely feel more skilled! Yet the feeling of awkwardness remains. Even after ~14 years, I still accidentally open the command-line window all the time. I mess up filtering commands, or get lost in the jumplist . I still sometimes press k ten times instead of running 10k . I discovered one thing about myself. The feeling of true Vim fluency—one where every keystroke is exact, I never make mistakes, and I’m exploiting every obscure feature—is a fantasy, at least for me. That’s a comforting constant in this topsy-turvy life: I’ll always have more to learn about Vim. Click here to see the finished product. How to use external commands. I didn’t realize I could send text from a Vim buffer to an external command, and vice-versa. Now I can write regular programs that operate on stdin. For example, I now frequently use a program to remove “smart quotes” , which I call without leaving the editor. See and for details. Vim’s docs are mixed. I frequently ran the command for something, read the docs, and came away clueless. Thanks to the great people of the internet for so many great explanations! One of the flags stumped me so much that I had to ask my own question on Vi Stack Exchange because nobody had really documented it before. How Vim writes files. When you run :w , it’s just saving the file—how hard could it be? Turns out, it’s complicated! If you’re overwriting a file, Vim (by default) creates a backup of the old version, then overwrites it with the new version. This can help avoid data loss in some situations. But there’s a lot of complexity here: in the fate of that backup file after a successful write , in how the backup switcharoo happens , and where the backup is saved . This is probably irrelevant for most people, but I had to understand the details in order to set every option. The command-line window—which I’d always open by accident—is super useful. The option taught me about the command-line window . I frequently opened this by running q: instead of :q , and didn’t know what I had done. Now I know: a useful way to search and edit the history of previous commands and searches. For example, if I run some long command and make a typo, I can open the command-line window and fix the error using regular Vim keybindings. Digraphs are an obscure feature for typing obscure characters. For example, you can enter “½” in Insert mode with CTRL-K 1 2 . There’s a big list in . I don’t use this much, except for typing fractions, but I use this more than I thought I would. How to set options conditionally. I knew about regular conditionals with things like . But I didn’t know that you could set a Vim option only if it’s supported with code like . Vim and Neovim have more differences than I thought. Among the many changes , I was pleased to learn that pasting is smoother in Neovim , that Q repeats the last recorded macro , it makes running arbitrary code safer , and has a much nicer default color scheme. Vim used to have a different name. Today, “Vim” stands for “Vi IMproved”, an old Unix editor from the 1970s. But it was originally called Vi IMitation !

0 views
Evan Hahn 6 months ago

Notes from "On Writing Well"

I’ve been trying to improve my writing so I read On Writing Well by William Zinsser. My main takeaways: Clear thinking is a prerequisite for clear writing. How do you avoid cluttered writing? “The answer is to clear our heads of clutter. Clear thinking becomes clear writing; one can’t exist without the other. It’s impossible for a muddy thinker to write good English.” Reduce scope. Zinsser hammers this point repeatedly. For instance: “Nobody can write a book or an article ‘about’ something. Tolstoy couldn’t write a book about war and peace, or Melville a book about whaling. They made certain reductive decisions about time and place and about individual characters in that time and place—one man pursuing one whale. Every writing project must be reduced before you start to write.” Keep the thesis in mind. “Writers must […] constantly ask: what am I trying to say? Surprisingly often they don’t know. Then they must look at what they have written and ask: have I said it?” I don’t want to write like this guy. I think Zinsser’s writing is dogmatic, verbose, outdated, and above all: not to my taste. But that helps me clarify my own style by showing me what I don’t want to do. And despite all that, I agree with a lot of his recommendations. Even though there were many parts I disliked, I think On Writing Well holds better advice than a writing guide I read last year . I hope my writing improves as a result of reading this book. Clear thinking is a prerequisite for clear writing. How do you avoid cluttered writing? “The answer is to clear our heads of clutter. Clear thinking becomes clear writing; one can’t exist without the other. It’s impossible for a muddy thinker to write good English.” Reduce scope. Zinsser hammers this point repeatedly. For instance: “Nobody can write a book or an article ‘about’ something. Tolstoy couldn’t write a book about war and peace, or Melville a book about whaling. They made certain reductive decisions about time and place and about individual characters in that time and place—one man pursuing one whale. Every writing project must be reduced before you start to write.” Keep the thesis in mind. “Writers must […] constantly ask: what am I trying to say? Surprisingly often they don’t know. Then they must look at what they have written and ask: have I said it?”

0 views
Evan Hahn 6 months ago

Notes from "Bad Company: Private Equity and the Death of the American Dream"

Bad Company: Private Equity and the Death of the American Dream is a book about private equity in the United States. My main takeaway: private equity is bad . I also learned a few other straightforward lessons: This book further cemented a belief I hold: it’s harmful to pursue profit above all else. Private equity has one goal: to maximize shareholder value. As you might imagine, this causes lots of problems. Private equity firms may acquire a business with no intention of keeping it running. It can be more profitable to shutter the business. Leveraged buyouts dramatically lower the risk to the private equity buyer. Government subsidies and laws often make it easier for private equity firms to operate.

0 views
Evan Hahn 7 months ago

How I implemented relative imports with Pyodide

I was recently playing with Pyodide , the WebAssembly Python runtime. I wanted to have my main code import a utility file. Something like this: This took me awhile to figure out! I’m not convinced I have the best solution , but here’s what I did: Here’s what my JavaScript loader code looked like: This worked for me, but I wish there were a cleaner solution. Maybe Pyodide has a way to hook into ? If you have a better solution, please reach out ! Fetch with . Save it to Pyodide’s virtual file system. Run the main code!

0 views
Evan Hahn 8 months ago

Experiment: making TypeScript immutable-by-default

I like programming languages where variables are immutable by default. For example, in Rust , declares an immutable variable and declares a mutable one. I’ve long wanted this in other languages, like TypeScript, which is mutable by default—the opposite of what I want! I wondered: is it possible to make TypeScript values immutable by default? My goal was to do this purely with TypeScript, without changing TypeScript itself. That meant no lint rules or other tools. I chose this because I wanted this solution to be as “pure” as possible…and it also sounded more fun. I spent an evening trying to do this. I failed but made progress! I made arrays and s immutable by default, but I couldn’t get it working for regular objects. If you figure out how to do this completely, please contact me —I must know! TypeScript has built-in type definitions for JavaScript APIs like and and . If you’ve ever changed the or options in your TSConfig, you’ve tweaked which of these definitions are included. For example, you might add the “ES2024” library if you’re targeting a newer runtime. My goal was to swap the built-in libraries with an immutable-by-default replacement. The first step was to stop using any of the built-in libraries. I set the flag in my TSConfig, like this: Then I wrote a very simple script and put it in : When I ran , it gave a bunch of errors: Progress! I had successfully obliterated any default TypeScript libraries, which I could tell because it couldn’t find core types like or . Time to write the replacement. This project was a prototype. Therefore, I started with a minimal solution that would type-check. I didn’t need it to be good! I created and put the following inside: Now, when I ran , I got no errors! I’d defined all the built-in types that TypeScript needs, and a dummy object. As you can see, this solution is impractical for production. For one, none of these interfaces have any properties! isn’t defined, for example. That’s okay because this is only a prototype. A production-ready version would need to define all of those things—tedious, but should be straightforward. I decided to tackle this with a test-driven development style. I’d write some code that I want to type-check, watch it fail to type-check, then fix it. I updated to contain the following: This tests three things: When I ran , I saw two errors: So I updated the type in with the following: The property accessor—the line—tells TypeScript that you can access array properties by numeric index, but they’re read-only. That should make possible but impossible. The method definition is copied from the TypeScript source code with no changes (other than some auto-formatting). That should make it possible to call . Notice that I did not define . We shouldn’t be calling that on an immutable array! I ran again and…success! No errors! We now have immutable arrays! At this stage, I’ve shown that it’s possible to configure TypeScript to make all arrays immutable with no extra annotations . No need for or ! In other words, we have some immutability by default. This code, like everything in this post, is simplistic. There are lots of other array methods , like and and ! If this were made production-ready, I’d make sure to define all the read-only array methods . But for now, I was ready to move on to mutable arrays. I prefer immutability, but I want to be able to define a mutable array sometimes. So I made another test case: Notice that this requires a little extra work to make the array mutable. In other words, it’s not the default. TypeScript complained that it can’t find , so I defined it: And again, type-checks passed! Now, I had mutable and immutable arrays, with immutability as the default. Again, this is simplistic, but good enough for this proof-of-concept! This was exciting to me. It was possible to configure TypeScript to be immutable by default, for arrays at least. I didn’t have to fork the language or use any other tools. Could I make more things immutable? I wanted to see if I could go beyond arrays. My next target was the type, which is a TypeScript utility type . So I defined another pair of test cases similar to the ones I made for arrays: TypeScript complained that it couldn’t find or . It also complained about an unused , which meant that mutation was allowed. I rolled up my sleeves and fixed those errors like this: Now, we have , which is an immutable key-value pair, and the mutable version too. Just like arrays! You can imagine extending this idea to other built-in types, like and . I think it’d be pretty easy to do this the same way I did arrays and records. I’ll leave that as an exercise to the reader. My final test was to make regular objects (not records or arrays) immutable. Unfortunately for me, I could not figure this out. Here’s the test case I wrote: This stumped me. No matter what I did, I could not write a type that would disallow this mutation. I tried modifying the type every way I could think of, but came up short! There are ways to annotate to make it immutable, but that’s not in the spirit of my goal. I want it to be immutable by default! Alas, this is where I gave up. I wanted to make TypeScript immutable by default. I was able to do this with arrays, s, and other types like and . Unfortunately, I couldn’t make it work for plain object definitions like . There’s probably a way to enforce this with lint rules, either by disallowing mutation operations or by requiring annotations everywhere. I’d like to see what that looks like. If you figure out how to make TypeScript immutable by default with no other tools , I would love to know, and I’ll update my post. I hope my failed attempt will lead someone else to something successful. Again, please contact me if you figure this out, or have any other thoughts. Creating arrays with array literals is possible. Non-mutating operations, like and , are allowed. Operations that mutate the array, like , are disallowed. is allowed. There’s an unused there. doesn’t exist.

0 views
Evan Hahn 8 months ago

Fizz Buzz without conditionals or booleans

I recently learned about the Feeling of Computing podcast and listened to the latest episode . One of the hosts challenged listeners to “write Fizz Buzz with no booleans, no conditionals, no pattern matching, or other things that are like disguised booleans .” Here’s my Python solution: This solution is basically three things put together: Create endless cycling sequences of and the same idea for . Combine those two sequences with the numbers 1 through 100 using , to get the following sequence: Convert the number to a string, then “mask” it with any “Fizz”, “Buzz”, or “FizzBuzz” string. For example, returns , and returns . Because of this, my code breaks once you reach 10,000 because the digits start “leaking out” the end of the string. You’ll start seeing results like and . I’m sure there are better ways to do this, but that was my quick solution. How would you solve this problem? Create endless cycling sequences of and the same idea for . Combine those two sequences with the numbers 1 through 100 using , to get the following sequence: Convert the number to a string, then “mask” it with any “Fizz”, “Buzz”, or “FizzBuzz” string. For example, returns , and returns . Because of this, my code breaks once you reach 10,000 because the digits start “leaking out” the end of the string. You’ll start seeing results like and .

0 views