Below are the five most recent posts in my weblog. You can also see a chronological list of all posts, dating back to 1999.



This one was a bit of a surprise hit. Golf Peaks is a Golf-themed puzzle game. The objective is to get the golf ball into the hole, obviously, but how you do that is by choosing from a set of fixed moves (e.g., move one square; jump one square; etc.) and a direction.

This works well with my eldest daughter. She takes one joy-con and I take the other. I typically am responsible for direction, and she hits 'A' to move the ball. We discuss which move to make, which has been a good test of her numeracy.


I've been meaning to write more about my PhD work for absolutely ages, but I've held myself back by wanting to try and keep a narrative running through the blog posts. That's not realistic for a number of reasons so I'm going to just write about different aspects of things without worrying about whether they make sense in the context of recent blog posts or not.

Part of what I am doing at the moment is investigating Template Haskell to see whether it would usefully improve our system implementation. Before I write more about how it might apply to our system, I'll first write a bit about Template Haskell itself.

Template Haskell (TH) is a meta-programming system: you write programs that are executed at compile time and can output code to be spliced into the parent program. The approach used by TH is really nice: you perform your meta-programming in real first-class Haskell, and it integrates really well with the main program.

TH provides two pairs of special brackets. Oxford brackets surrounding any Haskell expression cause the whole expression to be replaced by the result of parsing the expression — an expression tree — which can be inspected and manipulated by the main program:

[| \x -> x + 1 |]

The expression data-type is a series of mutually-recursive data types that represent the complete Haskell grammar. The top-level is Exp, for expression, which has constructors for the different expression types. The above lambda expression is represented as

LamE [VarP x_1]
    (InfixE (Just (VarE x_1))
            (VarE GHC.Num.+)
            (Just (LitE (IntegerL 1))))

Such expressions can be pattern-matched against, constructed, deconstructed etc just like any other data type.

The other bracket type performs the opposite operation: it takes an expression structure and splices it into code in the main program, to be compiled as normal:

λ> 1 + $( litE (IntegerL 1) )

The two are often intermixed, sometimes nested to several levels. What follows is a typical beginner TH meta-program. The standard function fst operators on a 2-tuple and returns the first value. It cannot operate on a tuple of a different valence. However, a meta-program can generate a version of fst specialised for an n-tuple of any n:

genfst n = do
    xs <- replicateM n (newName "x")
    let ntup = tupP (map varP xs)
    [| \ $(ntup) ->  $(varE (head xs)) |]

Used like so

λ> $(genfst 2) (1,2)
λ> $(genfst 3) ('a','b','c')
λ> :t $(genfst 10)
$(genfst 10) :: (a, b, c, d, e, f, g, h, i, j) -> a

That's a high-level gist of how you can use TH. I've skipped over a lot of detail, in particular an important aspect relating to scope and naming, which is key to the problem I am exploring at the moment. Oxford brackets and slice brackets do not operate directly on the simple Exp data-type, but upon an Exp within the Q Monad:

λ> :t [| 1 |]
[| 1 |] :: ExpQ

ExpQ is a synonym for Q Exp. Eagle-eyed Haskellers will have noticed that genfst above was written in terms of some Monad. And you might also have noticed the case discrepancy between the constructor types VarE (Etc) and varE, tupP, varP used in that function definition. These are convenience functions that wrap the relevant constructor in Q. The point of the Q Monad is (I think) to handle name scoping, and avoid unintended name clashes. Look at the output of these simple expressions, passed through runQ:

λ> runQ [| \x -> x |]
LamE [VarP x_1] (VarE x_1)
λ> runQ [| \x -> x |]
LamE [VarP x_2] (VarE x_2)

Those x are not the same x in the context they are evaluated (a GHCi session). And that's the crux of the problem I am exploring. More in a later blog post!


At Red Hat we've just shipped Universal Base Image (UBI) build images for OpenJDK. Download them with your favourite container manager, e.g.:

podman pull
podman pull

UBI, announced a year ago, is an initiative where you can obtain, share and build upon official Red Hat container images without needing a Red Hat subscription. Unlike something like CentOS, they aren't modified in any way (e.g. to remove branding), they're exactly the same base images that Red Hat products are built upon, composed entirely of Open Source software. Your precise rights are covered in the EULA.

I work on the Red Hat OpenJDK container images, which are designed primarily for use with OpenShift. We've been based upon the RHEL base images since inception. Although our containers are open source (of course), we haven't been able to distribute the binary images more widely than to Red Hat customers, until now.

These are based upon the "minimal" flavour base image and add an OpenJDK runtime and development packages (either JDK8 or JDK11, as per the image name) as well as Maven and OpenShift integration scripts that allow the image to be used with Source To Image.

If you give these a try, please let us know what you think! Comment here, on twitter, or file Issues or Pull Requests in the GitHub project.


This is the ninth part in a series of blog posts. The previous post was Amiga floppy recovery project scope. The whole series is available here: Amiga.

It's been a while since I've reported on my Amiga floppy recovery project. With my bulky Philips CRT attached I slowly ground through the process of importing all my floppy disks, which is now done. The majority of disks were imported without errors. Commercial disks were the most likely to fail to import. Possibly I'd have more success with them if I used a different copying technique than X-COPY's default, but my focus was not on the commercial disks.

I have not yet restored the use of my LCD TV with the Amiga. I was waiting to hear back from Amiga Kit about an agreed return, but despite their web store claiming they're still open for business, I haven't been able to get any response to my emails to them since mid-February. I've given up, written off that order and bought an RGB/SCART adaptor elsewhere instead. Meanwhile my bulky CRT has returned to the loft.

The next step is pretty boring: time to check over my spreadsheet of disks, the imported copies of them, make sure everything is accounted for and decide whether to make further attempts for any that failed or write them off. For commercial disks it's almost certainly less effort to track down someone else's rip than to try mine again.

After that I can return to moving the Gotek floppy adaptor inside the Amiga case, as described in the last entry, and start playing.


Continuing a series of blog posts about casual Nintendo Switch games, next in the series is SUPERHOT. Normally £19.99, I picked it up for £13.99 in a sale. That's a little bit more than I would usually pay for a casual game. SUPERHOT first came on my radar because someone I know from a baby group worked on their VR port in some capacity.

Slow-motion buckshot

Slow-motion buckshot

A first-person shooter, SUPERHOT's USP is that time only progresses when you move—well, nearly. Time is slowed to a complete crawl when you are not moving. The game's visual style is very distinctive: Almost everything is a washed out grey or white colour and porcelean-like texture, except weapons and objects you can interact with, which are a matt black, and enemies, which are a bright red. It reminds me a lot of the 1992 Amiga game Robocop 3.

Robocop 3

Robocop 3

The play-style is very reminiscent of the "Bullet Time" sequences in The Matrix—seemingly impossibly overwhelming odds deftly manoeuvred through thanks to superhuman reaction times.

The game has a relatively short campaign of little vignettes, linked together by a cyberpunk narrative. The game is sometimes criticised for the short campaign, but for me that's ideal. And the vignettes being short and quite standalone suits my play requirements very well.

Amiga easter-egg

Amiga easter-egg

The narrative interspersed between the play scenarios is a little bit over-long, and you can spend an unreasonable amount of time bashing buttons to get through it. Despite that it's a moderately interesting story. Once you've beaten the campaign, you can go back and play any of the scenarios again, or try the newly unlocked endless mode. I haven't tried that yet.

The original prototype for the game is a free-to-play in-browser demo, available here. On Windows PC, there's a sequel-of-sorts in the works called MIND CONTROL DELETE with a lot of new features to add replay value.


Older posts are available on the all posts page.