Latest Posts (13 found)

What Dynamic Typing Is For

Unplanned Obsolescence is a blog is about writing maintainable, long-lasting software. It also frequently touts—or is, at the very least, not inherently hostile to—writing software in dynamically-typed programming languages. These two positions are somewhat at odds. Dynamically-typed languages encode less information. That’s a problem for the person reading the code and trying to figure out what it does. This is a simplified version of an authentication middleware that I include in most of my web services: it checks an HTTP request to see if it corresponds to a logged-in user’s session. Pretty straightforward stuff. The function gets a cookie from the HTTP request, checks the database to see if that token corresponds to a user, and then returns the user if it does. Line 2 fetches the cookie from the request, line 3 gets the user from the database, and the rest either returns the user or throw an error. There are, however, some problems with this. What happens if there’s no cookie included in the HTTP request? Will it return or an empty string? Will even exist if there’s no cookies at all? There’s no way to know without looking at the implementation (or, less reliably, the documentation). That doesn’t mean there isn’t an answer! A request with no cookie will return . That results in a call, which returns (the function checks for that). is a falsy value in JavaScript, so the conditional evaluates to false and throws an . The code works and it’s very readable, but you have to do a fair amount of digging to ensure that it works reliably. That’s a cost that gets paid in the future, anytime the “missing token” code path needs to be understood or modified. That cost reduces the maintainability of the service. Unsurprisingly, the equivalent Rust code is much more explicit. In Rust, the tooling can answer a lot more questions for me. What type is ? A simple hover in any code editor with an LSP tells me, definitively, that it’s . Because it’s Rust, you have to explicitly check if the token exists; ditto for whether the user exists. That’s better for the reader too: they don’t have to wonder whether certain edge cases are handled. Rust is not the only language with a strict, static typing. At every place I’ve ever worked, the longest-running web services have all been written in Java. Java is not as good as Rust at forcing you to show your work and handle edge cases, but it’s much better than JavaScript. Putting aside the question of which one I prefer to write, if I find myself in charge a production web service that someone else wrote, I would much prefer it to be in Java or Rust than JavaScript or Python. Conceding that, ceteris paribus , static typing is good for software maintainability, one of the reasons that I like dynamically-typed languages is that they encourage a style I find important for web services in particular: writing to the DSL. A DSL (domain-specific language) is programming language that’s designed for a specific problem area. This is in contrast to what we typically call “general-purpose programming languages” (e.g. Java, JavaScript, Python, Rust), which can reasonably applied to most programming tasks. Most web services have to contend with at least three DSLs: HTML, CSS, and SQL. A web service with a JavaScript backend has to interface with, at a minimum , four programming languages: one general-purpose and three DSLs. If you have the audacity to use something other than JavaScript on the server, then that number goes up to five, because you still need JavaScript to augment HTML. That’s a lot of languages! How are we supposed to find developers who can do all this stuff ? The answer that a big chunk of the industry settled on is to build APIs so that the domains of the DSLs can be described in the general-purpose programming language. Instead of writing HTML… …you can write JSX, a JavaScript syntax extension that supports tags. This has the important advantage of allowing you to include dynamic JavaScript expressions in your markup. And now we don’t have to kick out to another DSL to write web pages. Can we start abstracting away CSS too? Sure can! This example uses styled-components . This is a tactic I call “expanding the bounds” of the programming language. In an effort to reduce complexity, you try to make one language express everything about the project. In theory, this reduces the number of languages that one needs to learn to work on it. The problem is that it usually doesn’t work. Expressing DSLs in general-purpose programming syntax does not free you from having to understand the DSL—you can’t actually use styled-components without understanding CSS. So now a prospective developer has to both understand CSS and a new CSS syntax that only applies to the styled-components library. Not to mention, it is almost always a worse syntax. CSS is designed to make expressing declarative styles very easy, because that’s the only thing CSS has to do. Expressing this in JavaScript is naturally way clunkier. Plus, you’ve also tossed the web’s backwards compatibility guarantees. I picked styled-components because it’s very popular. If you built a website with styled-components in 2019 , didn’t think about the styles for a couple years, and then tried to upgrade it in 2023 , you would be two major versions behind. Good luck with the migration guide . CSS files, on the other hand, are evergreen . Of course, one of the reasons for introducing JSX or CSS-in-JS is that they add functionality, like dynamic population of values. That’s an important problem, but I prefer a different solution. Instead of expanding the bounds of the general-purpose language so that it can express everything, another strategy is to build strong and simple API boundaries between the DSLs. Some benefits of this approach include: The following example uses a JavaScript backend. A lot of enthusiasm for htmx (the software library I co-maintain) is driven by communities like Django and Spring Boot developers, who are thrilled to no longer be bolting on a JavaScript frontend to their website; that’s a core value proposition for hypermedia-driven development . I happen to like JavaScript though, and sometimes write services in NodeJS, so, at least in theory, I could still use JSX if I wanted to. What I prefer, and what I encourage hypermedia-curious NodeJS developers to do, is use a template engine . This bit of production code I wrote for an events company uses Nunjucks , a template engine I once (fondly!) called “abandonware” on stage . Other libraries that support Jinja -like syntax are available in pretty much any programming language. This is just HTML with basic loops ( ) and data access ( ). I get very frustrated when something that is easy in HTML is hard to do because I’m using some wrapper with inferior semantics; with templates, I can dynamically build content for HTML without abstracting it away. Populating this template in JavaScript is so easy . You just give it a JavaScript object with an field. That’s not particularly special on its own—many languages support serialized key-value pairs. This strategy really shines when you start stringing it together with SQL. Let’s replace that database function call with an actual query, using an interface similar to . I know the above code is not everybody’s taste, but I think it’s marvelous. You get to write all parts of the application in the language best suited to each: HTML for the frontend and SQL for the queries. And if you need to do any additional logic between the database and the template, JavaScript is still right there. One result of this style is that it increases the percentage of your service that is specified declaratively. The database schema and query are declarative, as is the HTML template. The only imperative code in the function is the glue that moves that query result into the template: two statements in total. Debugging is also dramatically easier. I typically do two quick things to narrow down the location of the bug: Those two steps are easy, can be done in production with no deployments, and provide excellent signal on the location of the error. Fundamentally, what’s happening here is a quick check at the two hard boundaries of the system: the one between the server and the client, and the one between the client and the database. Similar tools are available to you if you abstract over those layers, but they are lessened in usefulness. Every web service has network requests that can be inspected, but putting most frontend logic in the template means that the HTTP response’s data (“does the date ever get send to the frontend”) and functionality (“does the date get displayed in the right HTML element?”) can be inspected in one place, with one keystroke. Every database can be queried, but using the database’s native query language in your server means you can validate both the stored data (“did the value get saved?”) and the query (“does the code ask for the right value?”) independent of the application. By pushing so much of the business logic outside the general-purpose programming language, you reduce the likelihood that a bug will exist in the place where it is hardest to track down—runtime server logic. You’d rather the bug be a malformatted SQL query or HTML template, because those are easy to find and easy to fix. When combined with the router-driven style described in Building The Hundred-Year Web Service , you get simple and debuggable web systems. Each HTTP request is a relatively isolated function call: it takes some parameters, runs an SQL query, and returns some HTML. In essence, dynamically-typed languages help you write the least amount of server code possible, leaning heavily on the DSLs that define web programming while validating small amounts of server code via means other than static type checking. To finish, let’s take a look at the equivalent code in Rust, using rusqlite , minjina , and a quasi-hypothetical server implementation: I am again obfuscating some implementation details (Are we storing human-readable dates in the database? What’s that universal result type?). The important part is that this blows. Most of the complexity comes from the need to tell Rust exactly how to unpack that SQL result into a typed data structure, and then into an HTML template. The struct is declared so that Rust knows to expect a for . The derive macros create a representation that minijinja knows how to serialize. It’s tedious. Worse, after all that work, the compiler still doesn’t do the most useful thing: check whether is the correct type for . If it turns out that can’t be represented as a (maybe it’s a blob ), the query will compile correctly and then fail at runtime. From a safety standpoint, we’re not really in a much better spot than we were with JavaScript: we don’t know if it works until we run the code. Speaking of JavaScript, remember that code? That was great! Now we have no idea what any of these types are, but if we run the code and we see some output, it’s probably fine. By writing the JavaScript version, you are banking that you’ve made the code so highly auditable by hand that the compile-time checks become less necessary. In the long run, this is always a bad bet, but at least I’m not writing 150% more code for 10% more compile-time safety. The “expand the bounds” solution to this is to pull everything into the language’s type system: the database schema, the template engine, everything. Many have trod that path; I believe it leads to madness (and toolchain lock-in). Is there a better one? I believe there is. The compiler should understand the DSLs I’m writing and automatically map them to types it understands. If it needs more information—like a database schema—to figure that out, that information can be provided. Queries correspond to columns with known types—the programming language can infer that is of type . HTML has context-dependent escaping rules —the programming language can validate that is being used in a valid element and escape it correctly. With this functionality in the compiler, if I make a database migration that would render my usage of a dependent variable in my HTML template invalid, the compiler will show an error. All without losing the advantages of writing the expressive, interoperable, and backwards-compatible DSLs the comprise web development. Dynamically-typed languages show us how easy web development can be when we ditch the unnecessary abstractions. Now we need tooling to make it just as easy in statically-typed languages too. Thanks to Meghan Denny for her feedback on a draft of this blog. DSLs are better at expressing their domain, resulting in simpler code It aids debugging by segmenting bugs into natural categories The skills gained by writing DSLs are more more transferable CMD+U to View Source - If the missing data is in the HTML, it’s a frontend problem Run the query in the database - If the missing data is in the SQL, it’s a problem with the GET route Language extensions that just translate the syntax are alright by me, like generating HTML with s-expressions , ocaml functions , or zig comptime functions . I tend to end up just using templates, but language-native HTML syntax can be done tastefully, and they are probably helpful in the road to achieving the DX I’m describing; I’ve never seen them done well for SQL. Sqlx and sqlc seem to have the right idea, but I haven’t used either because I to stick to SQLite-specific libraries to avoid async database calls. I don’t know as much about compilers as I’d like to, so I have no idea what kind of infrastructure would be required to make this work with existing languages in an extensible way. I assume it would be hard.

