While I mentioned how great to do pattern match in Elixir. It’s surprisingly good to do this in TypeScript thanks to the ts-pattern.
Here is what I did to grab the content of different RSS feeds using Elixir:
def get_item_content(%{ "content" => %{ "value" => value } })
when is_binary(value), do: value
def get_item_content(%{ "content" => content})
when is_binary(content), do: content
def get_item_content(%{ "description" => %{ "value" => value } })
when is_binary(value), do: value
def get_item_content(%{ "description" => description })
when is_binary(description), do: description
def get_item_content(%{ "content_html" => content_html }), do: content_html
def get_item_content(_), do: "No Content"
And this is the ts-pattern
version:
type RSSItemShape =
| { content: string }
| { content: { value: string } }
| { description: { value: string } }
| { description: string }
| { content_html: string };
function extractContent(item: RSSItemShape): string | null {
return match(item)
.with({ content: __.string }, (r) => r.content)
.with({ content: { value: __.string } }, (res) => res.content.value)
.with({ description: { value: __.string } }, (res) => res.description.value)
.with({ description: __.string }, (r) => r.description)
.with({ content_html: __.string }, (r) => r.content_html)
.exhaustive();
}
First of all I should say this might looks a little like comparing Apple to Orange. Because Elixir’s pattern matching is a language feature, while the pattern matching of TypeScript is to leveraging the ts-pattern
library. The performance of Elixir’s version is most likely better than ts-pattern
until someday this was built into V8 node engine, I don’t need to worry the Elixir’s pattern matching won’t work in the future unless syntax changes. But I would need to keep an eye on the version upgrades of ts-pattern
which might introduce non-backward compatible changes.
However, today I am just gonna compare it from the developer’s experience perspective, how would it helps us to achieve our goals, express our intention more efficiently.
The first thing I noticed is that both the Elixir version and ts-pattern
version are pretty concise. Yes Elixir has more flexibility that you can do the pattern matching on function level or block level. But I think ts-pattern
did a great job in terms of readability. Unlike Elixir can absorb this into the language design, ts-pattern
has to be creative within the JavaScript language syntax boundary. But ts-pattern
already did a great job by boiling it down to the minimal key words: match
, with
, otherwise
and exhaustive
.
Composable. The one lovely thing about ts-pattern
is the composable due to its method calls. Composing multiple pattern matching can be quite handy and strait-forward in ts-pattern
.
Last thing of course is the type safety ts-pattern
can give you. It won’t compile if you are missing cases when using the exhaustive
keyword. This could prevent some errors creeping into the runtime. Be careful when passing any
object into the match
, as ts-pattern
has no way to know if you covered all the cases or not. So it will only throw exception during the runtime.
// matching any object
function extractContent(result: any): string | null {
return match(result)
.with({ content: __.string }, (r) => r.content)
.with({ content: { value: __.string } }, (res) => res.content.value)
.with({ description: { value: __.string } }, (res) => res.description.value)
.with({ description: __.string }, (r) => r.description)
.with({ content_html: __.string }, (r) => r.content_html)
.exhaustive(); // This will not prevent compile time check as we are matching any object
}
Overall I am super happy with ts-pattern
, hopefully it can make into JavaScript feature someday, as TypeScript essentially constrained by the target language poses.