What we’re building

There’s going to be a brief (I promise) wall of text before we get into some code, so entice you into continuing, here’s a video of what we’re building. A type safe implementation at a key access level for several methods that get, set, and update values in deeply nested, complex objects and arrays.

In this article I would like to discuss how to statically type and write a set of complex functions that provide an API with absolute type safety. Through this exercise, my goal is to highlight the strengths and weaknesses of TypeScript as a type system. I may have been in the minority leading up to this point, but I’ve been eagerly looking forward to statically typed JavaScript for a very long time. My first real experience with types and programming behind high school Java and C classes was pretty late. I spent a year writing Swift while building a native iPad application. It definitely changed how I thought about programming and shifted my mental model from scrambling to get things to work toward designing well thought out systems which worked because of a guarantee from the compiler.

I was very interested in, invested a lot of time into, and previously wrote about Facebook’s flow. However, to my disappointment, flow was not Swift. About 2 years ago I then moved over to TypeScript, which was also, unfortunately, not Swift. I also spent some time with OCaml and Rust. What I had previously perceived as shortcomings of Flow and TypeScript, I then came to realize that the ways in which these type systems were designed were meant to compliment the languages they were in service of. That isn’t to stay that they’re perfect. TypeScript (as well as Flow) have weak points. Whereas languages like Rust have limitations in the type system (which are currently being addressed) that prevent typings on async code, TypeScript and Flow have limitations that actually allow unsafe data to be passed through your code rather than preventing the code from being written. This was, I believe, a conscious decision on the part of Facebook and Microsoft to make adoption seem easier upfront. Unfortunately these lax rules leave it up to the developer to manage the effectiveness and safety that usage of types can guarantee.

Typescript’s strength is flexibility.

There are two broad categories of type systems, as described by Javiar Chávarri in this thread of Tweets. The first is Hindley-Milner, which describes the systems of Rust and OCaml, and then there are systems which are based on implicit subtyping. The former requires that every function only return one type. The tl;dr of this approach is that it effectively makes it harder for the engineer in the onset as the type system is harder to appease, but there is a stronger guarantee that there will be no runtime errors, and the toolchain can make better inferences about what the engineer intends to do.

Typescript allows for functions to return many different types, and also allows for an any type which acts as a wildcard but loses any guarantee of safety at the scope of that code. This puts the impetus of creating a sound type system more so in the engineers hands and also allows for a slight margin of error. This might be written off as a weakness, but could also be considered a strength. Let’s take a look at how we can use this flexibility to our advantage.

The three methods I would like to discuss writing are setIn, getIn, and updateIn. These three are excellent candidates to exemplify the strengths of Typescript because they model a complexity that other languages like OCaml and Rust would often struggle with. We need to accomplish the following:

1. Maintain that the keys over which we’re traversing into the object exist.

2. We need to ensure that the type returned from getIn properly references the value of the key we are pointing to.

3. In setIn and updateIn, we need to make sure that the value we are setting is the write type for the key we are referencing.

So let’s look at what these function signatures might look like, starting with the most simple: getIn.