Category Archives: Software Development

Make Art, not Content

The word “content” has become a catch-all for things that creators create. You hear it most on YouTube, which is weird to me because almost everything the “creators” there do is make videos, so I don’t know why they call it content or why they even call themselves creators. If they needed a catchall, we already had “Art”, which is what I use.

I know “Art” is a stretch, especially for the code, so, even though I use it, I don’t call myself an “Artist”. I usually call myself a “maker” to encompass programmer, writer, podcaster, sketcher, and graphic designer—but there isn’t a good equivalent word for the collection of output. Maybe “Works” or “Work” would be better, but it’s hard to use that word without explaining it. Art is also misleading, but I want to have that discussion.

I’m not always consistent. I call App-o-Mat a “content site”, because that’s what other people would call it. If there’s one thing good about “content”, it’s that people generally know what it means. But I don’t call this site ( a content site. In both my podcast and this blog, I refer to what this is as “art”.

Make Art with Friends is about my search for collaborators, but I think it was also the first time I realized this.

The First HTML Page Still Renders

If you look at the headers from GET’ing, it has these lines:

HTTP/1.1 200 OK
Last-Modified: Thu, 03 Dec 1992 08:37:20 GMT

I believe that it really hasn’t been updated since 1992. Look at the top snippet of the HTML to see why I think this:

<TITLE>The World Wide Web project</TITLE>
<NEXTID N="55">
<H1>World Wide Web</H1>The WorldWideWeb (W3) is a wide-area<A
NAME=0 HREF="WhatIs.html">
hypermedia</A> information retrieval
initiative aiming to give universal
access to a large universe of documents.<P>
Everything there is online about
W3 is linked directly or indirectly
to this document

There are a few obvious differences between this and modern HTML

  1. No enclosing <HTML> tag
  2. <HEADER> instead of <HEAD>
  3. <P> is being used like <BR/>

But, Safari renders it as I think it was intended.

Is this because bad HTML has always been rendered? Or is HTML somehow backwards compatible to this? I suspect the former.

I’ve always been annoyed that browsers render bad HTML. I think it makes it harder to find problems. But, I also love long-lived systems that don’t require human intervention and substitutable versions, so I hope that it’s planned backwards compatibility instead.

Tailwind Final Thoughts

I just finished migrating App-o-Mat from Bootstrap to Tailwind. Here are some things I recommend if you are trying something similar

  1. Don’t try to keep the same markup. It’s likely you can make it a lot simpler.
  2. If you need icons, use Heroicons.
  3. If you use @apply, use it to style tags or to make more utilities.
  4. Generally, Tailwind seems mature enough that what you think of as an edge-case is covered somehow.

The next thing I will try is using it in a React site (with TailwindUI components)

Public Software Playground Projects

I have dozens projects on my drive that are playgrounds for me to try out things I am learning. Each of them is a simple example of a “type” of project (e.g. a React Native app, a React App, a clojure program, a SwiftUI app, etc). I don’t actively maintain them until I need to use them again.

Two of these playground projects are public: Habits and App-o-Mat.

Habits is my first iOS App. I made it in 2008, and it’s the codebase I have continuously worked on the longest. It always builds in the latest Xcode, and it has both Objective-C and pre-CoreData sqlite code. It also has modern Swift.

App-o-Mat is a content site for iOS and watchOS tutorials, which is made in Django with a simple HTML-heavy front-end. I made it in 2014, and it’s my second longest continuously worked on codebase. I regularly migrate it to the lastest Python and Django (it was originally Python 2 and Django 1.0).

Whenever I want to try out new iOS features, I can usually find a place in Habits to do that. When I want to try out a new CSS framework, App-o-Mat is simple enough to try it out and see if I’d like it, but not too simple.

I started converting App-o-Mat to Tailwind a couple of days ago. I have run into enough edge-cases that I am sure that I am giving Tailwind a good look. I have tried out other CSS frameworks in the past (even just trying to update to the lastest Bootstrap) and it hasn’t gone as well as this is going. Tailwind is different enough that you need to try it on something real, and App-o-Mat

Making them public has been an incentive to keep working on them, which means they are playgrounds that are always ready for more play.

Tailwind Seems Nuts Until You Try It

Tailwind makes a complete mess out of your HTML. When you first try to do anything, it seems completely wrong. It feels like just a fancy style attribute.

But maybe all we needed was fancy style attribute after all.

I just finished rewriting the HTML for App-o-Mat‘s home page. I still have a lot to do, but the benefits are obvious:

  1. There is no CSS file (well not one that I write or maintain)
  2. My HTML structure is a lot simpler
  3. My site is in Django, so I have lots of ways to generate HTML partials and components
  4. The default behavior is very responsive and I can easily override what I need to
  5. It is trivial to debug in the browser inspector (the style is in the tag)
  6. They have thought of complex use cases like the typography plugin that can style markdown-generated HTML that you don’t control

Tailwind is the kind of thing that is more obvious if you’ve worked on bigger projects. The arbitrary class names, the disorganization, not knowing which class is bringing in which behavior, the chance that you mess up some other page that is sharing the class—that’s all solved with Tailwind.

The drawback (insane class attributes) is easily solved with the way we normally generate HTML in real projects.

It’s only day two, but it does feel to me like this makes more sense than Material (which is what I have been using for UI in my React projects). It will depend on how good the controls in TailwindUI are and if I can find what I need elsewhere.

My First Few Hours with Tailwind (2023)

I am late to Tailwind, which is how I like it, but Matt Rickard’s post convinced me that Tailwind has “won”, and specifically that it is the heir-apparent to Bootstrap. I’ve been looking to get App-o-Mat off of Bootstrap for a while, so I’m in. Ultimately, I might want to move to Tailwind for all of my projects, but App-o-mat is the simplest, so I’ll start there.

App-o-Mat is a Django site with almost no client-side javascript and a fairly simple design using Bootstrap (not flexbox) for layout. It was made in 2014, and it’s very much of its era.

My first impression was despair and I almost almost gave up right at setup because it needed npm. This made me think that Tailwind was not going to be compatible with a simple, HTML-dominant site.

I googled a bit and found some Django plugins, but they didn’t seem simpler or widely used. I googled a bit harder and eventually found the Tailwind Standalone CLI. This is more like it. It’s easy to adopt and to see exactly what it does, so I still end up with a project and deployment I understand.

The next issue I had is that App-o-Mat does have some forms and navigation where I need some UI Components. So, I ended up at TailwindUI, which I guess I need to buy, because there is no way I want to implement any of these components. I think Tailwind is going to wind up in more of my projects, so this price isn’t that bad.

I started planning out the project and decided that this was also a good time to bring the project into VSCode (from PyCharm). I love PyCharm, but GitHub CoPilot is too important to give up, and I’m so behind in my PyCharm updates, that going to VSCode seems about as easy. All of my other python projects are in VSCode now anyway.

The Most Consequential Clojure I Ever Wrote

In my congratulations to Rich Hickey, I mentioned that clojure influenced my code, but that I never wrote any professionally. I did, however, apply to a job opening at FogCreek/Trello with clojure code. They gated the application process with a programming question.

I call this code the most consequential clojure code I ever wrote because it got me an interview at FogCreek, which ultimately ended up in me working at Trello for almost seven years, during which we were acquired by Atlassian.

Since the question has been publicly answered many times, and is no longer used, I’ll reproduce it here:

Find a string of characters that contains only letters from “acdegilmnoprstuw” such that the hash(the_string) is 910897038977002.

If hash is defined by the following pseudo-code:

hash (s) {
h = 7
letters = "acdegilmnoprstuw"
for(i = 0; i < s.length; i++) {
h = (h * 37 + letters.indexOf(s[i]))
return h

For example, if we were trying to find the string where hash(the_string) was 680131659347, the answer would be “leepadg”.

I chose to use clojure because I didn’t know how big the value of h was going to get and wanted access to clojure’s implementation of BigInteger, which is seamless. All integer math in clojure promotes up to whatever size integer it needs. Once I thought of using clojure, I realized that it would be seen as a fun choice by the developers reviewing the code since FogCreek had a reputation of hiring developers that were interested in weird programming languages (see wasabi).

If you don’t want any hints on how to solve this, stop here.

So, they are asking you to invert the hash() function. In general, hash functions should not be invertible, but this hash is made by using simple lossless integer arithmetic with * and +, which both have inverses (/ and -). Each step of the function is invertible, so the function itself is invertible.

I decided to start with just implementing their pseudocode in clojure.

(defn fchash [arg]
  (let [letters "acdegilmnoprstuw"]
      (loop [h 7 s arg]
          (if-let [c (first s)]
              (recur (+ (* h 37) (.indexOf letters  (int c))) (rest s))

And to make sure this was correct, I evaluated (fchash "leepadg") to make sure I got 680131659347.

There are a few tricks to inverting the code.

First, you need to figure out when the loop is done. You don’t know how long the string is supposed to be, so you need to detect somehow that you are done. For this, note that the hash is originally seeded with the number 7, so we expect that we’ll eventually get back to this number in the inverted version.

Second, you need to figure out what number was added after the hash was multiplied by 7. To get that, you can use mod 37 because the number must be less than the length of the letters string (which is 16) and 16 is less than 37, so using mod gets you the number that was added to the hash after it was multiplied by 37.

That should be enough to attempt an answer (in whatever language). If you want to see what I submitted, look here: Clojure inverse hash.

Congratulations Rich Hickey

Rich Hickey, creator of Clojure, has announced his retirement from commercial software development. It looks like he’ll be still active in clojure development, but as an independent developer.

I met Rich in the 90’s when I took his Advanced C++ continuing education course at NYU. I was running a C development team, and we were adopting C++, so a few of us took the class. The most memorable part was the last few sessions where he described a GUI object-oriented design built around a dynamic object system (ala Self or Javascript) using his functor library.

The next 15 years of my career were dominated by C++ where my code was heavily influenced by what I learned in this class.

In 2007, when I saw his presentation at the NYC Lisp group, I reached out to see if he wanted to present to the Western MA Developer Group. Since Clojure was still relatively new, he was willing to come to present to us.

We had about 30-40 people there. One of our members, Chas Emerick, hosted the event. He went on to be a prolific contributor to the clojure ecosystem and co-author of O’Reilly’s Clojure Programming book.

I helped promote the event by writing my 20 Days of Clojure series. For the time, that was a lot of clojure content.

He came in March 2008 and blew the doors off with an elegant, concurrency-safe ant simulation:

Here is my original write-up of the meeting.

I still keep in touch with many of the developers that were there that day and we still talk about it. I can see the influence in their work.

The most important clojure code I wrote was the code I used to apply to FogCreek/Trello. They gated their application with a programming question, and I answered it in clojure because it looked like I would need something like a BigInteger in my answer, and clojure makes that easy. I also knew that the FogCreek/Trello team liked functional programming.

We might not all have adopted clojure (we opted for F# at Atalasoft and one of our engineers went on to become a Microsoft F# MVP), but our career trajectories were changed by that day when Rich opened our minds to what was possible with modern functional programming.

Thank you Rich and congratulations.

Be Happy When It’s Hard. Be Worried When It’s Easy.

When I was running product development for Atalasoft, I used to love it when a developer was having a hard time with a project. We sold image codecs, and our main competitor was open-source. Our secret sauce was that we could do things they wouldn’t. It was supposed to be hard.

If it were easy, everyone would do it, and it would be free. We wouldn’t have a business.

I think about this a lot when I see what people are doing with Large Language Models. Making an LLM isn’t easy, but Google thinks there’s no moat here for anyone. Still, it’s hard enough because it costs a lot of money, even if you know exactly how to do it.

The part that’s more concerning is what people who use LLM’s are saying. Everyone is so surprised how well it does with basically no work or skill on the part of the user. That’s fine, but then anyone could do it, and the thing they are doing with LLMs isn’t going to accrue value to them.

I think every knowledge worker should be using LLMs in some way, if only to learn about it. It offers enough benefits right now that it can’t be ignored. But, the easier it seems to be that you are getting good results, the more I would be concerned that you won’t be necessary to get those results in the future.