DEV Community

Ben Halpern
Ben Halpern Subscriber

Posted on

Convince me that types are awesome

Suppose I've mostly used untyped programming languages and don't have a great appreciation for types.

Let's do some convincing!

Top comments (60)

Collapse
 
deciduously profile image
Ben Lovy • Edited

With types, you can make illegal states unrepresentable. Instead of storing a bunch of strings, you can store a Name and and Address, and can encode into the type system that ContactInfo must contain a phone number, an email address, or both but not nothing. By encoding that requirement into the type system, it becomes impossible to represent an incomplete record in your program. It also aids refactoring - if your business logic changes, your compiler will guide you through the refactor for ensuring your whole codebase reflects the change.

Collapse
 
johnpaulada profile image
John Paul Ada

I agree with this. I recently tried out ReasonML and it's awesome! Nevermind less rope to hang yourself with, it really gives you no rope! I'm still trying to learn more about it though, like about phantom types and some clever ways to use types to enforce structure and rules in your code without using a single conditional statement.

Collapse
 
briwa profile image
briwa • Edited

One of the best advantages of types is that it lets you catch errors before the code is executed. With the right precompiler/language server, you can see that your code is faulty even in your IDE. I can only speak for Javascript to give you an example.

function sum (num1, num2) {
  return num1 + num2
}

const total = sum.apply(null, [1,2])
// Returns 3.

It is a sum function that takes in two numbers as the arguments. However, a common mistake is to think that the sum function can take in multiple arguments then get a sum of them. Maybe the function is imported into a different file and you might have already forgotten about the interface.

const anotherTotal = sum.apply(null, [1,2,3])
// You're expecting it to return 6, but... it's actually still 3.

Unfortunately, you wouldn't know that you're making a mistake until you run the code in the browser and test the feature that is using the code, or you have your unit tests running. The scariest part is, in the browser, it won't even throw an error.

With Typescript, you can spot this in your IDE even before it is run anywhere else. This is how it looks like, more or less:

const anotherTotal = sum.apply(null, [1, 2, 3])
//                                ❌ ^^^^^^^^^
//  Argument of type '[number, number, number]' is not assignable to parameter of type '[number, number]'.
//    Types of property 'length' are incompatible.
//      Type '3' is not assignable to type '2'. ts(2345)

This is just a really simple example. Of course, the mistake is obvious on this one, but, as the software gets more complicated, the argument for the necessity of typing (IMO) is going to get more and more valid. For more info on this topic (Typescript), I've written an article about this (shameless plug 😋):

Another advantage that I find it useful is the documentation. With a good IDE integration, Typescript (or any strongly-typed language) code is sort of self-documented, but if you want to take it up a notch, you can easily generate a user-facing documentation from it without a hassle, using libraries like Typedoc. It would help a lot for, say, you're creating a library in Typescript and you want to put up a page for the API documentation.

But I also understand the other side of the story. I've read this article, and I can see that the overhead of using Typescript (or any strong-typed language, for that matter) can be too expensive to some. I would say, if you still want to skip typings, at least have a good documentation and a good testing coverage. Without these two (and also typings), I'm not sure if maintaining the code would still be productive when the code is growing in complexity and also the number of devs maintaining it ;) Just my two cents.

Collapse
 
mandaputtra profile image
Manda Putra

How about user input? you define as a number but user inputted string? you catch the error or the compiler throw the error?

Collapse
 
briwa profile image
briwa • Edited

If you're talking about Typescript, it won't catch them, because in my opinion Typescript is not meant to be used to define user interfaces and user inputs (unless you're talking about .tsx files, but it's going to get too specific, which is out of topic). In general, side effects I/O such as user inputs should be validated/specified by an end-to-end test.

In HTML, you can definitely set a "type" to your inputs (for example <input type="number" /> for number-only inputs). But again, the context matters. If you have a use case and you have your own set of expectations of what a strongly-typed language should do, maybe I can understand your problem better.

EDIT: @stojakovic99 answer is more apt, I guess it's better for me to say that strongly-typed languages help you to define/catch errors before runtime, and outside of that (like user inputs) it should be handled differently (maybe an e2e test).

