The younger child and I were talking about maths on the school run this morning, and today’s topic was geometry. I was pleased to discover that he’s already got a reasonable
comprehension of the Pythagorean Theorem1:
I was telling him that I was about his age when I first came across it, but in my case I first had a practical, rather than theoretical, impetus to learn it.
It was the 1980s, and I was teaching myself Dr. Logo, Digital Research‘s implementation of the Logo programming language (possibly from this book). One day, I was writing a program to draw an indoor scene, including a window
through which a mountain would be visible. My aim was to produce something like this:
My window was 300 “steps”2
tall by 200 steps wide and bisected in both directions when I came to make my first attempt at the mountain.
And so, naively, starting from the lower-left, I thought I’d need some code like this:
RIGHT 45
FORWARD 100
RIGHT 90
FORWARD 100
But what I ended up with was this:
Hypotenuse? More like need-another-try-potenuse.
I instantly realised my mistake: of course the sides of the mountain would need to be longer so that the peak would reach the mid-point of the window and the far side
would hit its far corner. But how much longer ought it to be.
I intuited that the number I’d be looking for must be greater than 100 but less than 250: these were, logically, the bounds I was working within. 100 would be correct if my
line were horizontal (a “flat” mountain?), and 250 was long enough to go the “long way” to the centrepoint of the window (100 along, and 150 up). So I took a guess at 150 and… it was
pretty close… but still wrong:
I remember being confused and frustrated that the result was so close but still wrong. The reason, of course, is that the relationship between the lengths of the sides of a triangle
don’t scale in a 1:1 way, but this was the first time I found myself having to think about why.
So I found my mother and asked her what I was doing wrong. I’m sure it must have delighted her to dust-off some rarely-accessed knowledge from her own school years and teach me about
Pythagoras’!
The correct answer, of course, is given by:
I so rarely get to use MathML that I had to look up the syntax.
The answer, therefore, is… 141.421 (to three decimal places). So I rounded to 141 and my diagram worked!3
What made this maths lesson from my mother so memorable was that it fed a tangible goal. I had something I wanted to achieve, and I learned the maths that I
needed to get there. And now it’s impermeably etched onto my brain.
I learned the quadratic equation formula and how to perform algebraic integration by rote, and I guarantee that it’s less well-established in my long-term memory than, say, the sine and
cosine rules or how to solve a simultaneous equation because I’ve more-often needed to do those things outside of the classroom!
So I guess the lesson is that I should be trying to keep an eye out for practical applications of maths that I can share with my kids. Real problems that are interesting to solve, to
help build the memorable grounding that latter supports the more-challenging and intangible abstract maths that they may wish to pursue later.
Both kids are sharp young mathematicians, and the younger one seems especially to enjoy it, so feeding that passion feels well-worthwhile. Perhaps I should show them TRRTL.COM so they can try their hand at Logo!
2 Just one way that Logo is/was a cute programming language was its use of “steps” – as
in, turtle-steps – to measure distances. You might approximate them as pixels, but a “step” has meaning even for lines that don’t map linearly to pixels because they’re at wonky
angles, for example.
3 I’d later become unstuck by rounding, while trying to make a more-complex diagram with a
zig-zag pattern running along a ribbon: a small rounding error became compounded over a long time and lead to me being a couple of pixels off where I intended. But that’s another
story.
I nerdsniped myself today when, during a discussion on the potential location of a taekwondo tournament organised by our local martial arts school, somebody claimed that Scotland would be “nearer”
than Ireland.
I don’t dispute that somebody living near me can get to Scotland faster than Ireland, unless they can drive at motorway speeds across Wales… and the Irish Sea. But the word
they used was nearer, and I can be a pedantic arse.
But the question got me thinking:
Could I plot a line across Great Britain, showing which parts are closer to Scotland and which parts are closer to Ireland?
If the England-facing Irish and Scottish borders were completely straight, one could simply extend the borders until they meet, bisect the angle, and we’d be done.
Of course, the borders aren’t straight. They also don’t look much like this. I should not draw maps.
In reality, the border between England and Scotland is a winding mess, shaped by 700 years of wars and treaties1.
Treating the borders as straight lines is hopelessly naive.
Voronoi diagrams are pretty, and cool, and occasionally even useful! This one expands from points, but there’s no reason you can’t expand from a line (line a border!) instead.
My Python skills are pretty shit, but it’s the best tool for the job for geohacking2. And so, through a
combination of hacking, tweaking, and crying, I was able to throw together a script that produces a wonderful
slightly-wiggly line up the country.
The entire island of Ireland is used here to determine boundaries (you can tell because otherwise parts of County Antrim, in Northern Ireland, would be marked as closer to Scotland
than the Republic of Ireland: which they are, of course, but the question was about England!).
Once you’ve bisected England in this way – into parts that are “closer to Ireland” versus parts that are “closer to Scotland”, you start to spot all kinds of interesting
things3.
Like: did you know that the entire subterranean part of the Channel Tunnel is closer to Scotland than it is to Ireland… except for the ~2km closest to the UK exit.
A little further North: London’s six international airports are split evenly across the line, with Luton, Stansted and Southend closer to Scotland… and City, Heathrow and Gatwick closer
to Ireland.
The line then pretty-much bisects Milton Keynes, leaving half its population closer to Scotland and half closer to Ireland, before doing the same to Daventry, before – near Sutton
Coldfield – it drives right through the middle of the ninth hole of the golf course at the Lea Marston Hotel.
Players tee off closer to Ireland and – unless they really slice it – their ball lands closer to Scotland:
In Cannock, it bisects the cemetery, dividing the graves into those on the Scottish half and those in the Irish half:
The line crosses the Welsh border at the River Dee, East of Wrexham, leaving a narrow sliver of Wales that’s technically closer to Scotland than it is to Ireland, running up the
coastline from Connah’s Quay to Prestatyn and going as far inland as Mold before – as is the case in most of Wales – you’re once again closer to Ireland:
If you live in Flint or Mold, ask your local friends whether they live closer to Ireland or Scotland. The answer’s Scotland, and I’m confident that’ll surprise them.
I’d never have guessed that there were any parts of Wales that were closer to Scotland than they were to Ireland, but the map doesn’t lie4
Anyway: that’s how I got distracted, today. And along the way I learned a lot about geodata encoding, a little about Python, and a couple of surprising things about geography5.
2 Or, at least: it’s the one that’s most-widely used and so I could find lots of helpful
StackOverflow answers when I got stuck!
3 Interesting… if you’re specifically looking for some geographical trivia, that is!
4 Okay, the map lies a little. My program was only simple so it plotted
everything on a flat plane, failing to accommodate for Earth’s curvature. The difference is probably marginal, but if you happen to live on or very close to the red line, you might
need to do your own research!
5 Like: Chester and Rugby are closer to Scotland than they are to Ireland, and Harpenden
and Towcester are closer to Ireland than they are to Scotland! Who knew?
Scroll art is a form of ASCII art where a program generates text output in a command line terminal. After the terminal window
fills, it begins to scroll the text upwards and create an animated effect. These programs are simple, beautiful, and accessible as programming projects for beginners. The SAM is a
online collection of several scroll art examples.
Here are some select pieces:
Zig-zag, a simple periodic pattern in a dozen lines of code.
Program output is limited to text (though this could include emoji and color.)
Once printed, text cannot be erased. It can only scroll up.
But these restrictions compel creativity. The benefit of scroll art is that beginner programmers can create scroll art apps with a minimal amount of experience. Scroll art
requires knowing only the programming concepts of print, looping, and random numbers. Every programming langauge has these features, so scroll art can be created in
any programming language without additional steps. You don’t have to learn heavy abstract coding concepts or configure elaborate software libraries.
…
Okay, so: scroll art is ASCII art, except the magic comes from the fact that it’s very long and as your screen scrolls to show it, an animation effect becomes apparent. Does that make
sense?
Anyway, The Scroll Art Museum has lots of them, and they’re much better than mine. I especially love the faux-parallax effect in Skulls and Hearts, created by a “background” repeating pattern being scrolled by a number of lines slightly off from its
repeat frequency while a foreground pattern with a different repeat frequency flies by. Give it a look!
I think of ElonStan420 standing in that exhibit hall, eyeing those cars with disdain because all that time, energy, care, and expression “doesn’t really matter”. Those hand-painted
pinstripes don’t make the car faster or cheaper. Chrome-plated everything doesn’t make it more efficient. No one is going to look under the hood anyway.
…
Don’t read the comments on HackerNews, Adam! (I say this, but I’ve yet to learn not to do so myself, when occasionally my writing escapes from my site and finds its way over there.)
But anyway, this is a fantastic piece about functionalism. Does it matter whether your website has redundant classes defined in the HTML? It renders the same anyway, and odds are good
that nobody will ever notice! I’m with Adam: yes, of course it can matter. It doesn’t have to, but coding is both a science and an art, and
art matters.
…
Should every website be the subject of maximal craft? No, of course not. But in a industry rife with KPI-obsessed, cookie-cutter, vibe-coded, careless slop, we could use
more lowriders.
I’ve grouped these four perspectives, but everything here is a spectrum. Depending on the context or day, you might find yourself at any point on the graph. And I’ve attempted to
describe each perspectively [sic] generously, because I don’t believe that any are inherently good or bad. I find myself switching between perspectives throughout the
day as I implement features, use tools, and read articles. A good team is probably made of members from all perspectives.
Which perspective resonates with you today? Do you also find yourself moving around the graph?
…
An interesting question from Sean McPherson. He sounds like he’s focussed on LLMs for software development, for which I’ve drifted around a little within the left-hand-side of the
graph. But perhaps right now, this morning, you could simplify my feelings like this:
My stance is that AI-assisted coding can be helpful (though the question remains open about whether it’s
“worth it”), so long as you’re not trying to do anything that you couldn’t do yourself, and you know how you’d go about doing it yourself. That is: it’s only useful to
accelerate tasks that are in your “known knowns” space.
As I’ve mentioned: the other week I had a coding AI help me with some code that interacted
with the Google Sheets API. I know exactly how I’d go about it, but that journey would have to start with re-learning the Google Sheets API, getting an API key and giving
it the appropriate permissions, and so on. That’s the kind of task that I’d be happy to outsource to a less-experienced programmer who I knew would bring a somewhat critical eye for
browsing StackOverflow, and then give them some pointers on what came back, so it’s a fine candidate for an AI to step in and give it a go. Plus: I’d be treating the output as “legacy
code” from the get-go, and (because the resulting tool was only for my personal use) I wasn’t too concerned with the kinds of security and accessibility considerations that GenAI can
often make a pig’s ear of. So I was able to palm off the task onto Claude Sonnet and get on with something else in the meantime.
If I wanted to do something completely outside of my wheelhouse: say – “write a program in Fortran to control a robot arm” – an AI wouldn’t be a great choice. Sure, I
could “vibe code” something like that, but I’d have no idea whether what it produced was any good! It wouldn’t even be useful as a springboard to learning how to do that, because I
don’t have the underlying fundamentals in robotics nor Fortran. I’d be producing AI slop in software form: the kind of thing that comes out when non-programmers assume that AI can
completely bridge the gap between their great business idea and a fully working app!
The latest episode of South Park kinda nailed parodying the unrealistic expectations that some folks
seem to put on generative AI: treating it as intelligent or as a friend is unhealthy and dangerous!
They’ll get a prototype that seems to do what you want, if you squint just right, but the hard part of software engineering isn’t making a barebones proof-of-concept! That’s the easy
bit! (That’s why AI can do it pretty well!) The hard bit is making it work all the time, every time; making it scale; making it safe to use; making it maintainable; making it
production-ready… etc.
But I do benefit from coding AI sometimes. GenAI’s good at summarisation, which in turn can make it good at relatively-quickly finding things in a sprawling
codebase where your explanation of those things is too-woolly to use a conventional regular expression search. It’s good at generating boilerplate that’s broadly-like examples its seen
before, which means it can usually be trusted to put together skeleton applications. It’s good at “guessing what comes next” – being, as it is, “fancy autocomplete” – which means it can
be helpful for prompting you for the right parameters for that rarely-used function or for speculating what you might be about to do with the well-named variable you just
created.
Solving problems with LLMs is like solving front-end problems with NPM: the “solution” comes through installing more and more things — adding more and more context, i.e. more and
more packages.
LLM: Problem? Add more context.
NPM: Problem? There’s a package for that.
…
As I’m typing this, I’m thinking of that image of the evolution of the Raptor engine, where it evolved in simplicity:
This stands in contrast to my working with LLMs, which often wants more and more context from me to get to a generative solution:
…
Jim Nielsen speaks to my experience, here. Because a programming LLM is simply taking inputs (all of your code, plus your prompt), transforming it through statistical analysis, and then
producing an output (replacement code), it struggles with refactoring for simplicity unless very-carefully controlled. “Vibe coding” is very much an exercise in adding hacks upon hacks…
like the increasingly-ludicrous epicycles introduced by proponents of geocentrism in its final centuries before the heliocentric model became fully accepted.
This mess used to be how many perfectly smart people imagined the movements of the planets. When observations proved it couldn’t be right, they’d just add more
complexity to catch the edge cases.
I don’t think that AIs are useless as a coding tool, and I’ve successfully used them to good effect on
several occasions. I’ve even tried “vibe coding”, about which I fully agree with Steve Krouse‘s observation that
“vibe code is legacy code”. Being able to knock out something temporary, throwaway, experimental, or for personal use only… while I work on
something else… is pretty liberating.
For example: I couldn’t remember my Google Sheets API and didn’t want to re-learn it from the sprawling documentation site, but wanted a quick personal tool to manipulate such a sheet
from a remote system. I was able to have an AI knock up what I needed while I cooked dinner for the kids, paying only enough attention to check-in on its work. Is it accessible? Is it
secure? Is it performant? Is it maintainable? I can’t answer any of those questions, and so as a professional software engineer I have to reasonably assume the answer to
all of them is “no”. But its only user is me, it does what I needed it to do, and I didn’t have to shift my focus from supervising children and a pan in order to throw it together!
Anyway: Jim hits the nail on the head here, as he so often does.
In my first few weeks at my new employer, my code contributions have added 218 lines of code, deleted 2,663. Only one of my PRs has resulted in
a net increase in the size of their codebases (by two lines).
I need to pick up the pace if I’m going to reach the ultimate goal of deleting ALL of the code within my lifetime. (That’s the ultimate aim, right?)
ArtificialCast is a lightweight, type-safe casting and transformation utility powered by large language models. It allows seamless conversion between strongly typed objects using
only type metadata, JSON schema inference, and prompt-driven reasoning.
Imagine a world where Convert.ChangeType() could transform entire object graphs, infer missing values, and adapt between unrelated types – without manual mapping or
boilerplate.
ArtificialCast makes that possible.
Features
Zero config – Just define your types.
Bidirectional casting – Cast any type to any other.
Schema-aware inference – Auto-generates JSON Schema for the target type.
LLM-powered transformation – Uses AI to “fill in the blanks” between input and output.
Testable & deterministic-ish – Works beautifully until it doesn’t.
…
As beautiful as it is disgusting, this C# is fully-functional and works exactly as described… and yet you really, really should never use it (which its author will tell you, too).
Casting is the process of transforming a variable of one type into one of another. So for example you might cast the number 3 into a string and get
"3" (though of course this isn’t the only possible result: "00000011" might also be a valid representation, depending on the circumstances1).
Casting between complex types defined by developers is harder and requires some work. Suppose you have a User model with attributes like “username”, “full name”, “hashed password”,
“email address” etc., and you want to convert your users into instances of a new model called Customer. Some of the attributes will be the same, some will be absent, and some will be…
different (e.g. perhaps a Customer has a “first name” and “last name” instead of a “full name”, and it’s probably implemented wrong to boot).
The correct approach is to implement a way to cast one as the other.
The very-definitely incorrect approach is to have an LLM convert the data for you. And that’s what this library provides.
…
ArtificialCast is a demonstration of what happens when overhyped AI ideas are implemented exactly as proposed – with no shortcuts, no mocking, and no jokes.
It is fully functional. It passes tests. It integrates into modern .NET workflows. And it is fundamentally unsafe.
This project exists because:
AI-generated “logic” is rapidly being treated as production-ready.
Investors are funding AI frameworks that operate entirely on structure and prompts.
Developers deserve to see what happens when you follow that philosophy to its logical conclusion.
ArtificialCast is the result.
It works. Until it doesn’t. And when it doesn’t, it fails in ways that look like success. That’s the danger.
…
I’ve played with AI in code a few times. There are some tasks it’s very good at, like summarising and explaining (when the developer before you didn’t leave a sufficiency of quality
comments). There are some tasks it can be okay at, with appropriate framing and support: like knowing its way around unfamiliar-to-you but well-documented APIs2.
But if you ask an AI to implement an entire product or even just a significant feature from scratch, unsupervised, you’re at risk of rapidly hitting the realm of Heisenbugs, security
vulnerabilities, and enormous redundancies.
This facetious example – of using AI as a universal typecasting engine – helps hammer that point home, and I love it.
Footnotes
1How to cast basic types isn’t entirely standardised: PHP infamously casts the string "0" as false when it’s coerced into a
boolean, which virtually no other programming language does, for example.
2 The other week, I had a GenAI help me write some code that writes to a Google Sheets
document, because I was fuzzy on the API and knew the AI would pick it up faster than me while I wrote the code “around” it.
Our scanning system wasn’t intended to support this style of notation. Why, then, were we being bombarded with so many ASCII tab ChatGPT screenshots? I was mystified for weeks —
until I messed around with ChatGPT myself and got this:
Turns out ChatGPT is telling people to go to Soundslice, create an account and import ASCII tab in order to hear the audio playback. So that explains it!
…
With ChatGPT’s inclination to lie about the features of a piece of technology, it was
only a matter of time before a frustrated developer actually added a feature that ChatGPT had imagined, just to stop users from becoming dissatisfied when they tried to
use nonexistent tools that ChatGPT told them existed.
And this might be it! This could be the very first time that somebody’s added functionality based on an LLM telling people the feature existed already.
I’ve been in a lot of interviews over the last two or three weeks. But there’s a moment that stands out and that I’ll remember forever as the most-smug I’ve ever felt during an
interview.
There’ll soon be news to share about what I’m going to be doing with the second half of this year…
This particular interview included a mixture of technical and non-technical questions, but a particular technical question stood out for reasons that will rapidly become apparent. It
went kind-of like this:
Interviewer: How would you go about designing a backend cache that retains in memory some number of most-recently-accessed items?
Dan: It sounds like you’re talking about an LRU cache. Coincidentally, I implemented exactly that just the other
week, for fun, in two of this role’s preferred programming languages (and four other languages). I wrote a blog post about my design
choices: specifically, why I opted for a hashmap for quick reads and a doubly-linked-list for constant-time writes. I’m sending you the links to it now: may I talk you through the
diagrams?
Interviewer:
That’s probably the most-overconfident thing I’ve said at an interview since before I started at the Bodleian, 13 years ago. In the interview for
that position I spent some time explaining that for the role they were recruiting for they were asking the wrong questions! I provided some better questions that I felt they
should ask to maximise their chance of getting the best candidate… and then answered them, effectively helping to write my own interview.
Anyway: even ignoring my cockiness, my interview the other week was informative and enjoyable throughout, and I’m pleased that I’ll soon be working alongside some of the people that I
met: they seem smart, and driven, and focussed, and it looks like the kind of environment in which I could do well.
I was updating my CV earlier this week in anticipation of applying for a handful of interesting-looking roles1
and I was considering quite how many different tech stacks I claim significant experience in, nowadays.
There are languages I’ve been writing in every single week for the last 15+ years, of course, like PHP, Ruby, and JavaScript. And my underlying fundamentals are solid.
But is it really fair for me to be able to claim that I can code in Java, Go, or Python: languages that I’ve not used commercially within the last 5-10 years?
What kind of developer writes the same program six times… for a tech test they haven’t even been asked to do? If you guessed “Dan”, you’d be correct!
Obviously, I couldn’t just let that question lie2.
Let’s find out!
I fished around on Glassdoor for a bit to find a medium-sized single-sitting tech test, and found a couple of different briefs that I mashed together to create this:
In an object-oriented manner, implement an LRU (Least-Recently Used) cache:
The size of the cache is specified at instantiation.
Arbitrary objects can be put into the cache, along with a retrieval key in the form of a string. Using the same string, you can get the objects back.
If a put operation would increase the number of objects in the cache beyond the size limit, the cached object that was least-recently accessed (by either a
put or get operation) is removed to make room for it.
putting a duplicate key into the cache should update the associated object (and make this item most-recently accessed).
Both the get and put operations should resolve within constant (O(1)) time.
Add automated tests to support the functionality.
My plan was to implement a solution to this challenge, in as many of the languages mentioned on my CV as possible in a single sitting.
But first, a little Data Structures & Algorithms theory:
The Theory
Simple case with O(n) complexity
The simplest way to implement such a cache might be as follows:
Use a linear data structure like an array or linked list to store cached items.
On get, iterate through the list to try to find the matching item.
If found: move it to the head of the list, then return it.
On put, first check if it already exists in the list as with get:
If it already exists, update it and move it to the head of the list.
Otherwise, insert it as a new item at the head of the list.
If this would increase the size of the list beyond the permitted limit, pop and discard the item at the tail of the list.
It’s simple, elegant and totally the kind of thing I’d accept if I were recruiting for a junior or graduate developer. But we can do better.
The problem with this approach is that it fails the requirement that the methods “should resolve within constant (O(1)) time”3.
Of particular concern is the fact that any operation which might need to re-sort the list to put the just-accessed item at the top
4. Let’s try another design:
Achieving O(1) time complexity
Here’s another way to implement the cache:
Retain cache items in a doubly-linked list, with a pointer to both the head and tail
Add a hash map (or similar language-specific structure) for fast lookups by cache key
On get, check the hash map to see if the item exists.
If so, return it and promote it to the head (as described below).
On put, check the hash map to see if the item exists.
If so, promote it to the head (as described below).
If not, insert it at the head by:
Updating the prev of the current head item and then pointing the head to the new item (which will have the old head item as its
next), and
Adding it to the hash map.
If the number of items in the hash map would exceed the limit, remove the tail item from the hash map, point the tail at the tail item’s prev, and
unlink the expired tail item from the new tail item’s next.
To promote an item to the head of the list:
Follow the item’s prev and next to find its siblings and link them to one another (removes the item from the list).
Point the promoted item’s next to the current head, and the current head‘s prev to the promoted item.
Point the head of the list at the promoted item.
Looking at a plate of pointer-spaghetti makes me strangely hungry.
It’s important to realise that this alternative implementation isn’t better. It’s just different: the “right” solution depends on the use-case5.
The Implementation
That’s enough analysis and design. Time to write some code.
Turns out that if you use enough different languages in your project, GitHub begins to look like itwants to draw a rainbow.
Picking a handful of the more-useful languages on my CV6,
I opted to implement in:
Ruby (with RSpec for testing and Rubocop for linting)
PHP (with PHPUnit for testing)
TypeScript (running on Node, with Jest for testing)
Java (with JUnit for testing)
Go (which isn’t really an object-oriented language but acts a bit like one, amirite?)
Python (probably my weakest language in this set, but which actually ended up with quite a tidy solution)
Naturally, I open-sourced everything if you’d like to see for yourself. It all works, although if you’re actually in need of such a
cache for your project you’ll probably find an alternative that’s at least as good (and more-likely to be maintained!) in a third-party library somewhere!
What did I learn?
This was actually pretty fun! I might continue to expand my repo by doing the same challenge with a few of the other languages I’ve used professionally at some point or
another7.
And there’s a few takeaways I got from this experience –
Lesson #1: programming more languages can make you better at all of them
As I went along, one language at a time, I ended up realising improvements that I could make to earlier iterations.
For example, when I came to the TypeScript implementation, I decided to use generics so that the developer can specify what kind of objects they want to store in the cache,
rather than just a generic Object, and better benefit type-safety. That’s when I remembered that Java supports generics, too, so I went back and used them there as well.
In the same way as speaking multiple (human) languages or studying linguistics can help unlock new ways of thinking about your communication, being able to think in terms of multiple
different programming languages helps you spot new opportunities. When in 2020 PHP 8 added nullsafe operators, union types, and
named arguments, I remember feeling confident using them from day one because those features were already familiar to me from Ruby8, TypeScript9, and Python10,
respectively.
Lesson #2: even when I’m rusty, I can rely on my fundamentals
I’ve applied for a handful of jobs now, but if one of them had invited me to a pairing session on a language I’m rusty on (like Java!) I might’ve felt intimidated.
But it turns out I shouldn’t need to be! With my solid fundamentals and a handful of other languages under my belt, I understand when I need to step away from the code editor and hit
the API documentation. Turns out, I’m in a good position to demo any of my language skills.
I remember when I was first learning Go, I wanted to make use of a particular language feature that I didn’t know whether it had. But because I’d used that feature in Ruby, I knew what
to search for in Go’s documentation to see if it was supported (it wasn’t) and if so, what the syntax was11.
Lesson #3: structural rules are harder to gearshift than syntactic ones
Switching between six different languages while writing the same application was occasionally challenging, but not in the ways I expected.
I’ve had plenty of experience switching programming languages mid-train-of-thought before. Sometimes you just have to flit between the frontend and backend of your application!
But this time around I discovered: changes in structure are apparently harder for my brain than changes in syntax. E.g.:
Switching in and out of Python’s indentation caught me out at least once (might’ve been better if I took the time to install the language’s tools into my text editor first!).
Switching from a language without enforced semicolon line ends (e.g. Ruby, Go) to one with them (e.g. Java, PHP) had me make the compiler sad several times.
This gets even tougher when not writing the language but writing about the language: my first pass at the documentation for the Go version somehow ended up with
Ruby/Python-style #-comments instead of Go/Java/TypeScript-style //-comments; whoops!
I’m guessing that the part of my memory that looks after a language’s keywords, how a method header is structured, and which equals sign to use for assignment versus comparison… are
stored in a different part of my brain than the bit that keeps track of how a language is laid-out?12
Okay, time for a new job
I reckon it’s time I got back into work, so I’m going to have a look around and see if there’s any roles out there that look exciting to me.
If you know anybody who’s looking for a UK-based, remote-first, senior+, full-stack web developer with 25+ years experience and more languages than you can shake a stick at… point them at my CV, would you?
Footnotes
1 I suspect that when most software engineers look for a new job, they filter to the
languages, frameworks, they feel they’re strongest at. I do a little of that, I suppose, but I’m far more-motivated by culture, sector, product and environment than I am by the shape
of your stack, and I’m versatile enough that technology specifics can almost come second. So long as you’re not asking me to write VB.NET.
2 It’s sort-of a parallel to how I decided to check
the other week that my Gutenberg experience was sufficiently strong that I could write standard ReactJS, too.
3 I was pleased to find a tech test that actually called for an understanding of algorithm
growth/scaling rates, so I could steal this requirement for my own experiment! I fear that sometimes, in their drive to be pragmatic and representative of “real work”, the value of a
comprehension of computer science fundamentals is overlooked by recruiters.
4 Even if an algorithm takes the approach of creating a new list with the
inserted/modified item at the top, that’s still just a very-specific case of insertion sort when you think about it, right?
5 The second design will be slower at writing but faster at
reading, and will scale better as the cache gets larger. That sounds great for a read-often/write-rarely cache, but your situation may differ.
6 Okay, my language selection was pretty arbitrary. But if I’d have also come up with
implementations in Perl, and C#, and Elixir, and whatever else… I’d have been writing code all day!
7 So long as I’m willing to be flexible about the “object-oriented” requirement, there are
even more options available to me. Probably the language that I last wrote longest ago would be Pascal: I wonder how much of that I remember?
8 Ruby’s safe navigation/”lonely” operator did the same thing as PHP’s nullsafe operator
since 2015.
9 TypeScript got union types back in 2015, and apart from them being more-strictly-enforced they’re basically identical to
PHP’s.
10 Did you know that Python had keyword arguments since its very first public release
way back in 1994! How did it take so many other interpreted languages so long to catch up?
11 The feature was the three-way comparison or “spaceship operator”, in case you were wondering.
12 I wonder if anybody’s ever laid a programmer in an MRI machine while they code? I’d
be really interested to see if different bits of the brain light up when coding in functional programming languages than in procedural ones, for example!
While working on something else entirely1,
I had a random thought:
Could the :checked and and :has pseudo-classes and the subsequent-sibling (~) selector be combined to perform interactive filtering
without JavaScript?
Turns out, yes. Have a play with the filters on the side of this. You can either use:
“OR” mode, so you can show e.g. “all mammals and carnivores”, or
“AND” mode, so you can show e.g. “all mammals that are carnivores”.
Filter the animals!
(if it doesn’t work right where you are, e.g. in a feed reader, you can view it “standalone”)
There’s nothing particularly complicated here, although a few of the selectors are a little verbose.
First, we set the initial state of each animal. In “OR” mode, they’re hidden, because each selected checkbox is additive. In “AND” mode, they’re shown, because checking a checkbox can
only ever remove an animal from the result set:
The magic of the :has pseudo-class is that it doesn’t change the scope, which means that after checking whether “AND” or “OR” is checked within the #filters,
the #animals container is still an adjacent element.
Next time you’re implementing a filter interface, like this restaurant menu, perhaps ask whether you actually need JavaScript.
Then all we need to do is to use daisy-chain :has to show animals with a particular class if that class is checked in “OR” mode, or to hide animals that don’t have a
particular class in “AND” mode. Here’s what that looks like:
It could probably enjoy an animation effect to make it clearer when items are added and removed2, but that’s a consideration
for another day.
Many developers would be tempted to use JavaScript to implement the client-side version of a filter like this. And in some cases, that might be the right option.
But it’s always worth remembering that:
A CSS solution is almost-always more-performant than a JS one.
A JS solution is usually less-resilient than a CSS one: a CDN failure, unsupported API, troublesome content-blocker or syntax error will typically have a much larger
impact on JavaScript.
For the absolutely maximum compatibility, consider what you can do in plain HTML, or on the server-side, and treat anything on the client-side as progressive
enhancement.
Footnotes
1 The thing I was actually working on when I got distracted was an OAuth provider
implementation for Three Rings, connected with work that took place at this weekend’s hackathon to
(eventually) bring single-sign-on “across” Three Rings CIC’s products. Eventually being the operative word.
2 Such an animation should, of course, be wrapped in a @media
(prefers-reduced-motion: no-preference) media query!
The video below is presented in portrait orientation, because your screen is taller than it is wide.
The video below is presented in landscape orientation, because your screen is wider than it is tall.
The video below is presented in square orientation (the Secret Bonus Square Video!), because your screen has approximately the same width as as its height. Cool!
This is possible (with a single <video> element, and without any Javascript!) thanks to some cool HTML features you might not be aware of, which I’ll briefly explain
in the video. Or scroll down for the full details.
<videocontrols><sourcesrc="squareish.mp4"media="(min-aspect-ratio: 0.95) and (max-aspect-ratio: 1.05)"/><sourcesrc="portrait.mp4"media="(orientation: portrait)"/><sourcesrc="landscape.mp4"/></video>
This code creates a video with three sources: squareish.mp4 which is shown to people on “squareish” viewports, failing that portrait.mp4 which is shown to
people whose viewports are taller than wide, and failing that landscape.mp4 which is shown to anybody else.
That’s broadly-speaking how the video above is rendered. No JavaScript needed.
Browsers only handle media queries on videos when they initially load, so you can’t just tip your phone over or resize the window: you’ll need to reload the page, too. But it works!
Give it a go: take a look at the video in both portrait and landscape modes and let me know what you think1.
Adding adaptive bitrate streaming with HLS
Here’s another cool technology that you might not have realised you could “just use”: adaptive bitrate streaming with HLS!
You’ve used adaptive bitrate streaming before, though you might not have noticed it. It’s what YouTube, Netflix, etc. are doing when your network connection degrades and you quickly get
dropped-down, mid-video, to a lower-resolution version2.
Turns out you can do it on your own static hosting, no problem at all. I used this guide (which has a great
description of the parameters used) to help me:
This command splits the H.264 video landscape.mp4 into three different resolutions: the original “v1” (1920×1080, in my case, with 96kbit audio), “v2” (1280×720, with
96kbit audio), and “v3” (640×360, with 48kbit audio), each with a resolution-appropriate maximum bitrate, and forced keyframes every 48th frame. Then it breaks each of those into HLS
segments (.ts files) and references them from a .m3u8 playlist.
The output from this includes:
Master playlist landscape.m3u8, which references the other playlists with reference to their resolution and bandwidth, so that browsers can make smart choices,
Playlists landscape_0.m3u8 (“v1”), landscape_1.m3u8 (“v2”), etc., each of which references the “parts” of that video,
Directories landscape_0/, landscape_1/ etc., each of which contain
data00.ts, data01.ts, etc.: the actual “chunks” that contain the video segments, which can be downloaded independently by the browser as-needed
Bringing it all together
We can bring all of that together, then, to produce a variable-aspect, adaptive bitrate, HLS-streamed video player… in pure HTML and suitable for static hosting:
<videocontrols><sourcesrc="squareish.m3u8"type="application/x-mpegURL"media="(min-aspect-ratio: 0.95) and (max-aspect-ratio: 1.05)"/><sourcesrc="portrait.m3u8"type="application/x-mpegURL"media="(orientation: portrait)"/><sourcesrc="landscape.m3u8"type="application/x-mpegURL"/></video>
You could, I suppose, add alternate types, poster images, and all kinds of other fancy stuff, but this’ll do for now.
One solution is to also provide the standard .mp4 files as an alternate <source>, and that’s fine I guess, but you lose the benefit of HLS (and
you have to store yet more files). But there’s a workaround:
Polyfill full functionality for all browsers
If you’re willing to use a JavaScript polyfill, you can make the code above work on virtually any device. I gave this a go, here, by:
Adding some JavaScript code that detects affected `<video>` elements and applying the fix if necessary:
// Find all <video>s which have HLS sources:for( hlsVideo of document.querySelectorAll('video:has(source[type="application/x-mpegurl"]), video:has(source[type="vnd.apple.mpegurl"])') ) {
// If the browser has native support, do nothing:if( hlsVideo.canPlayType('application/x-mpegurl') || hlsVideo.canPlayType('application/vnd.apple.mpegurl') ) continue;
// If hls.js can't help fix that, do nothing:if ( ! Hls.isSupported() ) continue;
// Find the best source based on which is the first one to match any applicable CSS media queriesconst bestSource =Array.from(hlsVideo.querySelectorAll('source')).find(source=>window.matchMedia(source.media).matches)
// Use hls.js to attach the best source:const hls =new Hls();
hls.loadSource(bestSource.src);
hls.attachMedia(hlsVideo);
}
It makes me feel a little dirty to make a <video>depend on JavaScript, but if that’s the route you want to go down while we wait for HLS support to become
more widespread (rather than adding different-typed sources) then that’s fine, I guess.
This was a fun dive into some technologies I’ve not had the chance to try before. A fringe benefit of being a generalist full-stack developer is that when you’re “between jobs”
you get to play with all the cool things when you’re brushing up your skills before your next big challenge!
(Incidentally: if you think you might be looking to employ somebody like me, my CV is over there!)
Footnotes
1 There definitely isn’t a super-secret “square” video on this page, though. No
siree. (Shh.)
2 You can tell when you get dropped to a lower-resolution version of a video because
suddenly everybody looks like they’re a refugee from Legoland.
Ok, I’m NOT an immediate fan of “vibe coding” and overusing LLMs in programming. I have a healthy amount of skepticism
about the use of these tools, mostly related to the maintainability of the code, security, privacy, and a dozen other more factors.
But some arguments I’ve seen from developers about not using the tools because it means they “will lose their coding skills” its just bonkers. Especially in a professional context.
Imagine you go to a carpenter, and they say “this will take 2x the time because I don’t use power tools, they make me feel like I’m losing my competence in manual skills”. It’s your
job to deliver software using the most efficient and accurate methods possible.
Sure, it is essential that you keep your skills sharp, but being purposfully less effective in your job to keep them sharp is a red flag. And in an industry made of abstractions to
increase productivity (we’re no longer coding in Assembly last time I checked), this makes even less sense.
/rant
I’m in two minds on this (as I’ve hinted before). The carpenter analogy doesn’t really hold, because the underlying skill of carpentry
is agnostic to whether or not you use power tools: it’s about understanding the material properties of woods, the shapes of joins, the ways structures are strong and where they are
weak, the mathematics and geometry that make design possible… none of which are taken over by power tools.
25+ years ago I wrote most of my Perl/PHP code without an Internet connection. When you wanted to deploy you’d “dial up”, FTP some files around, then check it had worked. In that
environment, I memorised a lot more. Take PHP’s date formatting strings, for example: I used to have them down by heart! And even when I didn’t, I knew approximately the right spot to
flip the right book open to that I’d be able to look it up quickly.
“Always-on” broadband Internet gradually stole that skill from me. It’s so easy for me to just go to the right page on php.net and have the answer I need right in front of me! Nowadays, I depend
on that Internet connection (I don’t even have the book any more!).
A power tool targets a carpenter’s production speed,
not their knowledge-recovery speed.
Will I experience the same thing from my LLM usage, someday?