Type Checking: A Necessity in Dynamic Development

We create open-source because we love it, and we share our finding so everyone else can benefit as well.

Type Checking: A Necessity in Dynamic Development

When first learning about type checking, it can be easily seen as an unnecessary addition to an application. When it comes to dynamic languages, type checkers prove themselves invaluable in a rapid development setting, and in a multitude of ways. Once you see the benefits, it is difficult to keep from making it a common addition to each of your future projects.

What is Type Checking?

When working with static languages like C++ or Rust, each initialized object requires that a type be set for that object. This allows your application to understand how that type is interpreted and the amount of memory it consumes.

Variable Assignment (Rust):

let some_number: i8 = 255;

On the other hand, with dynamic languages, we can omit the type completely, and allow the interpretor to handle the typing.

Variable Assignment (JavaScript):

let some_number = 255;

Because we lack a type assignment, the interpretor will guess this type, and instead use coercion to determine types.

Type Coercion

When a dynamic language interpretor determines the type of an object, the output can vary depending on the circumstances. Many developers can some time before even understanding coercion (I know I did), but the moment it’s understood, is the moment you become more self-aware of type handling. The following meme sums it up quite well, unless you fully understand coercion, or in the case of JavaScript, are forced to use double equals conditions, you can end up with some unintended side-effects.

js coercion
JS Coercion at its finest

When coercion is active, the type is usually determined by the original data of the left hand side value, and how it can be matched up with the right hand side. Each value can change to almost any types, and will conditionally This in turn causes the problem coined, Duck Typing, where if it looks like a duck, and sounds like a duck, it must be a duck. Because of this, each value can conditionally return something different depending on the type it’s interpreted to be.

Since coercion is common in most dynamic languages, it’s recommended to lookup the coercion rules for your language; Python 3 developers can rest easy, as mandatory coercion was removed in Python 3, and instead moved to an optional coerse method.

Why Coercion and Duck Typing is Dangerous

We can always make sure that our values are always correctly evaluated, but there is a larger issue at hand. Some languages simply do not support automatic coercion except in an explicit manner, but even then we can run into problems with duck typing in general. Consider the following method in Ruby:

def return_number(some_int)
  return some_int + 2
end

If some_int takes in the integer 2, it returns 4. Great! What if it receives a string value of “2” instead? In Ruby’s case, it ends up throwing an exception, since it will no convert the integer to a string. If you work with Rails, you can almost guarantee you have run into this issue at least once, and it causes an issue bubbling all the way up to the frontend. The same issue in JavaScript can be even more disastrous:

function returnNumber(some_int) {
  return some_int + 2;
}

As usual, when it receives the integer 2, it returns 4, but when it receives a string “2”, it returns “22”. Instead of throwing an error, it converts the 2nd integer to a string, and concatenates them together. Whatever takes in that output will convert that string to an integer, and your app silently continues working with a major edge-case existing somewhere within it. No bueno.

Type Checking

Now that we understand the downfalls that come with dynamic types, we can use type checkers to avoid the problems that inherently comes from the lack of typing. While we will discuss what we can use for type checking in next section, let’s discuss the benefits that come from typing.

The obvious benefit comes with the methods mentioned above, where adding an integer type to the input will allow us to restrict the types that go into the method, making sure the output is always returning an integer. There is another benefit from this one change, we also end up finding whatever caused our input to take in a string in the first place! This is great, because if the cause is an input on the frontend, we can avoid any other possible edge-cases, corner-cases, and side-effects all because of similar cases elsewhere in the app.

When you have an application filled with types, you will find it to be a lot easier to read. If you work with a monolithic or multi-layered application, it can be hard to keep tabs on every single part of the application, especially the inputs and outputs of each method. typing each value will give you a blueprint for each one of your typed methods, making it a lot easier to read.

export function errorCreate(error: Object, errorArea: string): Object {
  return dispatch => {
    dispatch(serverError())
    if(error.code && error.code === 'UnauthorizedOperation') {
      dispatch(toggleAlertMessage(error, errorArea))
    } else {
      dispatch(callError({ message: `${area}: ${error.message}`, stack: error.stack }))
      dispatch(errorMessage(error, area))
    }
  }
}

Personally, I cannot tell you how much this one point makes my own job a lot easier, but it also helps me catch errors in code a lot easier. When working adding types to all of your objects checks, and used in tandem with a linter, you end up verifying each and every property in your application. This means typos, type casting, and all other errors are stopped dead in its tracks.

Last, but far from least, when using continuous integration, these types can be checked, causing tests to fail when there is a type issue (as it should). This should always act as a last line of defense, so you can always make sure types never play a part in any issue.

Type Checkers

Now that we have a reason to use them, let’s look at the different options available in a few different cases:

TypeScript

If you are planning to use JS for a new web application, TypeScript is a great option for type-safety. It is a superset of JS, meaning it is it’s own platform.

Flow

Facebook’s own type-checker, Flow is a great addition to any existing application. Since it’s a package, you can implement as little or as much you as you want into your application. This is great for applications in React, as you can use prop-types for your front-end components, and flow for your backend (e.g. Redux).

Sorbet

This is a new one to the Ruby ecosystem, and developed by Stripe, a company who has contributed to every language it has touched, and now bringing this to a language which has lacked a type-checker for far too long. While I’m not sure about the progress of Rails support, it has been great for Ruby overall, offering the functionality you would need from any type-checker, and fully supported by a large company like Stripe, you can expect full support if it isn’t there already.

Python Typing

Since 3.5, Python has provided its own typing system to use in each of your Python scripts. Just like other systems, this can be incorporated in varying amounts.

Conclusion

The more you use type-checking, the more apparent it becomes that type-checking is incredibly powerful, and the more you use it the less you deal with superficial issues. The biggest benefit being that type-checking is one of many in a suite of preventative measures, making sure your application runs smoothly, removing human mistakes, and overall allowing more time you can spend on development on areas you truly care about.