loading...
Cover image for Why learn... a statically typed language?

Why learn... a statically typed language?

gypsydave5 profile image David Wickes Updated on ・10 min read

Most people's first programming language is a dynamically typed, interpreted language - JavaScript, Python or Ruby. These are excellent languages to learn programming with, and you can be very productive with all three of them. But there's a separate category of languages in widespred use: statically typed, compiled languages - C, Go, Java, C# and many others. This article will try to explain the difference between the two language categories, look at their advantages and disadvantages, and then consider what would be a good choice of statically typed language for a programmer who is only familiar with dynamically typed languages to learn.

Who is this for?

The target audience of this article is someone who is comfortable with programming in a dynamically typed language and who is interested in learning a statically typed language, and wants to know why it is worth while. The examples are in JavaScript, TypeScript, Python and Go, but no knowledge of these languages should be required. It is based on my own experience of being a self-taught developer who started working in Ruby and JavaScript and has extended to languages like Go, TypeScript, Kotlin and Rust.

What is a statically typed, compiled language?

There are two pairs of opposites to look at here: dynamically typed vs. statically typed, and compiled vs. interpreted. Let's go through them in that order.

Dynamic vs Static typing

If someone asked you:

What's five added to a banana?

You'd be confused - what do they mean? It looks like they've made a mistake. Maybe they don't know what the meaning of 'add' is, or what a 'banana' is. Maybe they have a different meaning of 'add' to us. Something has gone wrong somewhere though, as their question doesn't make sense to us.1

Programming languages have a way of telling us that an expression written in the language do or do not make sense. They do this by using the type that every value in a programming language has. In dynamically typed languages we only really become aware of types when we use a value of one type in the wrong way - when we say something that doesn't 'make sense'.

For instance, in Python we can write this:

5 + "banana"

Try saving that in a file called typecheck.py and executing it with python typecheck.py. You should get the following error in your terminal:

Traceback (most recent call last):
  File "typecheck.py", line 1, in <module>
    5 + "banana"
TypeError: unsupported operand type(s) for +: 'int' and 'str'

This is a type error - you can tell that from the way it says TypeError in the error message. The error is telling you that you can't + the types int and str together. Which is fair enough; just like you don't know how to add together 5 and a banana, neither does Python.

The type error is thrown by a type checker, which checks that all the types in an expression are being used in the right way. The type checker kicks in when the Python program is run and checks that the two things that are being +ed together are of the right type.2

Type checking can happen at one of two times: when the program is running (commonly called 'run time') or sometime before then. Dynamically typed languages have their types checked at run time - this is what happened with the Python program we ran above; the type error become apparent when the program was run. Statically typed languages have their types checked before they are run.

Type annotations

In order for the type checker to accurately check the types in a statically typed language, you will often have to explicitly declare the type of a variable through a type annotation. A type annotation is a little extra information you add to a variable to say what type it is. In English we can imagine adding type annotations to our nouns and verbs as extra information in parentheses. So our simple sentence:

What's five added to a banana?

Becomes

What's five (which is a number) added (adding is something you do to numbers) to a banana (which is a fruit)?

Which might be good evidence that natural language is not a place for type annotations.

With these English type annotations we don't need to know what 'five' is, what a 'banana' is, and what 'addition' is, to know that this sentence doesn't make sense. We don't even need to know what a 'number' is. We just know that the verb in the middle needs two nouns to be of the type 'number' for this sentence to be valid. We could perform this kind of check automatically just by looking at the words without having to know anything about their meaning - we can't do 'adding' to a 'fruit'. The type checker in a statically typed language works in the same way.3

Let's see a type annotation in TypeScript, a statically typed variation of JavaScript:

var theNumberFive: number = 5

This declares that the variable theNumberFive has the type number, and assigns the value 5 to it.

The equivalent in JavaScript would be:

var theNumberFive = 5

Exactly the same, only without the type annotation.

We can also add type declarations to function signatures. The function add in JavaScript:

function add(n1, n2) {
    return n1 + n2
}

looks like this in TypeScript:

function add(n1: number, n2: number): number {
    return n1 + n2
}

We're saying that the function add takes two arguments, n1 which is a number and n2 which is a number, and returns a value which is also a number.

These annotations will be used by the TypeScript type checker, which runs when the TypeScript is compiled.

Compiled / Interpreted

In an interpreted language such as JavaScript each line of the program is read and executed in sequence, one after the other,4 by an interpreter, which builds up the running process from the program you wrote, line by line.

