Advanced Typescript: Narrowing & Predicates

In the last part of this series, we talked about how you can infer types in Typescript. In this part, we’ll take a look at a related use-case: making the Typescript compiler happy by narrowing the type of a sum type.

What’s Type Narrowing?

Let’s first describe the problem. In Todoist, you can complete & archive both tasks and sections. Let’s say we model it like this:

type TaskCompletedData = {
    subTasks: Task[]
    showHint: boolean
    cursor?: string

type SectionCompletedData = {
    completedItems: CompletedTask[]
    tasksLeft: number
    showHint: boolean
    cursor?: string

Now it doesn’t really matter what these properties are, I’m just going to use them for illustration, but if we want to build a function to work with a generic completedData variable, it can become hard to “narrow down the type” to what we’re interested in. See for example:

function getCompletedCount(completedData: TaskCompletedData | SectionCompletedData) {
    //how do I make Typescript happy here?

The answer is to use the Javascript in operator to check for existence of properties:

function getCompletedCount(completedData: TaskCompletedData | SectionCompletedData) {
    if ('completedItems' in completedData) {
        return getSectionsCount(completedData)
    } else if ('subTasks' in completedData) {
        return getItemsCount(completedData)
    } else {
        throw `Unexpected value for completedData: ${completedData}`

Why can’t you simply call getSectionsCount direct? That’s because Typescript can’t really guess the type of a variable at compile-time (& there’s no Typescript at runtime). Writing the function in this way means you have adequate guards in place so that when Typescript does its job of compiling down, you still have reasonably safe code when all types are erased. It’s not the prettiest of code for sure, but it’s part of the tradeoff of using a fully-erased type system.

Type Predicates

A counterpart to type narrowing is type assertions and type predicates. Note type assertions are the most commonly abused system to “force” the compiler to understand one type as another. The syntax is pretty simple:

const a = "Hello"
getCount(a as any)

In the above example, we are widening the type of a so that it’s compatible with a possible different type that the getCount function expects. Using as casting in this way is frowned upon and possibly never has a place in a good codebase. Type predicates however, are a safer way to achieve the same thing.

Note: to use type predicates, you need a reasonably newer version of Typescript. But it doess make writing these type guards a bit cleaner. Here’s an assumeNotNull Jest test helper that we have in both the Todoist and Twist codebases.

function assumeNotNull<T>(value: T): asserts value is NonNullable<T> {

The current Jest type definitions for expect(value).not.toBeNull() don’t really communicate with Typescript that subsequent calls to expect(value) will always have value as not null (this is because the first expectation will throw and break the test). Here we’re writing a helper function to make this explicit. The important bit to note here is the asserts value is bit, which is called a type predicate. These type predicates are very flexible, and can be used to nudge Typescript in a lot of different ways.

Now both predicates and type narrowing may seem like extra boilerplate that comes with Typescript adoption, and if you are used to writing Javascript, initialy it definitely does seem like that. I sometimes do think of these as “stuff I do to keep the Typescript compiler happy.” From a longer-term software maintanence perspective though, building a strict tsconfig, and sticking to a well-typed codebase has huge rewards. There’s nothing wrong in prototyping your solution in a .JS file, without even having any types at all. And once you start converting the solution to Typescript, the compiler warnings and errors will guide you to writing more safe code.

Well that’s it for this instalment. See you next time!

Forgiving Yourself

I’ve always been an analytical and self-critical person, and I look at my past behavior and try to find improvements. It’s probably an offshoot of being a programmer: endlessly looking for ways to make code more concise, readable, or faster: in search of the next optimization. When you are programming, this optimization is as easy or as hard as debugging: finding faults in your approach, and more often than not, fixing them. You also have tools available that help you avoid similar mistakes in the future, rigorous testing for example.

In real life, this is messier. The first obstacle of course is achieving a bird’s eye view of the situation. The third-party perspective—that which comes most naturally to a programmer—is much harder to achieve in real life. In fact, even when programming, mental tools such as rubber-duck debugging help. Therapy helps to bridge this gap: talking about what’s troubling you and being asked pointed questions about it, seeking the help of a specialist who can apply CBT for example can often lead you to surprising answers. It’s important to note that therapy (just like rubber-duck debugging) is just a tool: at the end of the day, it’s up to you to process the results of your self-examination, and come out of a cocoon.

One difficult counter-note to looking back at your mistakes is guilt. “Why did I do that silly thing?” “Would have been so much better if I said this.” & the classic: “Maybe next time I should not get so pissed off.” For a long time, I was really good at processing guilt: either shrugging it off as life’s many foibles or thinking of mistakes as an adventure to learn. But when mistakes piled up to a pattern of behaviour, and a few of them led me down some painful turns in life, I was quite out of my depth on how to deal with guilt.

After quite some sessions of soul searching, one of the most effective ways I’ve found to process guilt, move on, and be positive is this: to forgive yourself. It’s also one of those concepts that’s very experiential: it’s easy to say these words, read them, intellectually understand what they mean without really grokking its essence. I realised this when I tried to explain this to a few friends, and couldn’t find the right words to describe the process. So here goes a more preapred version.

Forgiveness is a Work in Progress

Forgiveness is not a magic wand that you wave around to get instant results. Everything I talk about here I’m in the process of examining every week. Some events I deal with better than the rest. It’s important to get into this mindset first: small baby steps to a better you every week. Learning to relive the moment objectively (& eventually to live in the moment objectively) is an ongoing practice. Eventually you ackowledge those things that were not under your control, situations there were, and where you could improve, and learn to understand the difference.

Forgiveness is Unconditional

Probably the hardest lesson for me to learn was that forgiving yourself has to be unconditional. It’s not: “I promise never to do this again” (because you will), or “I had <x> mitigating conditions, I probably would never have done this if that had not been the case”. Either way, you are probably wrong: it will happen again, and probably you’ll do worse. Your forgiveness should be rooted in a mind free from conditions and if clauses: it’s acknowledging that a mistake happened, you have thought upon it, and there is repentance, that’s all there is to it.

Forgiveness is a Pledge to Keep Trying

How many times have you fallen off the wagon? How many times will you decide to go the gym and then change plans? How many times have you had a drink too many? Or become too impatient with friends and family? I think the human condition is that we’ll always keep making mistakes. What’s important is to forgive, and keep trying to be better. It’s easy to mistake this for giving up altogether, in fact it’s the opposite: no matter how many times you forget the gym, or go back to an indisciplined life, you know you will one day be better because you’ll try again.

Forgiveness is Gratitude

Part of forgiving yourself is also learning to acknowledge the duality in every situation. Even the worst incidents have joy. The act of finding nuggets of things you can be grateful for is very empowering. While forgiveness can find you a new path to move forward, gratitude leads the way. Maintaing a gratitude journal is a very powerful tool if you are currently struggling.

Well, that’s it. I realise this post is probably a bit more sappy than you expected from me, but it’s part of a growing realisation that topics surrounding mental health have very little exposure in the engineering world. We’re far too analytical and right brained, and our left brain selves are left starving. And to compound to this, Indians still have a stigma in talking about mental health.

This post is mostly due to conversations over the weekend that I had with Prema and Sanjay, two dear friends and probably the liveliest couple I know. If you are in trouble and need help, ping Prema, she has my personal recommendation.

Here’s a video from our weekend together:

Switching to Linux for Work & Play

I’ve been thinking of replacing my 5-year-old Macbook Pro for a while now, and as always, the easiest thing to do would’ve been to just buy the latest model. Apple also has a lot of exciting stuff planned for the Mac, and it’s a great time to be a Mac user, with Big Sur modernizing the interface quite a bit, and the eventual ability to run iOS apps. Apple is definitely firing on all cylinders.

So why a switch to Linux right now? And why Linux instead of Windows? Here’s a few reasons.

Electron Apps are Taking Over

I couldn’t have contemplated such a switch 5 years ago. Simply because my editor in those days was Textmate, an app built for the Mac. Nowadays it’s VS Code, a cross-platform editor from Microsoft. Then there’s Slack, or Twist, or Todoist, or Hey, everything runs on Electron. And Electron apps can run equally well on Windows or Linux. There’s no special Mac-sauce in my apps anymore.

In fact, this is a strike against Windows as well, because my first thought was to switch to Windows (with WSL2), but again, Electron apps work everywhere.

Development Experience is way better on Linux

You deploy everything to a Linux server, and developing on Linux means there’s always 1:1 parity. Even as a frontend developer, tools such as Docker work natively on Linux whereas it’s a horrendous memory-sucking abomination on the Mac. And Apple has been moving away from making the Mac developer-friendly: with enhanced protections such as SIP & Developer ID (which to be honest are legitimate wins for non-developers) more and more open-source software have stopped working or stopped being supported on the Mac. Apple used to ship with the latest open-source software, but now the bundled versions of most tooling is embarassingly out of date. And instead of supporting or building software such as homebrew, all of Apple’s focus is on its own application ecosystem. I’m again not singularly critical of Apple here: they are one of the few big companies that understand the value of focus, but in my case, this focus works against me & my programming profession.

This is again another strike against Windows because while WSL2 is very exciting, with Microsoft now shipping a Linux kernel and updating it through Windows Update, nothing will ever beat Linux compatibility than… Linux.

There are now “Good Enough” Distros on Linux

Me running VS Code, all Doist apps, Steam, and composing this blog post, all on Linux

I’m using Pop!_OS, an operating system meant for developers, which I suppose is strengthened by the fact that the name has got both an exclamation mark and an underscore 😁. But the interesting thing is, after a few bits of customization, it looks and works almost like a Mac. And it’s much, much snappier than my old Macbook Pro.

I like Pop!_OS also because it comes with sensible defaults, and like all variants of Linux, allows for a lot of customizability. I quickly switched off the native tiling feature, but it’s nice that it’s available, as are tons of other developer-specific customization in gnome-tweak-tool. I also remapped a few keyboard shortcuts to be compatible with my Mac muscle memory.

There are a few shortcomings of course:

  1. Native apps (like the Finder equivalent) are less polished, and some (like Quick Look) are not available at all. I’m hoping this improves soon. Note that most apps (even the Terminal for example) have excellent Electron equivalents. Run that, and you get the same experience you will on a Mac.
  2. I’m really used to the menu bar being at the top. Unfortunately, Gnome doesn’t seem to have a reliable Global menu solution.
  3. Font rendering is still sub-par. I’m not sure what exactly is wrong, but I can certainly see a difference in Linux. I tried several combinations of font smoothing and aliasing to get something decent looking, but it’s sad that out of the box nobody has focused on this important UX yet. Default fonts are also fugly, so I had to install and customize a few replacements, thankfully this is easy to do.
  4. You have to be familiar with the command-line to get things done. This is not a bad thing for me as a developer per se, but just putting it out there for other folks who might be looking to switch. Some GUI apps simply refuse to work (the Pop Shop or the App Store equivalent is a good example), and some require esoteric configuration for some tasks (remapping my extra mouse keys).
  5. Hardware compatibility is still somewhat of an issue. The first WiFi dongle I purchased simply refused to work, and I had to research and chug over some extra money for a Linux compatible one. However, my extremely finicky mouse (Logitech MX Master) worked out of the box. So did my Xbox controller, and most everything else I connected. Pop!_OS also comes with an installer that has my proprietary NVIDIA drivers pre-installed, a big win when it comes to the hell I remember that was installing compatible video drivers earlier. It’s way better than the last time I tried Linux, but things still can be improved. The Mac of course sets the gold standard here: in all the time I’ve plugged stuff into the Mac, I don’t think I’ve ever installed a driver.

Well that’s all I can think of for now. It’s surprising that besides these few warts, Linux works surprisingly well for my use-cases. In fact, besides my muscle memory acting up for a few shortcuts (I’m used to Ctrl+A/Ctrl+E, and Option+arrow keys for line traversal on a Mac), and some ridiculous shortcut key decisions on Linux (why not Super+C and Super+V for a global copy/paste shortcut for god’s sake), I’m reasonably comfortable in Linux even after a couple of days of switching over. I’m still using my old Mac keyboard, and I’ll change that soon, because I’m still not completely used to the Super/Ctrl key switch for most shortcuts.

Again: this is a big strike against Windows. The Windows UI always looked too busy for me. It’s got too much stuff on the screen, and the Start Menu is just horrible. There’s also no Dock, and that’s one UI element that I always want available. I looked into this quite deeply because the last time I was on Windows, I was a big LiteStep/BB4Win enthusiast, and while there are signs of a revival, again: my UI preferences run counter to what Microsoft builds.

You can now Game on Linux

So one of my supposed “Linux will never work for me” deal-breakers was gaming. Not that Macs are suitable for gaming—in fact, quite the opposite—but, if I’m going to switch away from a Mac, I want the benefits of being a gamer. I used to love playing World of Warcraft and Elder Scrolls, and that’s something I’d like to revive, especially in the post-COVID world.

So imagine my surprise when my apathetic googling for “Linux gaming” produced some really surprising results. Apparently, during the time I was ignoring Linux, Valve decided to produce a piece of software called Proton—an almost zero-configuration tool built on top of the venerable Wine, and a new port of DirectX over to Linux called DXVK. So gaming on Linux is now as simple as: install Steam, click a few checkboxes, download a game, and hit Play. It’s remarkable when it works, and some games even perform better on Linux. Note: this is not all games, but every one of the games I like to play works well: ESO, Witcher 3, No Man’s Sky, everything works, and works well. This is honestly an astounding development. Except for this, I would’ve gone the Windows and WSL2 route, and it’s still surprising to me when I get the games to work. And in typical open source fashion, there are now better builds of Proton than what Valve ships in house.

Microsoft should honestly be very afraid. There’s potential that Linux can be a better gaming platform FPS-wise, with most of the next-gen cloud gaming platforms running Linux and if there’s any group of tech folks more sensitive to bigger numbers, it’s the gaming crowd. Meanwhile for me personally it almost seems like the best of both worlds: a Mac-like Unix environment that can run games too. Fricking awesome.

So that’s why I switched. What next?

I know this will come as a surprise to many who know me as the quintessential Apple fanboy but I’ve always been pragmatic in my head 😋. I started searching for an alternative only because my age-old Macbook Pro was getting too slow for development, and quite frankly, a top-of-the-line iMac would have gotten me all of these benefits, except perhaps the gaming bit. But these machines are insanely expensive in India, and cannot be custom-configured to boot. For half the price of a Macbook Pro, I bought an assembled PC off of Amazon, and it has been serving me well. This also doesn’t mean I’ll never switch back to a Mac: there’s a lot to love there, and I definitely want to see how Apple ARM shapes up. But for work and play, it’s Linux as of now. Peace.

10 Fiction Book Recommendations from July

I read a crapton of books every month: besides smashing records for how many Kindle Unlimited books I can borrow & return in a day, I also buy a lot of ebooks that seem interesting. Most nowadays are in the genre of science fiction and fantasy, and for the past few months, even a couple of narrower genres inside that called LitRPG and Wuxia.

So I thought I’ll push some recommendations for books I read in July. Note: please do not expect intellectual books & deep moving stories here, I read mostly to wind down my brain and my selections reflect that.

Homeland, Exile and Sojourn (The Legend of Drizzt 1-3) by R.A. Salvatore

Drizzt is one of the most iconic characters created in heroic fantasy. He’s a dark elf, but he does not conform to the template & vagaries of his race. I’ve read other books where he played a lead role and it was always fascinating reading about the character, but these 3 books look at his origins. It’s not only a nice intro to Drizzt, it’s also a deep look at Menzoberranzan: the cruel city of his origins.

Get Book 1 Homeland here.

Song Maiden, Mistress and Matron by Jonathan Brooks

A Gamelit fantasy book that breaks most of the stereotypes in the genre. The protagonist is a young disabled girl who enters a game as a plain-looking dwarf & wants to be left alone, and who then finds her voice in the game. The writing is a bit amateurish at times, but the concept carries the day, and the sequels end up levelling her up pretty well. It’s also got a decent conclusion at the end of 3 books, but as always, there’s a chance of more books.

Get Book 1 Song Maiden here.

Silver Fox & the Western Hero series by M.H. Johnson

Silver Fox & the Western Hero is quite an interesting Wuxia for western readers. It’s got every trope in the genre: cultivation, tough fights, and rumours of an epic destiny, but it’s much more accessible to western fantasy readers. It’s also an isekai, but one where you don’t really have to suspend your disbelief that much. Overall a good read, but do note: this is incomplete, in fact, the series seems to be just beginning 🙂

Get Book 1 Warrior Reborn here.

Pilgrim by Harmon Cooper

Pilgrim is one of those books that took me by surprise. I got into it expecting the usual trashy read, but this novel has depth. It’s also an amazing example of what happens when amateur authors become better at their craft, and it’s a tribute to distribution mechanisms like Kindle Unlimited (read Cooper’s earlier books to see what I mean). Pilgrim has got an original plot, a brooding hero, and a world that is very original and well built. I’m really looking forward to future books in this series.

Get Pilgrim here.