I heard that Apple Classical had movie soundtracks so I installed it on my iPad to see if they had the lost album I’ve been looking for. Sadly, they did not. I uninstalled the app after that.

Here’s a free idea for a mindless action movie.

A middle-aged, middle-class, run-of-the-mill guy is walking down a suburban street when he spots a USB drive on the footpath. He’s pretty tech savvy so he knows that he shouldn’t plug in random USB drives he finds outside. He spends the afternoon talking with his friends/family about it, and they tell him he should just throw it away. But curiosity gets the better of him and that evening he plugs it into his computer.

What he finds is a single document with bank login details. He enters those in and finds an account filled with hundred of millions of dollars. He decides to transfer some of that into his own account.

But that account is being watched by law enforcement, and the transfer notifies them and pinpoints his location (maybe he needs to enter a 2FA and they get his location from his mobile). They begin to track him down. Turns out the USB drive was a honeypot that was left there to entrap some form of criminal enterprise, or maybe organised crime.

I don’t know what happens after that but I’d imagine it’ll involve some chase scenes, he finds someone who can help him, etc. Basically, it devolves into the plot of Enemy of the State.

Working title: USB, or the USB Drive, or something punchier. Someone would have to say “USB” somewhere in the film, maybe saying “You S. O. B.” with the O quickly and quietly: you! S! oh B!

Anyway, enjoy. And you’re welcome. πŸ˜„

Overhead someone say, in not so many words, that my UI design for something was sub-par. πŸ˜” Sad, but can’t deny that it’s true to a degree. Acknowledged area of potential development, I guess.

Used ChatGPT a record number of times today (5). Asked a lot of questions about Stripe, like what happens to invoices when a subscription is cancelled. Proved more useful in giving me a direction to explore, rather than providing me a definitive answer, although that might come with time and trust.

Listening to ATP #528 follow-up about putting ChatGPT in front of Siri. It occurred to me that doing so will completely defeat the purpose of Siri being there at all. After all, if you can train GPT to generate the phrases that Siri understands, wouldn’t it also be possible to train it to just produce JSON and send it to a service to execute? You might as well cut out the middle man at that point.

Updates To Dynamo-Browse

In the off-chance that anyone other than me is reading this, it’s likely that there will be no update next week due to the Easter weekend. It may not be the only weekend without an update either. If I find that I didn’t get much done for a particular week, I probably won’t say anything and leave the well to fill up for the next one (although I do have some topics planned for some of those weekends).

In fact, I was not expecting to say much this week, given that work was going through a bit of a crunch period. But the needs of work finally pushed me to add a few features to Dynamo-Browse that were sorely lacking. So that’s where most of this week’s side-project budget went to.

Dynamo-Browse

A lot of work done to query expressions in Dynamo-Browse this week, some of it touching on a few topics I mentioned in a previous update.

I’ve finally got around to finishing the between keyword, so that it works with the query planner and actually produces a DynamoDB query when used with a range[^sort] key. This means no more falling back on table scans. It’s still in a branch as of this post, but I feel much less embarrassed with merging it now, given that this support has been added.

I’ve also made a decision about how to deal with multiple index candidates. Now, when Dynamo-Browse finds that multiple indices can apply for a specific query expression, it will produce an error, requesting you to specify which index to use. This can be done by adding a using suffix to an expression, which specifies how the query should be evaluated:

color="blue" using index("color-item-index")

This can be used at any time to override the index Dynamo-Browse should use, even if only one index candidate was found. It can also be used to force the query to run as a table scan if you don’t want to use an index at all:

color="blue" using scan

Ideally you shouldn’t need to use this suffix that often. The whole purpose of query expressions was to eliminate the need for specifying details of how the query should be evaluated. But we don’t live in a perfect world, and it makes sense adding this to deal with cases where it helps to be specific.

This is also in a branch, but I’m hoping this would be merged soon as well.

Unified Expression Types and Values

A relatively large change made this week was how how values and types are represented within query expressions.

