Choose language

Clean up your view states

Using pattern matching in TypeScript and JavaScript to make a clear contract for the states of the view layer

The problem with making statements

When you first learn to program, you quickly learn to wield the power of if/then statements. Being one of the base building blocks of writing software, you discover the simplest form of program flow.

In this blog

Clean up your view states 1200x627But once you become more experienced and look closer, you will discover that in most programming languages, the if/else is implemented as a statement and not an expression (note that some languages like Kotlin have implemented it both as an expression and a statement). So, what is up with expressions versus statements? 

In programming language terminology, an “expression” is a combination of values and functions combined and interpreted by the compiler to create a new value instead of a “statement”, which is just a standalone execution unit that doesn’t return anything. [1]

So, an expression makes you focus on the small context of your sub-problem. You return a value and continue with that. Use the value to solve the next sub-problem, and so on. A simple example:

Clean up your view status 1

You only work with the data known inside your context (which is a function here).

A statement created with if/then and switch allows you to think and work with a global context. And for small applications like scripts, this does not have to be a problem, but once the project grows or multiple people work on it over time, the global context easily grows into spaghetti. There are no restraints for you or other developers to add another totally unrelated clause that writes to any random part of the global context or even outside calling remote services directly. 

A switch statement looks a bit more like pattern matching (I’ll explain what that is in a minute), however, because it also is implemented as a statement, you can have completely unrelated test expressions. Let’s look at an example where a switch statement is used to alter the global context based on a string value to match:

Clean up your view states 2

Where “priveWithVAT” and “notifyUser” are declared elsewhere in the global context. The variable ‘fruit’ is the context to match on, but the switch statement does not enforce cohesion between the test cases and besides that allows you to make further statements, like ‘notifyUser = true’ and ‘callRemoteService()’ in the global context.

The result of running this code is that several global variables are updated. What is the problem with global context? It could be unintentionally updated by other pieces of code causing unintended effects from your statements. Besides that it makes your code harder to understand, there are moving parts everywhere.

Another problem of if/then and switch is that it does not force you to write a case for all possible inputs. Sure you could add an ‘else’ to your block or a ‘default’ for a switch, but those are optional, so you may forget, or you add one and your colleague deletes it when refactoring.

So the bottom line is that statements like if/else and switch have some disadvantages:

  • They allow you to work outside the local context which increases the chance of bugs
  • They do not help nor force you to write test cases for all possible outcomes of the matching 

Luckily, in the 1950s smart people solved this problem for us. They invented pattern matching. Pattern matching gives you a context to guide the matching to a clear outcome (expression). In typed languages, the compiler forces you to cover all possible cases.

Pattern Matching instead of if/then (and switch)

Many modern languages like F#, Haskell, Python, Rust, Scala and Swift have pattern matching built into the language. But when working on a web application, you may need to use JavaScript or TypeScript. Can you use pattern matching in this application to improve the quality of your software? 

JavaScript

There is currently no built-in support for pattern matching, but a proposal [2] championed by developers from Google, Microsoft, Netflix, Sony and others is being worked on. This, however, could take many years. In the meanwhile, we can use several options depending on your project and needs.

Ramda [3]

Is a large collection of small functions that you can combine to create a program in a declarative way. If you happen to use Ramda in your project, know that you can use ‘cond’ to create an expression that can pattern match. “cond” needs a set of predicate function + outcome function pairs. The first pair where the predicate is true will have its outcome called.

Daggy [3]

Gives us “sum types” and a way to actually match input and output on the type. Since JavaScript does not have strong types you can use Daggy to create the type structure you need to pattern match on.

A “sum type” is literally a summation of all the options a type can have, for example a boolean sum type consists of the sum of all options for a boolean being true and false.

Daggy makes sense when you are working with a view technology like React. You can separate the definition plus the creation of the sum type (outside the view layer) from the usage of the sum type (inside the view layer). This way you can keep your view layer as simple as possible.

Armed with Daggy to define the possible options in the sum type and Ramda.cond for the matching of the given input to the right type in the sum, we can do this:

Clean up your view states 3In the view layer, we can ask React to use Daggy’s “cata” (short for catamorphism which is like a reduction from a more complex type down to a more simple one) to render the proper outcome to the screen.

Clean up your view states 4

One of the principles here is to keep the view as dumb as possible. When you do not care so much or just want to have one place to go from input to output, you could also skip Daggy and only use Ramda.cond and some function abstractions like isUnderweight, isNormal etc. directly in the View.

(for reference I’ve included a version without pattern matching [8]).

TypeScript

If you are working with TypeScript instead of JavaScript, you would like to take advantage of having the compiler. You could see if you break the context you are using at compile time, and you could check if you’ve covered all branches when your use case has a finite set of logical branches (options/outcomes).

Ts-pattern [5]

This lib was especially made to take advantage of TypeScripts compile time type checking. This will give you confidence you are not violating the context you declared.

Instead of using Daggy to structure the context and Ramda.cond to pattern match, we’ll use a TypeScript type for the structure and ts-pattern “match” to do the matching in both model and view layers.

Clean up your view states 5And the view can use ts-pattern too:

Clean up your view states 6

Again, if you want to combine the model and view layers into one, you can do the calculation of the categories and the displaying of the right category in one call to “match”.

In the references are links to two GitHub repos where you can check out complete runnable projects.

Conclusion

Pattern matching is a powerful and flexible technique, reducing the chance of subtle bugs. And while we wait for native support in JavaScript and TypeScript, we can use (and combine) the options mentioned above.

For a web-application It has added value to ‘dumb down’ your view layer so that it only has to pattern match on a structure that has all information needed to render. When using ts-pattern or Daggy you can rest assured that all possible view states are handled and that your view is very readable.

Some advice:

  • Replace if/then and switch statements with single-purpose expressions (using pattern matching) to avoid the temptation of overloading these statements for multiple purposes which increases code complexity and the risk of breaking stuff
  • There is no built-in pattern matching in JavaScript and TypeScript, but good third party options are available
  • When using JavaScript, you could use Ramda.cond with or without Daggy
  • When using TypeScript, you could use ts-pattern

Tjerk Valentijn

About Tjerk Valentijn

Tjerk Valentijn is a seasoned Software Developer with a remarkable 12-year tenure at Visma Connect. In his role, Tjerk collaborates with a talented team to craft cutting-edge software solutions, leveraging his expertise in programs like Typescript and JavaScript. His contributions are pivotal in advancing technology for the Dutch government, showcasing Tjerk's commitment to excellence and innovation in software development.

References

[1] https://fsharpforfunandprofit.com/posts/expressions-vs-statements/

[2] https://github.com/tc39/proposal-pattern-matching

[3] https://github.com/fantasyland/daggy

[4] https://ramdajs.com/docs/#cond

[5] https://github.com/gvergnaud/ts-pattern

Source code for this article is available

https://github.com/tjercus/bmi-daggy-ramda

https://github.com/tjercus/bmi-ts-pattern

[8] https://github.com/tjercus/bmi-daggy-ramda/tree/an_imperative_version

Related blog posts