---
title: Uncertain, lazy, forgetful, & impatient: It’s what you want your code to be.
url: https://photostructure.com/coding/uncertain-lazy-forgetful-and-impatient/
description: Uncertain, lazy, forgetful, & impatient; characteristics of good code.
date: 2019-12-16
---


While building [PhotoStructure](/), which is written in [TypeScript](https://www.typescriptlang.org/), I found myself missing a bunch of [Scala](https://www.scala-lang.org/)-isms.

## ❓ Character trait #1: Unabashed uncertainty

One of the first bits I missed from Scala was the `Option` monad.

“_Oh no_,” I hear you thinking,

{{< figure src="/img/2018/12/monads-1.jpg" caption="Woody's so very tired of the functional n00bz spreading their gospel" >}}

but it’s not going to be one of _those_ blog posts. Honest.

For the uninitiated, null pointers have been considered a “[billion dollar mistake](https://en.wikipedia.org/wiki/Null_pointer#History).”

> ... I couldn’t resist the temptation to put in a null reference, simply because it was so easy to implement. This has led to innumerable errors, vulnerabilities, and system crashes, which have probably caused a billion dollars of pain and damage in the last forty years.

These are commonly seen as `TypeError`s in JavaScript. You've probably got a couple in your console right now. _Well, maybe not **now** now. I hope not._

Functional programming, or FP, has had a solution to this, called `Option`. [Here's the Rust variant](https://doc.rust-lang.org/std/option/enum.Option.html). An Option is a union type of either

1. A `Some<T>` value, or a
2. `None`, which is the absence of a value.

**The important takeaway:** when you return an `Option` (or a `Maybe`), you’re advertising to your callers that you’re uncertain you can return a value, and that they need to behave accordingly.

Every method that makes an [RPC](https://en.wikipedia.org/wiki/Remote_procedure_call) call, handles user data, or in any way touches external systems should _probably not be certain_ that they can be successful in returning something reasonable.

In Ye Olde Java Shoppe, you'd probably have your method throw an `Exception`. You then have to decide (and fight with your code reviewers) if you need a new Exception subclass, and if it needs to be a run-time or caught exception. If it warrants a new error type, do you subclass the prior typed Exception or introduce another hierarchy? It's a royal PITA. The fact that it's arguable means there isn't really a "right" answer, and (most importantly) _your callers probably don't care why you failed_, just that you _did_. If your caller doesn't care why you failed (because it doesn't need to tell their caller), an `Option` is a nice option.

[Here is a simple implementation of Option in TypeScript](https://gitlab.com/snippets/1791850). There’s an entire library, [fp-ts](https://github.com/gcanti/fp-ts), which has a rich set of FP tools for you to use, too.

If you’re concerned about overwhelming your garbage collector with these `Some` wrappers, you can use a much lighter weight approach. Meet `Maybe`:

```ts
export type Maybe<T> = T | undefined;
export type MaybeNull<T> = Maybe<T> | null;
```

To help with the ergonomics of dealing with `Maybe`s, `map` is convenient:

```ts
export function map<T, U>(obj: MaybeNull<T>, f: (t: T) => U): Maybe<U> {
  return obj == null ? undefined : f(obj);
}
```

In use:

```ts
return map(await db.fetchUser(username), user => {
  // do something interesting with user,
  // now that you know it’s defined
  ...
});
```

Keep in mind that if this is called in a tight loop, those closures will create
a metric boatload of GC pressure, even with recent (2024) V8 engines. Thanks to the
[optional
chaining](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-7.html#optional-chaining)
syntax in TypeScript 3.7, if you just want to call a method on the user, you can
do something like

```ts
(await db.fetchUser(username))?.methodOnUser();
```

If you want to do more than one thing with the optional reference, the `map` is still a handy tool to have.

### Uncertainty of the future

But, you say, but everything these days are [`Promise`s](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise), what have you got for me to help with that?

Meet `thenOpt`:

```ts
return thenOpt(db.fetchUser(username))
  .flatMap(user => checkForPwnedPassword(user))
  .flatMap(pwnedResult => ...)
  .getOrElse(() => {
    // this block is called if the resolved db user was undefined/null,
    // or the checkForPwnedPassword promise resolved to undefined/null.
  })
```

It’s the same interface as an `Option`, but the functions may return synchronous results or Promises. If the Promise resolves to nullish, the remaining calls (until an `orElse` or `getOrElse`) are not invoked.

**[Here is the implementation of thenOpt](https://gitlab.com/snippets/1922387).** Share and enjoy.

## 💤 Character trait #2: Only paying when it's due

Another tool I missed from Scala was `lazy`, which is a `val` subtype.

A `lazy val` is only computed and assigned **at access**. If that computation is expensive, it’s nice to be able to defer that computation until it’s actually needed. This can both help expedite system startup time as well as work around circular dependencies (when they can’t be, or just aren’t, avoided).

My first implementation was simple and simply cached a [thunk](https://en.wikipedia.org/wiki/Thunk):

```ts
export function lazy<T>(thunk: () => T): () => T {
  let invoked = false;
  let result: T;
  return () => {
    if (!invoked) {
      invoked = true;
      try {
        result = thunk();
      } catch (_) {}
    }
    return result;
  };
}
```

Note that this implementation swallows errors. You may find it more useful to both cache the result, _and_ catch and rethrow any caught error.

A couple use cases:

```ts
class File {
  ...
  readonly sha = lazy(() => {
    // Only compute the SHA if someone asks:
    // (implementation left as an exercise for the reader)
  })
}

class Server {
  ...

  readonly end = lazy(() => {
    // cleanup code that must only be called once per instance
  })
}
```

I found fairly quickly that tests needed to clear prior test state, and that certain lazy fields should probably expire after a certain amount of time, and gee whiz wouldn't it be nice if I could clean up any resources from prior value but only if there was a prior value, and... Anyway, that [fancier lazy is here](https://gitlab.com/snippets/1791927).

_We now say goodbye to the "I miss Scala" portion of our broadcast._

## 🧠 Character trait #3: What are we talking about again? Oh, right. Forgetfulness.

Many morsels of computation that we want to cache are only valuable or relevant for a short amount of time. If we could allow our program to be appropriately forgetful, we can limit our memory consumption and prevent leaks.

It’s not always easy, though.

> There are 2 hard problems in computer science: cache invalidation, naming things, and off-by-1 errors. — [Leon Bambrick](https://twitter.com/secretGeek/status/7269997868)

If you know what you’re caching, and have sufficient domain knowledge to know when it’s appropriate to be forgetful, this problem is tractable.

One simple approach to invalidation is “time-to-live”, or TTL-based invalidation. Caching based on keys is supported by [TTLMap](https://gitlab.com/snippets/1792108).

As a concrete example, if you're processing files, _say, [photos and videos](/),_ and you know you'll be done processing the file in probably seconds, but maybe a minute, you can set your TTL on your file metadata cache to a minute, and get excellent cache hit rates.

There are a number of other cache invalidation strategies, but those can wait for another blog post.

## ⏱️ Character trait #4: Impatience

As soon as your method or system requires external resources, as discussed above, the success of your code is not inevitable. Networks drop packets. Filesystems hang. [Operating systems decide they don’t need to keep operating](/im-on-windows-and-things-are-stuck/). Bad stuff happens.

Sometimes it's reasonable to pass those hardships onto your callers, but you aren't like that, are you? You're an upstanding, empathetic _designer of ergonomic and user friendly systems._ What to do?

{{< figure src="/img/2018/12/timeouts-5.jpg" caption="(this time, Woody's concerned about the idempotence of the retried promise)" >}}

Timeouts and retries add robustness to otherwise flaky systems, but (as with all things) there are gotchas.

Unfortunately in esNext land you can’t “time out” your promise and release resources, but you _can_ let the caller receive a rejection if the promise is not resolved within a given amount of time.

```ts
export async function thenOrTimeout<T>(p: Promise<T>, timeoutMs: number) {
  let resolved = false;
  let result: Maybe<T>;
  p.then((ea) => {
    resolved = true;
    result = ea;
  });
  return Promise.race([
    p,
    new Promise((resolve, reject) =>
      setTimeout(
        () => (resolved ? resolve(result) : reject("timeout")),
        timeoutMs,
      ),
    ),
  ]);
}
```

Add logging and season to taste. But yeah. Add timeouts to everything.

You can also add retries pretty easily, too, just be careful to only retry [idempotent](https://en.wikipedia.org/wiki/Idempotence) actions. [Here's an implementation for `retryOnReject`](https://github.com/photostructure/exiftool-vendored.js/blob/master/src/AsyncRetry.ts).

<!-- ## Comments? Suggestions?

There’s a [thread on hacker news](https://news.ycombinator.com/item?id=21788086). -->

