Sum Type Constructors in TypeScript
Or, making TypeScript into a terrible ML because I canât use Elm in my day job.
A pretty common pattern Iâve seen is to have three basic states for some kind of HTTP request: loading, failure, and success. Since each of these has its own associated date, itâs a really good fit for a discriminated union or sum type. In a language like Elm (or F⯠or Haskell or PureScript orâ¦) youâd write that basically like this:
module Fetch exposing (State)
type alias HTTPStatusCode = Int
type alias ErrorData = { code: HTTPStatusCode, reason: String }
type State a
= Loading
| Failure ErrorData
| Success a
Because I find that pattern extremely helpful, Iâve at times gone out of my way to replicate it in TypeScript. And what you get is⦠verbose. Itâs a necessary evil, given what TypeScript is doing (layering on top of JavaScript), and so much so that I wouldnât actually recommend this unless youâre already doing this kind of programming a lot and find it pretty natural. If you are, though, hereâs how you get the equivalent of those four lines of Elm in TypeScript:
type HttpStatusCode = number;
export enum Type { Loading, Failure, Success }
export class Loading {
readonly type: Type.Loading = Type.Loading;
static new() {
return new Loading();
}
}
type ErrorData = { code: HttpStatusCode, reason: string };
export class Failure {
readonly type: Type.Failure = Type.Failure;
constructor(readonly value: ErrorData) {}
static new(value: ErrorData) {
return new Failure(value);
}
}
export class Success<T> {
readonly type: Type.Success = Type.Success;
constructor(readonly value: T) {}
static new<A>(value: A) {
return new Success(value);
}
}
export type FetchState<T> = Loading | Failure | Success<T>;
export const FetchState = {
Type,
Loading,
Failure,
Success,
};
export default FetchState;
Thatâs a lot more code to do the same thing. Even if you dropped the static constructorsâwhich you really donât want to do, because then you canât use them in a functional style but have to use new Loading()
or whatever to construct them.
You can make this work. And I do. And honestly, itâs amazing that TypeScript can do this at allâa real testament to the sophistication of the TypeScript type system and the ingenuity that has gone into it.
But have I mentioned recently that Iâd really prefer to be writing something like F⯠or Elm than TypeScript?