Compilation is the act of turning the program you've written in one language into another language. For TypeScript, the target language is JavaScript. And during the compilation - at 'compile time' - the type checker will analyse the TypeScript program for any errors.

Compilers are usually used to translate a high level programming language (like JavaScript) into a lower level language like an assembly language or machine code. In the case of TypeScript, the compiler outputs anoher high level language - JavaScript.5

Compiled vs. interpreted is barely ever a cut and dried distinction when with a particular programming language - an interpreter will sometimes have a compilation step which runs just before the code is executed,6 and the output of a compiler will have to be run by an interpreter. In addition, being compiled or interpreted is not necessarily a property of the language itself. Compilers have been written for languages that are normally interpreted, and interpreters for languages that are normally compiled.

For a statically typed, compiled language, the compilation step is where the type checker runs. Type checking is useful for the compiler as it allows it to make optimizations in the performance of the software - if a variable is always going to be a number it can optimize the memory locations used.

Advantages

Type checking catches mistakes

Let's put this all together and write our example natural language 'expression' in both JavaScript and TypeScript we will soon seen one of the advantages of a statically typed language

var five = 5
var banana = "banana"

function add(n1, n2) {
    return n1 + n2
}

add(five, banana)

which will give us the result

'5banana'

Oh JavaScript... more than happy to + anything together.7 It's easy to laugh at this sort of error, but I've seen teams working on JavaScript bugs for days based on a number being stored as a string. It's an easy mistake to make. It's also the sort of bug that will never, ever happen to you - until it happens to you.

But if we try to replicate the same bug in TypeScript

var five: number = 5
var banana: string = "banana"

function add(n1: number, n2: number): number {
    return n1 + n2
}

add(five, banana)

When we compile this with the TypeScript compiler8

add.ts:8:11 - error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'.

8 add(five, banana)
            ~~~~~~


Found 1 error.

The TypeScript compiler has caught our mistake and has even underlined where we went wrong - we can't put a string where a number is meant to go.

This is the biggest advantage of static typing from the programmer's perspective; the type checker makes sure that we're not doing anything stupid like using a string like it's a number. All of a sudden we've got a new level of certainty about how the program we've written will work - without even running it.

Editor integration

But the fun of type checking doesn't end with compilation - far from it. Because a type checker can be run even before you compile your program it can be integrated with your text editor to give you information about your program as you're typing. Because the type annotations declare what the type of a variable is, the editor can now tell you useful things like which methods are available to use on it.9

Compiled code runs faster

Compilation doesn't just translate one language into another; the compiler also looks at your program and tries to work out ways to make it run faster or more efficiently. Recursive function calls get turned into simple loops, for instance.

Disadvantages

This all sounds good - but what are the downsides of using a statically typed, compiled language?

Compilation takes time

Compilation of a program can take a long time. Less time these days with fast computers and good compilers, but still something like two or three minutes in the worst cases I've experienced. If your workflow is reliant on fast, tight feedback loops then you might start to find a compiler annoying you as your program increases in size.

Types need more syntax

If you're used to a dynamically typed language, the verbosity of a statically typed language can be extremely off-putting. Having to declare the types of every variable and function parameter can get very wearing on the eyes. A modern language will try to take away the strain of this by inferring the type of variables where it can, but older statically typed languages like Java, C#, C++ and C can look very verbose.

The world isn't typed

The verbosity of a statically typed language is made very clear at the boundaries of a program - where it interacts with 'the world'. A number of extra steps are required to wrangle the data coming into your system. This becomes apparent when parsing JSON - to get the full benefit of types in your system you'll have to take the general JSON type and turn it into one of your types, which can be pretty arduous. A dynamic language makes this a lot easier (although more open to type errors as seen above).

No REPL based development

Most compiled languages do not have support for a Read-Evaluate-Print-Loop,10 and do not lend themselves to the sort of interactive development seen in languages such as Clojure. If you work in this way you'll miss it - if you don't it won't make a bit of difference to you.

Where should I start?

So what's a good statically typed, compiled language to start with?

If I had a lot of experience with JavaScript then there might be a good argument to try TypeScript, but I find that languages that compile to JavaScript introduce a layer of overhead and tooling that can stop you focussing on the language.

I would advise steering away from Java as there's a lot of unnecessary cruft and complication in the language, some of which is a hangover from C. For instance, compare