0 views

The Mostly True Naming Rule

Naming things properly is very hard to do , so, as programmers, we come up with little rules to help us. These rules are often inconsistent.

0 views

Why Insist on a Word

A central concept to HTML, and hypertext theory more generally, is something called Representational State Transfer, a.k.a. REST. Over at htmx, a lot of the writing we do is based on REST theory. REST is a widely misunderstood term , and if you point that out to people, you will be told, repeatedly and sometimes quite irately: who cares? REST has a new meaning now—use words the way people understand them and spare us the lecture.

0 views

Building the Hundred-Year Web Service

My UtahJS talk, “Building the Hundred-Year Web Service” , was put online this week! It’s about how to build software infrastructure that lasts a very long time.

0 views

Less htmx is More

It’s been two years since I wrote my first production webservice with htmx . Two years is not a very long time, but early indicators suggest that the software projects I’ve written with htmx are a much better experience for users, and orders of magnitude easier to maintain, than the software projects they replaced. They are likely to remain useful for longer than anything else I’ve ever written (so far). Pretty good! Like any new tool, especially a tool that got popular as quickly as htmx , there are differing schools of thought on how best to use it. My approach—which I believe necessary to achieve the results described above—requires you to internalize something that htmx certainly hints at, but doesn’t enforce: use plain HTML wherever possible .