A query expression, once parsed, can be executed in multiple contexts. It can be used to generate a conditional expression for a DynamoDB query or scan, or it can be evaluated within the app itself to produce a result or alter the fields of a DynamoDB record in memory. Each of these contexts have a different set of types the expression operates on. When interpreting the expression in order to produce a result, the expression operates on types that implement types.AttributeType. This fits nicely with what the expression has to work with, which is usually the raw DynamoDB records returned by the Go client. The context used to produces conditional expressions, however, operate on a sort of hybrid type hieararchy, that supports both AttributeType and Go types. This is because to the client used to build the expression accept native Go values, which are sometimes available β€” particularly if they show up in the expression as a literal β€” but sometimes not.

But here’s the problem: I want to be able to add functions to the expression language that can be used in both contexts. I’ll get into what sort of functions I’m thinking of in a minute, but the issue is that with two sets of type hierarchies, I’d have to implement the functions twice.

Another problem is that an evaluation context operating on AttributeTypes feels very inefficient. Numbers are represented as string, and new attribute values are created on the heap. This is probably not too bad in the grand scheme of things, but it would be nice to use native Go values here, even if it’s just to avoid going from strings to numbers constantly.

So I spent most of yesterday trying to fix this. I built a new private Go interface called exprValue and added as implementing subtypes all the types supported by DynamoDB β€” strings, numbers, booleans, lists, etc. Values of these type implement this new interface, and can be converted to Go values or DynamoDB AttributeType values depending on the need.

Most of the evaluation logic was changed to use these types, including the builtin functions, and already I’m seeing some dramatic improvements of what’s possible now. I can define a function once and it can be evaluated both in the evaluation and query building context (provided that it’s only operating on constant values in the query building context). It also addressed some long standing issues I’ve had with the expression language, such as adding support for using a list with the in keyword; something that was not possible before:

pk in $someList

This could potentially be helpful with the “fan-out” one I mentioned a few weeks ago.

This is still early days, but I think it’s been a huge improvement to what was there before. And it’s super satisfying cleaning out all this tech-debt, especially if it means I can add features easily now.

Date Functions In The Expression Language

Now with the new type hierarchy in place, the time has come to start adding functions. What existed to date were the operators and functions that DynamoDB’s conditional expression language supported, and little else. It’s time to go beyond that. And to be honest, this was always the plan, especially given that operators like “begins with” (^=) have been there since the start.

This first thing I’m pondering now is time and date functions. The immediate issue is one of representation: in that I don’t want to settle on any specific one. I’ve seen dates stored as both string date-stamps, usually in ISO 8601, or as integer seconds from the Unix epoch, and it would be good to operate on both of these, in addition to other possible representations, like milliseconds from the Unix epoch, to some other string encoding scheme.

So what I’m thinking is an abstract date-type, probably something backed by Go’s builtin date.Time type. This will neither be a number or a string, but can be converted to one, maybe by using a keyword like as:

now() as "S"   -- represent date as ISO-8601 timestamp
now() as "N"   -- represent date as Unix timestamp
now()          -- error: need to convert it to something

Or maybe some other mechanism.

The idea is that all the builtin functions will operate on this type, but will prevent the user from assuming a particular representation, and will force them to choose one.

I think this is something that will fit nicely with the new type hierarchy system, but for now (pun unintended), I’ll stick with Unix timestamp, just so that I can use something that is easy to implement. But to make it crystal clear that this is temporary, any such functions will have an annoying prefix.

So two new functions were added this week: the _x_now() function, which returns the current time as seconds from the Unix epoch as a number; and the _x_add(), which returns the sum of two numbers. Much like the time functions, I’d like to eventually add arithmetic operators like + to the expression language, but I needed something now and I didn’t have much time to work on that.

Attribute Commands

Finally, a few random notes about commands dealing with attribute values.

The set-attr command can now accept the switch -to, which can be used to set the attribute to the result of a query expression. No more copying-and-pasting values, and operating on them outside Dynamo-Browse. The good thing about this is that the previous attribute values are available in the value expression, so you can use this switch to set the value of attribute based on other attributes in a row. This comes in super handy with bulk changes. I’ve used this to adjust the value of TTLs in a table I’m working in. To set the TTL to be 10 minutes into the future, I just marked the rows, entered the command set-attr -to ttl, and use the expression _x_add(_x_now(), 600). Super useful.

Also, I’ve found a bug where the del-attr command does not work with marked items. It’ll only delete attributes from the item that’s selected (i.e. in pink). I haven’t got around to fixing this, but I hope to very soon.

I think that’s all for this week. Until next time.

