Connecting...

W1siziisimnvbxbpbgvkx3rozw1lx2fzc2v0cy9zawduawz5lxrly2hub2xvz3kvanbnl2jhbm5lci1kzwzhdwx0lmpwzyjdxq

The higher order of types by Marcin Baraniecki

W1siziisijiwmtkvmduvmtcvmtevmtcvndyvoda1l3blegvscy1wag90by0xmtexmze1lmpwzwcixsxbinailcj0ahvtyiisijkwmhg5mdbcdtawm2uixv0

How often do you use TypeScript?

Developer at SoftwareMillMarcin Baraniecki is completely sold with the programming language. But why is this? Find out how it takes his programming to the next level.

 

'I can’t imagine starting a serious project using only plain old JavaScript. With all my respect to the de facto lingua franca of the web, I’ve been totally sold to TypeScript for several months now. Turns out, there are ways to take this relationship to a whole new level.

 

What is TypeScript?

If you haven’t had a chance to stumble upon TypeScript yet, I’ve got great news for you: complain no more about this dreaded, dynamic nature of JavaScript. We’ve got you covered now, and we’ve got static types. More and more libraries and frameworks come with their '*.ts' counterparts every day. Gone are the times of functions in the wild accepting any kind of arguments. How great is this:

or this:

Yeah. You can finally be sure what goes in and what comes out.

There are other competitors on the market, too. Flow by Facebook is one of them.

 

How does static typing help in everyday work?

If you come from languages like Java or C#, I think the answer will be obvious. Not only it is easier to understand the input and output of certain APIs, but you also gain a great buddy — the type-checking compiler! TypeScript’s one is pretty flexible when it comes to the configuration, so while it’s best to adhere to the strict laws, some rules can be relaxed (if needed). On the other hand, you get strong IDE support — be it Visual Studio Code or Webstorm, with all their code navigation, completion & refactoring features.

And should I mention that Everything You Do Is Now Much, Much Safer?

 

The problem with null values

Typed language, however, cannot save you from the pitfalls of 'null' values. JavaScript made the situation even stranger — by introducing 'undefined', which behavior isn’t much different than 'null''s. There are few niceties in TypeScript that make working with such values safer, eg 'strictNullChecks', which is a compiler setting that enforces strict, explicit checks against a possible absence of a value. Such that if you want to allow the value to be 'null' or 'undefined', you would have to write:

Notice that this code will not compile — the 'age' field was declared with a '?' at the end, meaning that it can be a 'number', but it can also be 'null' or 'undefined'! TypeScript will yell at the last line, saying that you’re trying to access a possibly non-existent value.

The solution to such situations has always been called a defensive style programming:

This line compiles, but we had to introduce an explicit check, with a fallback to some default value in case of 'age' being 'undefined' (or 'null'). Few more lines like this and the code becomes clumsy pretty quickly.

There are, however, better ways to deal with such… optional values. Languages like Haskell, Scala, ML & Rust have almost always had a solution for that. They introduced the notion of the wrapper boxes called 'Option', 'Optional' or 'Maybe'. Such “boxes”:

  • are generic over the value they encompass, like 'Maybe<T>': 'Maybe<number>' or 'Maybe<Person>',
  • enumerate two variants: 'Just<T>' & 'Nothing' (or 'Some<T>' & 'None' — these are alternative names)
  • expose an elegant interface of methods to modify or safely “unpack” the possibly contained value.

Using TypeScript, such features can be easily integrated with true-myth, which is a “library for safe, idiomatic null and error handling in TypeScript” (more about error handling later). An example above, rewritten with true-myth would look like that:

Notice the use of 'Maybe<number>' in the type declaration — that way we declared, that the value might be there, but it is equally possible that it’s absent, while in the following lines we will never have to mention either of these ugly words starting with “un…” or “nu…” at all!

In order to calculate the value of 'twiceTheAge', we 'map' over the 'age', providing an arrow function that is called only if the value was there. We want to pull out the value from the box at the end of the day, but hey — remember, that it could not be there — hence a fallback value of '0' is passed to the 'unwrapOr' call.

No 'if's, ternary operators and all this defensive style — just a nice, functional flow. Can you guess the final value of 'twiceTheAge'?

 

The problem with errors

Turns out — not only an absence of value can be handled in a much better, idiomatic way, but so can application errors! Let’s admit it — a classic error throwing pattern (unwinding the stack if not caught properly) should rather be avoided, and if really needed — pushed to the “boundaries” of the application, in scenarios of where it no longer makes sense to recover from a fatal exception.

So, having a function that can possibly throw an error (notice the use of 'never' type denoting a function that throws an exception and thus never returns):

And having learned about the 'Maybe<T>' type, we could write:

Well, not so fast! This way we possibly lose insight into why the function failed to return a value in the first place.

Enter 'Result<T, E>'. Also known as 'Either' in other languages (Haskell, Scala), it’s another type of a “box”, although this one is never empty! It holds… well… a result of some computation, be it a target value, or a value representing an error. So instead of losing the trace of what possibly went wrong, we can return an 'Err' containing eg. an error code:

A 'Result' exposes a very similar API to the 'Maybe' type — you can 'map', 'flatMap' and 'unwrapOr' (amongst many others).

 

Summary

Safe optional values, errors represented as data — this is what I call the higher order of types. These patterns are very well known in other (mostly functional) languages, but it seems to me that the world around JavaScript has yet to discover them.

My team and I have been using both described types in at least two serious, commercial projects so far. You won’t see the defensive style against 'null' or 'undefined' in the codebase, as well as errors unwinding a very high call stack.

We even came up with some patterns for React components, but this, as well as other details and use cases, is an interesting topic that I want to write about in another blog post.'

This article was written by Marcin Baraniecki and originally published on SoftwareMill.