HaskellScalaTypes

Why strong static types matter (in Haskell)?

Posted on by

Why strong static types (in Haskell) matter?

Like many developers, you may already have developed several projects in JavaScrip, PHP, Python or any other dynamically typed language. You may have come to the point where you decided to learn a new language with a strong static type system just for fun or you are forced to because your company switched to another technology.

As a disclaimer, this blog post is not intended to start any wars. If you are using dynamic types and love it, that's totally fine. I'm pretty sure and aware that dynamic typing has its own sweet points. But this is not the focus of this blog post. I would like to show you what reasons you might have to use static types.

First of all, what is the key difference between dynamic and static types? The most important part is how your compiler/interpreter tries to figure out what kind of type your variables or expressions have. With dynamic typing, this is processed at runtime (when the code is finally executed). For static typing, all types are determined at compile time (before the code can be executed). So what could be the problem if we try to apply types at runtime? Let's have a look at a simple JavaScript example:

function add2(a) {
    return a + 2;
}

We defined a VERY simple function "add2". Thanks to naming and simplicity of this function we know: "This function adds 2 to our parameter "a" and returns the result". Ok! Sounds good, right? This is exactly what we needed, take a number, add 2 and return. Now have a look at our parameter a. It has no type at this point. This means it could be a string, a number, an array (or even worse undefined, null). And you will only know about the type if you start using this function at runtime. Now, let's play around with this function and pass in different values: string, number, array, undefined.

add2("two");
// "two2"

add2(2);
// 4

add2([]);
// NaN

add2(undefined);
// 2

If this still lets you sleep well at night when this code goes into production, nevermind. Nothing prevents any other developer in your team to pass in those values and don't use your neat function as you intended it. Think about it, how can you solve this problem? Name the function "add2ToNumber" and hope for the best? This won't help. We all know how developers work, in a hurry, time schedules, lazyness. We are humans, we are supposed/used to make mistakes.

Next approach: Write tests! This will definetly help. Let's just push our test coverage to 100% and we know exactly what will happen. How many tests do you need to prove the correct behaviour of our add2 function? We passed four different types into our function and it did four different things. We need to test all this cases (plus many more). Writing tests just became a main part of your work. You will spend 80% of your time writing tests and tweak your implementation to cover your simple functions. If you work for a client, will he pay for it? If you work for a product manager with tough deadlines, will he wait for it?

Alright, let's think again, now we have the solution. We limit the function to only one type and throw an exception in all other cases. This will reduce test cases.

function add2(a) {
    if (typeof a !== 'number') {
        throw new Exception("Please pass in a number!");
    }

    return a + 2;
}

But, this still does not prevent others using your function with any of the values we provided in the samples. Hopefully they will see an error by trying to use the function with a string or any other value which is not allowed. However, this Exception is there. And will stay there. You have to keep this in mind! You can not get rid of it. Your application still poses the risk of stop working at runtime.

Let's do an experiment and transition to PHP and write the same function without a type check and execute similar calls:

<?php

function add2($a) {
    return $a + 2;
}

add2("two");
// "two2"

add2(2);
// 4

add2(array());
// 2

add2(null);
// 2

As you can see, passing in an array results in a different type as in JavaScript. Why do I mention this example here? As a developer you are faced with many programming languages (at least 3). If languages differ in the manner they handle types in this way, the transition from one language to another is very difficult. You have to be aware of all special cases. You have to keep this in mind!

What if you write a second and a third function which uses the result of our "add2" function to calculate a new result?

function add5(a) {
    if (typeof a !== 'number') {
        throw new Exception("Please pass in a number!");
    }

    return add2(a) + 3;
}

function add7(a) {
    if (typeof a !== 'number') {
        throw new Exception("Please pass in a number!");
    }

    return add5(a) + 2;
}
```    

How many branches do you have to follow to evaluate "add7(80)"? We added complexity to our application and: You have to keep this in mind!

*** Pair Programming ***

I'm pretty sure most of you already did pair programming. Usually one developer is typing and the other one is thinking. "Oh wait, add a condition here to check for 
number. And by the way, there is a typo! Fix it." Sounds familiar? 

From my point of view a system with static types is exactly what the "thinking developer" is on a very low level. The good part in Haskell is, you can not get rid of your 
compiler buddy. He is always there and he will always complain about code that does not make sense. Having this buddy with us reduces our cognitive load as developers.
You may have realised how often I have used the phrase "You have to keep this in mind!". For static types all those cases just fall apart. You don't have to be
super-brain anymore. You can not go into production if your application does not compile! You can not forget one special case because the compiler won't let you. And you 
don't have to write tests which depend on types or workarounds.

Finally, how would the simple "add2" function look like in Haskell?

```haskell
add2 :: Int -> Int
add2 a =
  a + 2

No Exception handlinig. You can rely on the result to be Int, plus, the compiler will complain if you try to use this function with any other type anywhere in you code.

Let's try this in Scala:

def add2(a: Int): Int = {
  a + 2
}

Without knowing Haskell or Scala you can dive in and understand what this function is supposed to do, don't you? Even if you write this code and leave the company you work for, the next developer will be able to catch up very quickly and work on/with this code without having to figure out how a language will handle conversions (i.e. adding an array() and 2 resulting in 2).

But coding with static types is painful

Yes, and no. What hurts you more: Deploying code on a friday evening and making the whole application go down just because you misspelled (typeof a !== "numbre")? Or definig static types? Fact is, you can not get rid of runtime errors. Undefined is not a function!

If you have any additional thoughts or comments, just leave a note or ping us on Twitter.