Collapse
 
stojakovic99 profile image
Nikola Stojaković • Edited

User input is done during the runtime, so, you need additional measures to catch the error. Personally, for TypeScript I use class-validator and treat whole API request as DTO. For Java I use bean validation.

Thread Thread
 
mandaputtra profile image
Manda Putra • Edited

Yes, that's it, I use typescript before I do what you do too, this is why I'm still not using it (Unless I use Angular). if I want to sell TS to somebody else definitely not the typing system. the IDE and OOP make the refactoring code better, autocomplete, what else, typing system doesn't make less bug and maintaining code better.

Just use this, basarat.gitbooks.io/typescript/doc... reading this makes me use typescript for 2 months. then back again to plain javascript, some problem with another lib still sometimes occurs in TS (usually typings). I can't stand that :(

Collapse
 
jckuhl profile image
Jonathan Kuhl

User input cannot be caught by Typescript, because user input is brought in at runtime.

You don't run Typescript, you compile it to JavaScript and run the JavaScript, so by the time it's running, type information is discarded.

However, Typescript will help you prepare a function to receive user input by setting up the appropriate types, but that's as far as it can go.

Collapse
 
mortoray profile image
edA‑qa mort‑ora‑y

Slow down there! You already got a response from me today about switch. :D

I can talk for days, literally days, about how awesome types are and the problems with untyped languages...

...though I figure I might not have to, in about a year, as the dev codebase grows, and the contributors increase, you'll look back at this post and cringe. ;P

Collapse
 
hamishdickson profile image
Hamish Dickson • Edited

Imagine you have this function

def combine(a, b) = ???

how would you test this does what you think it should do? The way this is written combine can accept any a or b and can return anything

Now lets add some types

def combine(a: Int, b: Int): Int = ???

For your program to compile a and b must both be of type Int and combine must return an Int. This means rather than testing every possible input and output, you now only consider a tiny subset of the inputs/outputs you originally started with

Here's another example. This function is parametricly polymorphic in T (that's what the square brackets are saying), we must take a T and must return a T

def thing[T](a: T): T = ???

Can you think of an implementation other than this? Remember T can be anything, so the implementation must work for any possible T

def thing[T](a: T): T = a

I can't. And in fact the types have limited the space of implementations for this function down to just this and things like just throwing an exception or doing a side-effect.

Depending on your language you can go really far with this. In lots of FP languages OOP is never used and instead ad-hoc polymorphism (aka typeclasses) is preferred. The idea is really just an extension of the idea "my program won't even compile if the types don't work". If your language has higher kinds, you can meaningfully talk about things like IO and sequential/parallel computations. If you have a language like Idris with dependent types, you can do type-driven-development, where each time you compile your program you do a "proof of correctness" (note the quotes before you start yelling at me :) )

Collapse
 
foresthoffman profile image
Forest Hoffman

When you use a library with seriously shitty documentation, it's an absolute hell to have no tool to actually display you what x or y method expects or does.

I enjoy writing in TypeScript for this very reason. The documentation can be garbage, but at the very least I can look at the distributed definition files for guidance.

Collapse
 
shiling profile image
Shi Ling

I used to cringe at JavaScript for the lack of types because I feel unsafe, but then after years of using JavaScript, I've grown pretty comfortable with it and just know where I needed to do type checks and coercion. Now, I cringe at how slow I work with Typed languages because of the additional mental effort to think about the type of every variable and the compilation time. But really, I've just gotten really spoilt, because the extra mental effort and time waiting for compilation is not significant really.

The thing I came to realise about why I would pick Typed languages is to make it easier to work with junior developers. I used to feel unsafe with JavaScript and now I feel comfortable with it, and the reason is simply because of experience from making all sorts of mistakes before. I find myself frequently pointing out to junior devs now where they have missed a potential type error and need to add type checks and/or type coercions when working with JavaScript. At the end of the day, I think Typed Languages have better developer experience. Plus, I really rather a machine tell me that I have made a silly mistake than having wasted the time of a colleague pointing it out to me later on.