What would be a nice addition to the spell-check suggestions menu is a brief (3-5 words) definition of the word. I always find myself choosing the wrong suggestion, and a feature like this would help a lot.

Concept screenshot of the spell-checker menu with definitions

When I first saw John Gruber’s post post about Wavelength, I immediately dismissed it as yet another app I couldn’t use, forgetting that I actually do have devices I can try it on. So I’m trying out the Mac app now. First few minutes of using it: yeah, quite a nice app. We’ll see how we go with it.

Currently reading: The Brand You 50 (Reinventing Work) by Tom Peters πŸ“š

When three different bloggers you follow all write about the author retiring, you pay attention.

First Posts Of The Day

It’s bit strange how the first post of the day can always feel like the hardest to get out. Every one after it is so much easier to write.

I wonder if it’s because when faced with an empty text-box, there are these grand plans about what I’m going to write, as if everyone reading this is hanging on my every word: it’ll be my masterpiece of wit, inspiration, and insightfulness that will spread far and wide and blow the minds of everrryyywoonnneee1. Then I write something, and naturally it falls far short of these expectations: mundane, unimportant, already said before2.

Then I say to myself, “ah well, at least it’s written down.” And with that, the expected level of quality for anything else that day has been set.

So, this is today’s first post. More might come, probably along the same level of importance as this one. At least until the tomorrow, when the cycle starts again.


  1. Another possibility is that I feel I need to write something at the same level of quality of those that I read. That’s probably not a bad feeling to have; but, at least for me, it can get in the way of writing anything at all that day. ↩︎

  2. And lets not forget the bad spelling and grammar I failed to catch. ↩︎

Completely forgot how to commute properly. This is the second day this week I forget to bring my umbrella on a day with forecasted rain. I got lucky last time. Today, not so much. 🌧️

Ugh! Mountains of work to do and only a week to do it all. Gonna be a bird-by-bird sort of day today. But first… coffee (well, coffee number two actually).

Left work late and now caught in a train suspension due to an accident (someone got hit by a train). So… dining out this evening.

Learnt a lot about digital video today. Fascinating stuff. Didn’t realise that frame rates can be non-integers (59.xx FPS). Turns out it’s a legacy of analogue TV, where they try to squeeze colour into bandwidth originally designed for a B&W signal. Shoehorns all the way down I guess.

There are nice things about having separate IDEs for different languages β€” GoLand for Go, WebStorm for HTML+JS, etc. β€” but I can see the advantages of using a single one for everything. I was trying to resolve merge conflicts with my customised GoLand key-bindings and I was getting confused as they weren’t working. Turns out the reason was that I was actually using WebStorm, not GoLand.

The two IDEs are so alike I wonder if things like key-bindings shouldn’t just apply to all installed IDEs on a system. I guess since JetBrains is working on a brand new IDE it probably doesn’t matter at this stage.

Love hearing from successful bloggers who’ve recently celebrated a major milestone (Manton, Ben Thompson, Kottke) that they thought that they were late to the party. Just shows that when you start is less important than just keeping at it.

Got a small envelope with a US stamp delivered today. No idea what it was. Certainly wasn’t expecting anything.

Small envelope on a table with a US stamp

(opens it up) Ah, my Incomparable membership notebook has arrived. How cool! I completely forgot about this, which makes it the best sort of mail to get.

Front of notebook with the Incomparable logo and the title 'Deep Thoughts' on the cover Notebook opened to page of empty grid paper

πŸ”— Google is killing most of Fitbit’s social features today

An amusing thought came to me while I read this: Google has an opportunity to play to it’s strength and act like the assassin for features or services. Don’t want to support something? Get Google to acquire you and inevitably shut you down. It’s such a unique niche that companies should be paying Google for this service.

I’m not a Fitbit user, but I know how it feels to be burned by Google’s obsessive need to shut down things I find useful, so I can understand all the upset over this.

The Android Obsidian app has been playing up recently. Sometimes when I try to make a new note, I loose the ability to add new lines. I have no idea what’s going on, but it’s making me sad. The mobile apps were the reason why I tried Obsidian again. But they’re only useful when they work. πŸ™

Bocce at Carlton Gardens this afternoon. We probably played our fastest game of speed bocce today, clocking in at 13 minutes. Second fastest was 14 minutes, also played this afternoon. Amazing how fast you could go if parking tickets are on the line.