Created October 13, 2021

TypeScript Types Can Do Some Cool Things That Elm Can't

Yes, it's important to be aware of [TypeScript's Blind Spots]. Elm has a sound type system, whereas TypeScript does not have a sound type system (by design and because of differences in design goals, not because of shortcomings in the type system). But TypeScript is a great tool when you need it, and its type system has some very powerful features. You can express some things that Elm's type system can't. You can argue whether the simplicity of Elm's type system is good or bad. I think there are a lot of benefits to simplicity, but I also love being able to do powerful things as a library author.

Whatever your opinion, it's fun to compare and good to be aware of the differences. So here are some cool things you can do with TypeScript's type system that you can't do with Elm.

Literals#

In Elm, the compiler will never go from compiling to non-compiling (or vice versa) from a change in a literal value. For example, if you change a string from "Admin" to "admin", all Elm sees is two Strings. TypeScript can distinguish between literal values. In TypeScript, you could say

type UserRole = "admin" | "logged-in" | "guest";

And it will enforce that those values are used.

Tuples#

Tuples are just Arrays (not a special type). A Tuple is a subset of an Array type, unlike in Elm where ( String, String ) is not valid if you pass it in where a List String is needed.

In TypeScript, this compiles:

function join(items: string[]);

function printName(name: [string, string] | [string, string, string]): string {
  return join(name);
}

In Elm, these two types are incompatible. The set of values of type ( String, String ) and the set of type List String are non intersecting.

This is both a neat feature of the type system, and a pragmatic design that fits into the design constraints of using JS.

Spread Operator#

In Elm, you can express non-empty lists with the type ( value, List Value ). There are packages for non-empty list that use a custom type, but I prefer using the type alias form. This package, turboMaCk/non-empty-list-alias builds an API around the type alias version of non-empty lists. (TODO: link to blog post?)

This is great in terms of guarantees. But TypeScript has a cool feature that can help give you non-empty lists in a more ergonomic way. This function will check that the Array is non-empty.

function getFirst(nonEmptyValues: [string, string[]...]): string {
  return nonEmptyValues[0];
}

TODO: playground example link.

And not only that, you can even use the spread operator to ensure that a list has at least n items.

function atLeastThree(atLeastThreeItems: [string, string, string, string[]...]): string {
  return nonEmptyValues[0];
}

Template Literals#

type AdminId = `ADMIN_${number}`;

let validAdminId: AdminId = "ADMIN_123";

let invalidAdminId: AdminId = "ADMIN_ABC";

Mapped Types#

https://www.typescriptlang.org/docs/handbook/2/mapped-types.html

Lots of advanced stuff here: https://www.typescriptlang.org/docs/handbook/2/types-from-types.html

Sign up to get my latest Elm posts and course notifications in your inbox.

Incremental Elm



Pure Elm content. Unsubscribe any time.