User user = new User()

in Java, which always makes me feel like I've written the word user at least two too many times, to this in Go

user := NewUser()

If you did want to look at a statically typed language built on the JVM, Kotlin is a good choice.

The best choice in my opinion is the Go programming language. It has a simple type system (there are no generic types to worry about), the language's syntax is small and easy to learn, the tooling and documentation are best in class, and it's increasingly popular. Take a look at the excellent Go By Example or Learn Go With Tests.

What do you think?

Do you have any experience of transitioning from dynamically typed languages to staticly typed languages. Or vice versa? What were the hardest parts? What advice would you offer? Which language(s) do you think make the best introduction to static typing?


  1. We could say that the sentence is syntactically correct, but is semantically nonsense. 

  2. Try and imagine what would happen if there were no types in a language. All you would have is bits floating around in memory. How would you know where the 'number' started? Or ended? Or which bits of the memory were the program? This is why all programming languages are typed - programming would be impossible without them. 

  3. Although often the type checker does know the types of the values it's looking at - it will know that 1 is a number. This is how type inference works, helping statically typed languages become a lot less verbose. For instance in Go we can just say x := 1 and the type checker will be able to infer the type of x to be a number. 

  4. There are some subtleties to this - often a language interpreter will compile parts of the code on the fly, and compiled languages can have sections of code whose types can only be worked out after compilation when we run the program (at 'run time'). 

  5. This is sometimes called transpilation

  6. This is called a 'just in time' compiler for obvious reasons. 

  7. If you've not watched Gary Bernhardt's JavaScriptWAT video, now would be a good time. 

  8. If you're interested in seeing this for yourself, you will need a NodeJS environment on your computer. Then you will need to install the TypeScript compiler from NPM by running npm install -g typescript. To compile a TypeScript file, i.e. one called add.ts, run tsc add.ts. The compiled JavaScript output will be in a file called add.js if there are no compilation errors. 

  9. This sort of assistance is available in dynamically typed languages, but not to the same degree. 

  10. There is, of course, some nuance to this. For instance languages that run, on the Java Virtual Machine (JVM) can support a REPL by sending the compiled Java Byte Code emitted from the REPL directly to a running instance of the JVM. 

Posted on by:

gypsydave5 profile

David Wickes

@gypsydave5

British. Strong opinions held weekly. No, that's not a typo. Teaches when and where and what I can.

Discussion

markdown guide
 

Great article. I'm a big advocate of static typing as it stops many of the potential bugs even before code hits the production (right now I'm using TypeScript on my job for writing back-end of our current project). Sadly, people get too emotional about their first languages and tend not to use right tools for the job.

 

Thanks for this interesting article!

I just switched from PHP to Golang and I must admit that I feel a lot better now. In PHP, I used many linters and unit tests to ensure the quality of my code whereas in Go it's just built-in.

The developer experience is also incredibly better as IDEs can detect errors even before the compilation, without any third-party tools. The code is also easier to browse.

The only drawback is the compilation step, but in a language like Go, compilation is so fast that you can even just use go run ./... without any perceptible overhead.

 

In PHP, I used many linters and unit tests to ensure the quality of my code whereas in Go it's just built-in.

That's a bit of a trap, you should still write tests, but you don't need to guard against types most of the time, that's for sure. Go still has the interface{} escape route so you might have to do some type checking at runtime once in a while.

You can setup linters as well, with golangci-lint for example.

 

When I said that the tools are built-in, I meant it's part of the SDK. I was not saying it's automatic or whatever. 😊

 

what are the advantages of using golangci-lint over go lint and go vet?

They are both included in golangci-lint. It packs multiple linters for different aspects, check out the Readme in the link

 

Thanks for the feedback Boris - yes, Go really is a lot of fun, isn't it?

 

I wouldn't say that. It better suits my developer experience but there are still some "pin in the ass" behaviors.

 

That's exactly what I'm doing at the moment. I love Python, but I've realized that by learning statically typed language I can become better programmer in general. That's why I started to learn Rust :)

 