0 views

The Messy Pile

A couple months ago I was sitting next to Ivy Wong and I saw them working on a dropdown menu so cute that I immediately asked how they did it.

0 views

Who's Afraid of a Hard Page Load?

While I’m not going to settle the Single-Page Web Application (SPA) debate in a blog post, there is one claim about SPAs that routinely goes unchallenged, and it drives me nuts: that users prefer them because of the “modern,” responsive feel.

0 views

The Best "Hello World" in Web Development

Here’s how you make a webpage that says “Hello World” in PHP: Name that file and you’re set. Awesome. Version 1 of our website looks like this: Okay, we can do a little better. Let’s add the HTML doctype and element to make it a legal HTML5 page , an header to give the “Hello World” some heft, and a paragraph to tell our visitor where they are. This is a complete webpage! If you host this single file at one of the many places available to host PHP code, it will show that webpage to everyone who visits your website. Here’s Version 2 : Now let’s make Version 3 comic sans! And baby blue! We just have to add a style tag: Already our webpage has a little bit of personality, and we’ve spent just a couple minutes on it. At each step we could see the website in a browser, and keep adding to it. We haven’t even used any PHP yet—all this is plain old HTML, which is much easier to understand than PHP. This is the best “Hello World” in web development, and possibly all of programming. The thing that first got me interested in PHP in the first place is a comment that Ruby on Rails creator David Heinemeier Hansson made on the “CoRecursive” podcast , about PHP’s influence on Rails: […] the other inspiration, which was from PHP, where you literally could do a one line thing that said, “Print hello world,” and it would show a web page. It would show Hello World on a web page. You just drop that file into the correct folder, and you were on the web […] I think to this day still unsurpassed ease of Hello World. He’s right—this is an unsurpassed ease of Hello World. It is certainly not surpassed by Ruby on Rails, the “Getting Started” guide for which not only requires installing ruby, SQLite, and Rails itself, but also has you run an initialization command ( ) that creates a genuinely shocking number of files and directories : Of course, Rails is doing a lot of stuff for you! It’s setting up a unit test framework, a blog content folder, a database schema, whatever is, and so on. If I wanted all of that, then Rails might be the way to go. But right now I want to make a webpage that says “Hello World” and start adding content to it; I should not have to figure out what a is to do that. As a reminder, here’s the directory structure for our “Hello World” in PHP: My goal here isn’t to rip on Ruby on Rails—although, is a Dockerfile really necessary when you’re just “Getting Started”?—but to highlight a problem that is shared by basically every general-purpose programming language: using Ruby for web development requires a discomfiting amount of scaffolding. Over in the Python ecosystem, one of the first web development frameworks you will encounter is flask , which is a much lighter-weight framework than Rails. In flask, you can also get the “Hello World” down to one file, sort of: Even here, there are a ton of concepts to wrap your head around: you have to understand basic coding constructs like “functions” and “imports”, as well as Python’s syntax for describing these things; you have to figure out how to install Python, how to install Python packages like , and how to run Python environment management tool like (a truly bizarre kludge that Python developers insist isn’t that big of a deal but is absolutely insane if you come from any other modern programming environment); I know we said one file earlier, but if you want this work on a server you’re going to have to document that you installed flask, using a file like ; when you start to add more content you’re going to have to figure out how to do multiline strings; and what’s going with the inscrutable ? If any of these concepts aren’t arranged properly—in your head and in your file—your server will display nothing. By contrast, you don’t have to know a thing about PHP to start writing PHP code. Hell, you barely have to know the command line. If you can manage to install and run a PHP server this file will simply display in your browser. And the file itself: You didn’t have to think about dependencies, or routing, or , or language constructs, or any of that stuff. You’re just running PHP. And you’re on the web. The Time-To-Hello-World test is about the time between when you have an idea and when you are able to see the seed of its expression. That time is crucial—it’s when your idea is in its most mortal state. Years before my friend Morry really knew how to code, he was able to kludge together enough PHP of make a website that tells you whether your IP address has 69 in it. It basically looks like this: You may or may not find that to be a compelling work of art, but it would not exist if spinning up Flask boilerplate were a requirement to do it. And he had taken a CS course in basic Python; the experience of making a website in PHP was just that much better. This turned out to be the first in a long line of internet art projects, some of which we made together and some of which he did on his own . doesmyipaddresshave69init.com is a dumb idea for a website. But sometimes dumb ideas evolve into good ideas, or they teach you something that’s later used to make a good idea, or they just make you chuckle. Or none of the above. The best thing about websites is that you don’t have to justify them to anyone—you can just make them. And PHP is still the fastest way to make a “dynamic” website. I recently made a little invoice generator with a local browser interface for my freelance business. It works great! It’s got a homepage with a list of my generated invoices, a route for making a new one, and routes to view each invoice in a printable format. I don’t find the boilerplate required to make a RESTful web service in NodeJS especially onerous—I have a pretty good system for it at this point. But PHP brings the time-to-hello-world down tremendously. I just don’t think this would have gotten off the ground if I had to setup ExpressJS, copy my router boilerplate, make 2 files for each route (the template and the javascript that serves it), and do all the other things I do to structure web-apps in Node. Instead, I got all that stuff built-in with vanilla PHP, and that will presumably work for as long as PHP does. I didn’t even have to touch the package manager . A lot of people have the attitude that writing vanilla code (and vanilla PHP especially) is never okay because you need secure-by-default frameworks to ensure that you don’t make any security mistakes. It clearly true that if you are building professional software you should be aware of the web security model and make informed decisions about the security model of your application; not everyone is building professional software. Relatedly, one route to becoming is a software professional is to have a delightful experience as a software amateur. I believe that more people should use the internet not just as consumers, but as creators (not of content but of internet ). There is a lot of creativity on the web that can be unlocked by making web development more accessible to artists, enthusiasts, hobbyists, and non-web developers of all types. The softer the learning curve of getting online, the more people will build, share, play, and create there. Softening the learning curve means making the common things easy and not introducing too many concepts until you hit the point where you need them. Beginners and experts alike benefit. Thanks to Nathaniel Sabanski and Al Sweigart for their feedback on a draft of this blog. I wrote this blog at Recurse Center , a terrific programming community that you should check out. My example invoice generator is not meant to be put online, so it doesn’t escape text to prevent XSS attacks, or do the other web security basics . Admittedly, some of PHP’s design decisions really lend themselves to insecure code. For starters, they really need a short echo tag that auto-escapes. At this time, I don’t think I’m going to start defaulting to PHP for client work. I’m very comfortable in JS for general-purpose dynamic programming, and JS has a bunch of other useful web built-ins that PHP does not. I am definitely going to do more web art in PHP, though. I especially like how compact and shareable it can be, which has tremendous value for certain types of code. PHP is also missing a bunch of stuff I consider really important to writing RESTFUL web services, that makes pre-processing your requests close to mandatory. Big ones for me include removing the file extension from the URL, and PUT/DELETE support. Yes, I’m aware of well-known opinion-haver David Heinemeier Hansson’s other opinions. Some of them are right and some of them are wrong. More languages should have a “thing” that they are “for.” Maybe I’ll write about how awk rekindled my love for programming next.

0 views