One of the great features of modern programming languages is structural pattern matching on algebraic data types. Once you’ve used this feature, you don’t ever want to program without it. You will find this in languages like Haskell and Scala.I couldn’t agree more myself. That said, I spend most of my time writing programs with languages that don’t have first-class support for algebraic data types (ADTs). So what’s a programmer to do? This blog post provides examples of two ways to approximate structural pattern matching in TypeScript. The class-based example borrows heavily from Bjarnason’s excellent blog post Structural Pattern Matching in Java, and the discriminated union example was inspired by the Advanced Types section of the TypeScript documentation and countless conversations with Michael Avila, one of my coworkers.
Failable
– a type which represents the disjoint union of success and failure-values – with the following syntax:
What we’re saying here is that a value of type Failable t e
is either a Success t
(“we succeed with a value of type t
“) or a Failure e
(“we failed” with a value of type e
). Functions that return a Failable
can indicate one or the other but not both.
When interacting with a value of type Failable
, we need to pattern match on the type’s constructor if we want to perform operations on their underlying values.
Failable
:
In the above example, the Failure
and Success
interfaces both have a common, singleton type property – tag
(item #1
). These interfaces are unioned together to form our ADT, Failable
(item #2
). For any function accepting a Failable
to be well typed it must first examine the value of the Failable
‘s tag
before making use of Failure
or Success
-specific properties (item #3
).
We can use the tag
type guard to build a type safe failable
function, too:
strictNullChecks
enabled, TypeScript will fail to compile our failable
function if it omits one or more of the type’s singleton properties (tag
, in this example), because the function would implicitly return undefined
for the unhandled type and undefined
is not an inhabitant of our return type, U
.
failable
seems sensible, I’ve run into things like templateElementLabelResolutionResult
.
Failable#match
method would not contain a parameter for this new type.
instanceof
stuff required) provided that they guard on the common property.
In the following example, we define a function parseLogLine
which accepts a line of text from a log file as a String
and returns a ErrorMessage
, WarningMessage
, InfoMessage
(if the string was parseable as a log-line) or Unknown
(if it was not).
Interested in a Career at Carbon Five? Check out our job openings.