Static typing still rules in many parts of the industry. It was interesting to see an article advocating for static typing vs one advocating for dynamic typing (which is what I've seen more often).

I believe the world is moving towards a major change. In the past all introductory languages in most universities was statically typed (with the honorable exception of MIT and friends that went the Scheme/Racket first route). Most universities used languages such as Pascal, then C/C++, then Java or even C# (others include Ada even). So a great amount of developers learned this first and it was part of their natural thought process.

However, these days for the first time many many developers are learning Python as their first language, then using other dynamic languages like Javascript so contact with static typing is a bit more limited.

I seriously wonder what the long term effect of that will be. Are we looking at a future where dynamic typing becomes a new norm? (as this generation begins to have more powers to decide?)

I don't really know but its an interesting question. Smalltalkers and Lispers of the past had often argued that static typing was limiting and too verbose. Most of the industry disagreed. But lets see where we go in the years to come.

 

Oddly enough, I think that we're seeing more of a return to static typing at the moment. The best evidence I have for this is the turn that's happened in the last few years from Ruby towards Rust / Go.

A few years back the Ruby community was flooded with Java developers 'escaping' to a simpler world. And it was simpler and more powerful - Ruby's reflection and type coercion makes a lot of dumb tasks a lot quicker. And you never have to compile it! What could be better?

Fast forward a few years and now all those Ruby developers are 'escaping' to Go (and Rust). All the benefits of static typing that they've been missing in Ruby are available, but now the compiler is fast and the tooling is amazing.

I think the future is going to be languages with ways of checking correctness built in, and explicit typing (and type checking) is going to be a part of that. But who knows.

 

I think the folks that start off with dynamically typed languages will likely naturally come upon on the need for a type system as they try to more carefully design their production systems. This has been my experience.

I think the dynamic languages are simply a better introduction to programming because they subtract a layer of complexity and allow you to focus on the basics.

 

Typings is also useful for our coworkers to jump into a project or future us when we dive back into an old project.

I find it much easier to re-figure out how everything is tied together when there are typings around.

 

Which language(s) do you think make the best introduction to static typing?

I think TypeScript is really a great choice for this.

It has smart inference, can be configured to be less strict and you can always opt-out with "any" in case you need to.

It has many features that reduce verbosity, like "typeof" or "infer" to derive types from existing code.

Take for instance this snippet from above, I'd shorten it to:

const five = 5
const banana = "banana"

function add(n1: number, n2: number) {
    return n1 + n2
}

add(five, banana)

Now it looks almost like plain JS and produces the same error.

 

Nice article.

I think it should be very obvious to any programmer that the benefits of static typing and compilation outweigh greatly the inconveniences resulting from the laziness we all have.

After all, how often do you ever want to assign a number to a variable and then assign a list of strings to the same variable? Or why wouldn't want to get an error before it happens live ? Then again, it must be for the same reason some developers do strange things like not throw an exception when something unexpected happens (I don't understand that logic either).

TypeScript - for example - gave super powers to javascript and coding is suddenly fun and productive.

TypeScript gives back the security that you lost in javascript where every tiny mistake can make you waste many hours if your application is more than a few hundred lines of code. It is a great example of why static typing and compilation is a must for every good code base.
Of course TypeScript added many other things like easier inheritance which contributed to much better code, but enforcement of type checking and compilation were a huge improvement for the whole team.

 

Loved the article David, and agree with the conclusion. Go is more or less becoming a general purpose statically typed language with a really fast compilation step. The syntax is quite simple, it has type inference so it's not required to be verbose about types all the time.

 

Thanks for the article! I liked your look at the tradeoffs.

I write node.js applications and try to keep a very airtight standard of correctness; so my first statically typed language is typescript!

The funny thing about the “world” part you noted is that in dynamically typed languages (JavaScript) implementing input validation for API endpoints still takes a great deal of work. However, with io-ts I’m looking forward to using the types as validators. Of course this won’t check everything my custom validator would, but it seems like a useful start! lorefnon.tech/2018/03/25/typescrip...

 

Really great article!

My first exposure to typed programming languages was C++ in college. Afterward, I did a lot of work with PHP, Python, JavaScript and Ruby. It wasn't until I had to learn Go last year that I really embraced statically-typed languages.

I found that the code I ended up writing had far fewer runtime errors because I didn't have to worry about an incorrect type being passed into a function. Although this could also be because I've gotten a lot better at testing as well. I echo your recommendation for Go for the reasons you mentioned as well as some of the other benefits it has around distributing your finished product and the ease with which it supports other platforms and processors, like the Raspberry Pi for instance.

 

Great article! Thanks for writing this.

Perhaps this helps: repl.it/languages

 

Great article. I cant think of a graph which dynamically typed languages have learning curve to be easy, but it goes down to complexity, and vice versa to statically typed languages