St Bathans

November 2019 · 1 minute read · photos otago

St Bathans is an old gold mining town in Central Otago. It was established in the 1860s to support miners and is now nearly abandoned. It’s still a popular tourist spot with the blue lakes (photograph below) an iconic scene of the old sluicings.


Thinkpad T440s

November 2019 · 2 minute read · tech laptop thinkpad

I recently picked up a second hand Thinkpad T440s laptop as a replacement for my Macbook Air. The Macbook was stuck with 4gb of RAM with no option to upgrade it. For a few hundred dollars I got a Thinkpad T440s with the following specs

  • Intel i5-4300U dual core CPU @ 1.90GHz
  • 12gb RAM (maximum for the model)
  • 128gb HDD
  • 14” 1600x900 screen

I replaced the hard drive with a new 480gb SATA SSD. There are guides on the internet for replacing the screen with a 1920x1080 panel. You can also replace the keyboard and trackpad. The keyboard is really nice to type on with plenty of travel in the keys. I find the trackpad fine, though the internet consensus is that the trackpad that ships with this model is crap and you’re better off replacing it with a trackpad of a later Thinkpad model.

I’m running the latest version of Xubuntu with tlp installed for better power management. All of the hardware and function keys work out of the box, suspend as well. The batteries last around 3 hours when running a typical development workload of IntelliJ, Firefox, and some Docker containers.

Overall I’m really happy with the machine. For the type of development work I do there really is no need to buy a modern laptop. Well built hardware from 4 years ago does a fantastic job.


Typescript I - Safe Fetch with Decoders

August 2019 · 5 minute read · tech typescript

What happens when we run this Typescript code?

type Person = {
    Name: string;
    Sport: string;
}

async function loadPerson(id: number): Promise<Person> {
    let result = await fetch(`/person/${id}.json`);

    return await result.json() as Person;
}

loadPerson(1).then(person => console.log(person.Name));

person/1.json

{
  "Name": "Ortho Stice",
  "Sport": "Tennis"
}

Exactly what you think - it logs Ortho Stice to the console. Do you think the code is type safe? What do you think as Person is doing? If you read the code as you would C# or Java then you might think the results of result.json() is being cast to type Person, and that the value of result.json() is being checked to see that it adheres to type Person. But that’s not true. Typescript checks types at compile time, not run time. The as Person is us, the programmer, telling the Typescript compiler that result.json() absolutely, 100%, will always return data that adheres to the type Person. This is type assertion is useful if we were gradually converting a Javascript project to Typescript, but is incredibly reckless if we’re telling the compiler about the shape of data remote APIs will return. If you were a new developer to this project and looked at the type annotation of the function you’d assume some checking of the data was taking place.

The above Typescript code compiles to the following Javascript.

"use strict";

async function loadPerson(id) {
    let result = await fetch(`/person/${id}.json`);

    return await result.json();
}

loadPerson(1).then(person => console.log(person.Name));

Now what Typescript does (or doesn’t do) is clear. The types are erased at compile time and we’re just left crossing our fingers that the JSON object returned from the server has an attribute called Name. Not a good look for a production application. The code would run just fine against the following JSON file. Let’s address this with a decoder.

{
  "Name": "Enfield Tennis Academy",
  "Location": "Boston"
}

Decoders

Decoders provide a safe way to turn interpret some generic object (such as the JSON response of an API) into a solid implementation of one of your application types. It’s a common pattern in programming languages with strong type systems such as Elm and F# where the compiler won’t let you do the as Person shenanigans Typescript does. There’s very little additional code required and it’s a nice way to do object validation. We’ll be using the Typescript library ts.data.json, I’m sure others exist. Along side your application types you’ll define a decoder.

type Person = {
    Name: string;
    Sport: string;
}

const PersonDecoder = JsonDecoder.object<Person>(
    {
        Name: JsonDecoder.string,
        Sport: JsonDecoder.string
    },
    "PersonDecoder"
);

There’s some extra cool stuff happening that’s worth pointing out. By passing in the generic type Person we are not only declaring that the decoder will return data which adheres to Person, but it also checks at compile time to make sure that all attributes we define in the decoder (where the values are JsonDecoder.string) actually match up with the attributes in our Person type. The attributes must match 1:1 or else your code won’t compile and you’ll see red squiggles in your IDE. This is a great feature which means your application type and decoder always stay in sync.

To put the decoder into use we add a final step in the response.

async function safeLoadPerson(id: number): Promise<Person> {
    let result = await fetch(`/person/${id}.json`);

    let data = await result.json();

    return await PersonDecoder.decodePromise(data);
}

safeLoadPerson(1).then(person => console.log(person.Name));

If the JSON returned by the server does not match what’s defined in the decoder then we get a nice error message thrown describing the missing key, key present that we didn’t expect, or mismatching types.

<PersonDecoder> decoder failed at key "Sport" with error: undefined is not a valid string

Safe Fetch

We can write a generic wrapper around fetch which lets us explicitly define the type of data we expect the call to return (for the compiler), and a decoder which will decode data to that type.

type Route = string;

async function request<T>(uri: Route, decoder: JsonDecoder.Decoder<T>): Promise<T> {

    let response = await fetch(uri);

    let jsonResponse = await response.json();

    return await decoder.decodePromise(jsonResponse);
}

let uri: Route = "/person/1.json";

request<Person>(uri, PersonDecoder).then(person => console.log(person.Name));

Now we have a function request where:

  1. The data type request returns is checked at compile time.
  2. There is a decoder which explicitly states how to convert some generic JSON object into this type.
  3. At run time the JSON returned from the API is run through the decoder to check adherence to this type.
  4. The decoder stays in sync with our types through compile time checks.

Pretty good! This is a quick example showing how you can use types and decoders to get both compile and run time safety. You’d likely want to add capability to handle errors, for when the remote API returns a different JSON object describing the error. This can be achieved through the oneOf decoder, where decoders get applied in order and the first one that succeeds gets returned.

const PersonOrErrorDecoder = JsonDecoder.oneOf<PersonDecoder, GenericErrorDecoder>(
    [JsonDecoder.object<PersonDecoder>, JsonDecoder.object<GenericErrorDecoder>],
    "PersonOrErrorDecoder"
);

Further reading

  • Structural Typing in Typescript - if you use Typescript and don’t know what structural typing is you should probably read this.
  • Typescript FAQ - describes a lot of the quirks of Typescript and how it differs to other language type systems.
  • Purify - functional programming library for Typescript.
  • ts.data.json - decoder library for Typescript.