Collapse
 
stereobooster profile image
stereobooster

Let's clarify a bit. You may think untyped language is a language which has no types (the name suggests it), but in reality, it is a language with the single type (it doesn't make sense to talk about types if there is only one). Examples of untyped languages: untyped lambda calculus (lambdas), assembly (bit strings). Most of the languages have types. Let's take, for example, ruby (which @ben likes):

1 + ""
String can't be coerced into Integer
(repl):1:in `+'
(repl):1:in `<main>'

This is nothing else than type error ¯\_(ツ)_/¯. So ruby is typed language, but there is no static type checker (at least not built in one, there is sorbet project which will be part of ruby 3).

What Ben meant to ask, I guess, is how dynamically type checked languages differ from statically type checked languages.

More about terminology dev.to/stereobooster/pragmatic-typ...

Collapse
 
gypsydave5 profile image
David Wickes • Edited

First up

Suppose I've mostly used untyped programming languages

This is unlikely, unless you program only using bits - but that's not efficient or terribly fun. You probably mean that you've been using dynamically typed languages - which definitely have types, but do not have typed variables or a type checker to make sure that you're not, say, assigning an integer to a variable which holds strings.

It's possible that I may have written an article to address this already...

Collapse
 
arschles profile image
Aaron Schlesinger

This is gonna be a really crappy persuasive argument, but I wanted to give pros and cons of types, and you can decide for yourself 😄

Pros:

  • More self-documenting
    • what does this function expect?
    • how is it gonna use it?
    • what is it gonna return?
  • IDEs can provide richer & faster intellisense / code completion / go to definition / inline docs \
    • Check out XCode for Swift and Visual Studio for C#. Both are 🔥 at this stuff for their languages
  • Usually less boilerplate-ey tests to write
  • Some groups of runtime errors are impossible. Some examples:
    • In some languages, there's a type of array that cannot be empty, so myarray[0] will never throw
    • Others have literally no exceptions. Functions that might fail will always return a type that can either be a successful value, or an error. You have to deal with both or your code won't compile

Cons:

  • Some typed languages have horrific compiler error messages if you mess up. Good luck figuring out what you did wrong, let alone fixing it
  • Typed languages often have a steeper learning curve
    • There are exceptions on either side, of course!
    • Go is easy to pick up for lots of folks
    • ... and Ruby has magic in it that can trip folks up pretty early on in their learning
  • There's math behind type systems (no joke! "Category Theory" on wikipedia). Some languages will force you to learn some of that, even if they don't mean to
  • Some typed languages have "generics" - being able to write functions that can handle any type. It's handy and powerful, but these functions can be super confusing for the person calling the function

That's all I can think of for now. I think typed languages pay off after you get to a medium sized codebase. I usually reach for them on day 1, because I've been burned a lot by errors in dynamic languages, that typed languages don't have.

Hope this helps!

Collapse
 
pinotattari profile image
Riccardo Bernardini

Do not ask this question to an Ada programmer: we are the hard-core of strong typing :-) :-)

Seriously, as many said, correctness is a big plus of strong typing. I say strong typing and not just typing since C, for example, is typed but not strongly (it converts silently between integer/pointers/chars/float/...). In a strongly typed instead no conversions are done by default and mixing different types by mistake is impossible.

Few years ago I wrote a program that made a .dot graph starting from Ada code, showing the dependence among packages. The code uses both package names and the names of the files that contain the package. Initially I used String for both of them, but I discovered that I was keeping mixing them (passing a filename when a package name was expected).

The solution was very simple: I just defined

type Filename is new String;
type Package_Name is new String;

and the compiler prevented me from mixing them. A good portion of potential bugs never made beyond the compilation stage.

Of course, strong typing reduces a bit the flexibility in your code. In languages like Matlab or Rudy variable can change value type dynamically and you can call procedures with any type of parameter you want. Indeed, because of this, I find this kind of languages a good choice for short or "short lived" fast-and-dirty software: you write it in little time and it does the work you wanted. If it is short lived, you do not care about maintainability, if it is short, it is quite easy to master it anyway.