231 comments

[ 3.7 ms ] story [ 217 ms ] thread
There has been some research done on how programmers interpret gradual typing systems, and it turns out that it breaks many expectations and ends up being confusing.

Explainer video: https://www.youtube.com/watch?v=iRf9l3Bz7nA

Original paper: https://cs.brown.edu/~sk/Publications/Papers/Published/tgpk-...

Interesting paper thanks for sharing.
Note that the paper is concerned with run-time type checking and glosses over the fact that most/all of those code samples in the survey would be rejected at compile time by a static type checker (or at least by TypeScript).

Their conclusion that "erasure" is disliked and unexpected is effectively saying the underlying dynamic language's behavior is disliked and unexpected.

Of course it's possible to deliberately or accidentally coerce TypeScript into violating its type constraints and behaving in JavaScript's disliked and unexpected ways, but the survey questions would have to be substantially different, and likely the responses as well.

yes
Usually I’d downvote “non-contributing” replies like this, but for this one I’m making an exception.

Why? The answer to the question is utterly obvious, and that one word is really all you need to answer it.

Was working an application written in JS

Converted it to TS. In the process of fixing up types, it found bugs for us. Value demonstrated

Typescript from the ground up is absolutely worth it, starting with how it gives you full IDE inference for all those "big blob of options" objects that a lot of JS methods use.
Any IDE could do that for JavaScript; it has nothing to do with TypeScript.
I agree, and I've found that WebStorm (and other JetBrains software with JavaScript support) does a great job at it, although I do find TypeScript improves the experience.
How could this be done for JavaScript without any mechanism for signifying the type of object that is expected?
JS files can use JSDoc, which typescript handles (when you edit a JS file in VS Code, you're already secretly using typescript).
If you're using a third party dependency that has type definitions tsserver / VSCode will pick it up regardless of whether you're writing js or ts.
I don't believe many understand how much tsserver is doing behind the scenes in Vscode for JS code bases.. Of course any IDE can do that, because they can use tsserver via the language server protocol haha.
Yeah I use it with CoC in nvim and find it works as well as VSCode.
Type inference from analyzing data flow.

Especially if you avoid making everything global.

Instead of focusing on what TS does not, focus on what it does.

TypeScript is not sound; JavaScript isn’t either. The first one will catch some type errors, the second will catch none.

Syntax highlighting, linting, testing, and now type checking: every step can make you more confident about the code you ship, before it even hits the browser.

You can forgo using any help and probably you’ll code faster, but, again, you lose confidence.

Tests are the only thing that give me confidence about any code I write, Javascript or otherwise.
And compilers are effectively a rudimentary form of test. They are not sufficient, nor necessary but they do validate some basic assumptions about the code.
They also remove redundant testing (you only need to prove something fulfills certain properties once as long as you have a type for it); and the more advanced the type system the fewer tests needed.
I would upvote you twice if I can for that compiler is a test thing
If for no other reason, I would like Typescript for flagging when you typo some property - e.g. foo.FooID vs foo.FooId.

I've lost a stupid amount of time in regular JS because of things like this that make me want to pick up the computer and throw it out the window.

Decent editor does that now. I'm using atom on daily basis and it's helping me with that. VScode and intellij does the same
Doesn't the functionality for that in most editors leverage typescript typings, so that it's not really independent of typescript?
Ah I misread the parent comment. I thought it was more like "I wish typescript has this anti-typo functionality"

You're right that it's not independent from TS.

you can add JSDoc annotations and use a modern IDE like vscode or webstorm, etc.
Tests only help if they are written, and if they cover the code in question. Both of things are unfortunately not always given. If things are not covered by tests type systems are still better than nothing.
It's not just about confidence. It's also about the speed you can use/refactor the code. Suggestions, type information on hover and instant feedback if you forgot, used wrong order or type, misspelled or simply don't remember exactly signatures or auto generated documentation means a lot when writing code. You can move much faster when working on typed code. Nobody ever claimed that types are replacement for tests. It just means that your tests don't require obvious clutter anymore, you can focus on more complex/important test cases to cover.
FlowType has better soundness than TypeScript. I like to think of TypeScript as a "feelgood" typesystem (especially how it handles predicate functions, type variance in function arguments, and type casts). The only downside is that Flow's typesystem is volatile. You _might_ find it painful to upgrade between versions.

With that being said, I still prefer Flow. Not sure how relevant this is, but at my company we mostly write Flow in the teams. For libraries I tent to provide both (code being written in flow, with with .d.ts provided). I also maintain both 3rdparty ts and flow type defs.

TS tooling blows Flow out of the water, though. But, I've heard flow team at facebook is focusing a bit more on DX and on making the typesystem a bit more ergonomic and expressive. :)

> TS tooling blows Flow out of the water, though

This is the dealbreaker. Building Flow in OCaml was a mistake, full stop.

I'm not convinced the level of soundness we're talking about is important except for the people who like to wax philosophical about PL theory. TypeScript has never been "unsound" in a way I actually care about and I would argue if you get caught up in a soundness issue you're already doing something completely crazy.

It's true that ocaml has different contribution entry profile. The only "issue" here is that flow being a js tool, naturally would attract more js contributions, but that's it. You can't say ocaml itself is not fit for purpose because this kind of tool is pefect use case for ocaml. It would be interesting to see flow rewrite in flow itself thought.
It being written in OCaml is why the tooling is poor. TypeScript integrates perfectly with most build tooling (e.g. Webpack) because it's just JavaScript like every other tool. To run Flow everything has to be shelled out to an external bin which adds a lot of flakiness and manual configuration if you want to integrate it with your build config.

I understand OCaml is great for writing meta languages (ha) but it's bad for the end user.

Maybe, but personally I don't use any tooling at all, just raw js with types in comments and npm test as `eslint . && flow check && jest`.
I use flow as well at the company I'm working for. I found lightweight setup that works surprisingly well for me. It started as experiment to use types in comments. There is no transpilation phase at all, just pure js with types in comments. All my projects use simple npm test "eslint . && flow check && jest" and there are no prepublish hooks, no manglings, no source maps etc. It means you can edit linked modules in place and get instant feedback. The workflow is very fast and non intrusive. It looks like this [0] or this [1] etc.

[0] https://github.com/appliedblockchain/parser-combinators [1] https://github.com/appliedblockchain/helpers

Javascript is dynamically typed -- that does not mean it has no types. You are free to add dynamic type checks anywhere you want. I view these as critical for APIs even if you decide to use typescript. I also view them as critical for third-party interfaces where you may not be able to test integrations completely.
Doesn't typescript enable things like renaming an identifier and all it's usages, without doing it as a global text replace, or worrying that you might have renamed something you shoudldn't? Or is the type checking so "leaky" in a typical project that that kind of luxury (That would be done every day by Java or C# developers etc) isn't really available anyway?

Unless it's a massive overhead to use typescript, autocomplete, renaming etc seems like they would make it worth it on their own.

I use what you describe on a regular basis in typescript without issues indeed.
This will depend on the project, as TS can go from very loose to very strict depending on the settings. For a project with all strict settings turned on and casts minimized, then I find refactoring is an absolute breeze. I have successfully done huge refactors that completely worked without any (known) regressions as soon as all the tsc errors are resolved. And your editor combined with the language server drives a lot of the refactoring (similar to Java/C# as you alluded to) To me this is the real killer feature of TS over JS.
Mentioned as an afterthought at the end: IDE support for typescript, the best part about using the language.

Not mentioned: type annotations serve as a form of documentation that gets statically verified. The verification strategy has some cracks, and there's a (fairly small) chance that the documentation will end up misleading. But API documentation for javascript projects generally sucks, and typescript helps to fill a lot of the common gaps.

i'm tired of devs that claim that TS and GraphQL are "self-documenting" and don't need to write any documentation. it doesn't fill in the common gaps, it's just an excuse to not have to think about or talk to users.
Do you think this claim is specific to TS/GraphQL? I've heard it from devs well before either existed.
GraphQL libraries often have built in documentation abilities that make it trivial to document, extract, and make pretty api docs fairly trivially. It’s still like pulling teeth to get developers to use the functionality at all.

That said, the appropriate reaction is to laugh louder the longer they make the claim.

In my experience it helps devs move the documentation up one level. By that I mean they don't just document the mechanics and type signature of the function, but instead document what the function is used for and why it exists.

You can already write decent docs in plain JS, but I've found that having the types already documented by the compiler helps devs move up that level of thinking automatically.

I'm the author and the claims that Graphql and typescript replace documentation is madness
...good thing that there's pretty much no one making them, then.

There's plenty of people saying that it effectively augments documentation, and provides a form of it that's quite useful.

TBF, Typescript's VScode's integration is about the best thing the language has got going for it.

It's very likely what's driving adoption right now.

These kind of articles seem to be commonplace and I frankly do not think they provide any productive value. "any" is supposed to be the escape hatch as TypeScript is designed to be incremental. Anyone with a semblance of understanding knows that TypeScript transpiles into regular JavaScript, you don't run TypeScript code in the browser.
Friendly tips: you can replace any with unknown. Other friendly tips: runtime type checks are available https://github.com/gcanti/io-ts https://github.com/pelotom/runtypes
Can you please provide more detail when it's best to use runtime type checks? For webapps with strict TS config, I'm failing to see the advantage. Runtime errors can occur when crossing boundaries from server API to UI but when you discover the exceptions, you go fix your contract.
1st it's easier to identify which contract is inaccurate, or even whether the contract is inaccurate or not in the first place

2nd it's more convenient for both the end-user and the developers that errors are handled this way

Guaranteed almost 100% type soundness!!! Non TypeScript user will hate this.

Config it to maximum strictness!

Add in runtime type checks Parse, don't validate https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-va...

Bring type to business logic, e.g.

RegisteredUser { userId: number, email: string }

AnonymousUser { userId: number, email: null }

User = RegisteredUser | AnonymousUser

Handle errors ala Rust,

Return errors/errortype rather than throws

Utilize structure to determine result, e.g.

Failure { error: Error, value: null }

Success<T> { error: null, value: T }

It is worth it. My experience with TypeScript is a whole lot different.

Conspiracy theory inducing facts:

- LogRocket's main use is monitoring bugs in browser application

- Airbnb is LogRocket's customer

I'm interested in the way you describe handling errors here. Do you have any real world examples you can point me to? Thanks
Check out the Either monad in Haskell. It has an implementation in the JS library funfix.
I would have gladly shown it to you, but almost all my code is proprietary

Here's a small example:

```

function fetchCurrentWeather(){ return fetch(...) .catch(() => ({ error: new NetworkError(), value: null}) .then(res => res.json()) .then(maybeWeather => Weather.is(maybeWeather) ? { value: maybeWeather, error: null} : { error: new DecodeError(), value: null}) .catch(() => ({ error: new DecodeError(), value: null})) }

// And then you use it somewhere in you react app

async initialFetch(){ try { if (this.state.isLoading) return; this.setState({ isLoading: true, weather: null, error: null }); this.setState({ weather: await fetchCurrentWeather().then(reault => { if (result.error) throw result.error; return result.value; }) }); } catch (error) { this.setState({ error }) } finally { this.setState({ isLoading: false }); } }

render(){ return ( <div> {this.state.isLoading && ...} {this.state.weather && ...} {this.state.error && ...} </div> ) }

```

Darn, sorry for the bad format. Typing this from phone. I guess you gotta copy that to your editor and apply some formatter.
Why is this better than throwing errors or using Promise.resolve()?

I know it is inefficient because creating a new error with a stack trace each time is bad for performance. However, you can also throw errors which do not extend Error to avoid this. Destructuring the output and explicitly checking for errors every time seems to be quite the hassle.

Because the error/success is encoded in the type, you are forced to handle the error case. If you aren't also wrapping throwable functions in `try/catch` then you have a lot of unhandled error cases.

If you know that something truly isn't going to error then you can just force cast it as `foo as Success<T>`. That will still blow up at runtime if its not a `Success`.

Alternatively, you could introduce a monadic chaining that is able to pipe `Result<T>` objects through many functions then handle at the end.

Check out the chapter on handling errors in the book “Programming TypeScript”, it gives a solid pattern for achieving something very close to Rust’s Option.
I would like to recommend fp-ts over other JS/TS functional libraries, already contains types like Option
Do you mean:

    Failure { error: Error }
    Success<T> { value: T }
I assume you want value or error not value and error.
eh. the pattern he is trying to mimic from elixir is:

{:ok, value}

or

{:error, err}

so, you would be correct.

Limitation in TypeScript. Even in TS 3.6.x you can't do

```

if (result.error) { ... }

```

on a variable that possibly returns { value: T }

It'll complain that result may possibly have no attribute error

You can do:

  if ('error' in result) {
    console.error(result.error); // ok: result inferred as 'Failure' 
  } else {
    doThings(result.value) // ok: result inferred as 'Success<T>'
  }


Even though you used string there, it's pretty type-safe because if you have a typo in your string, the inferred types will propagate other type errors

  if ('errors' in result) { // typo
    console.error(result.errors); // Type error: result inferred as 'never' 
  } else {
    doThings(result.value) // Type error: result inferred as Success<T> | Failure
  }
...

What do you expect here? The compiler is doing exactly what is expected.

I started doing the Fail and Success type pattern after learning about it in Elixir/FP world. Its been a lifesaver
the title completely doesnt match the content, this is pure clickbait and logrocket is so shameless about it. i grit my teeth and click but am completely unsurprised at yet another low quality list of arguments that have been rehashed over and over.

here's a less well known, more rigorous study: http://earlbarr.com/publications/typestudy.pdf - To Type or Not to Type: Quantifying Detectable Bugs in JavaScript

they mention a time tax of 262 seconds median for TS annotations for their set of 400 bugs. almost 4.5 minutes. so idk, there seems to be some math missing in this paper about the actual trade offs. so maybe you get a 15% improvement in "detectable" bugs found, at a 13.3% time cost (60 minutes /4.5 minutes) per hour of work. worth it...? are your devs expensive? more expensive than QA.
Types are easier to read, reason about and maintain than tests, particularly if it's QA writing the tests instead of engineers.
I've found TypeScript and React with VSCode to be incredibly productive for web development. If you're on the fence, give it a shot.

TypeScript is a superset of JavaScript, so it brings a lot of baggage along with it, but if you're writing frontend code it's a real pleasure to work with compared to JS.

While TypeScript isn't perfect, it surely is a major step forward compared to plain JS! Newer features like conditional types and mapped types open up new possibilities. My colleague actually recently gave a fantastic talk about these advanced features at the TS Berlin Meetup, highly recommended watch if you want to see what it's like pushing the boundaries of TypeScript: https://www.youtube.com/watch?v=PJjeHzvi_VQ
Still compiles Down to js where you lose all type checking.

Have you ever looked at a compiled js file? Things like spreading objects I to each other

{...a,...b}

End up being a nest object assign which is actually slower than spreading.

Slower and more widely supported.

Also iirc you can set the compile target to esnext and it will compile to the object spread expression.

Checks don't need to be in the JS, they just need to be there at compile time. No differently than a binary generated by Haskell; and you have source maps to replace debugging symbols too.

And as mentioned by another user, you can choose your compile target to a specific version of JS which does support object spreading.

Actually, Haskell compilation produces more type information available at runtime than Typescript's compiler does. For example, algebraic data types turn into tagged unions at runtime on Haskell, allowing pattern matching to operate on them.

Whereas on Typescript the compilation process erases all this information, making it impossible to evaluate many such expressions.

There are multiple 3rd party solutions to this, such as: https://github.com/pelotom/runtypes

> algebraic data types turn into tagged unions at runtime on Haskell

How does that work? I know next to nothing about the GHC, but in Haskell I would assume some tag is associated with each constructor (probably an integer) for the ADT and that the actual pattern matching gets compiled down to simple comparisons to either that integer or the right function for the more complex cases (pattern matching strings and other values).

TypeScript essentially does that, except that the tags are not going to be optimized by the compiler (they will remain strings if the dev uses them), and the "pattern matching" is just regular conditional checks (if, ternary or switch) with even the exhaustiveness check being done as a manual hack introduced by the dev and checked at compile-time (the else or default case assigning the value to the never type).

As for runtime values, Haskell also needs to validate types when deserializing, although that will be done by the deserialization libraries such as Aeson instead of deserializing and then optionally validating like in TS.

Yes, you're right, it should be an integer comparison check ultimately.

> TypeScript essentially does that, except that the tags are not going to be optimized by the compiler

What do you mean by this? Typescript erases type information during compilation. So you would not be able to emulate pattern matching against types on TS. Or you could if you manually added some common tag to every single data type like interface.

Or are you talking about just compile-time type checking on TS?

> Or you could if you manually added some common tag to every single data type like interface.

This is how you emulate them, I took it for granted and neglected to explicitly mention this is a moderately popular convention in TypeScript (those are the tags I referred to). fp-ts for example uses it all over the place.

With this the end result is essentially the same with compile-time type-safety for everything, and compiled down to an untyped binary or an untyped JS blob.

> except that the tags are not going to be optimized by the compiler

These strings should all be === at least if they are compiled together (the JITter could even intern them to make sure), I believe -- so it will typically be a single 64-bit ptr compare whether it's a string or int. Only in the case where you're constructing the DU tag at runtime for some reason would you need to compare the strings.

friendly tips: runtime type checks are available https://github.com/gcanti/io-ts https://github.com/pelotom/runtypes
Why would you want runtime checks over static checks? The one thing I can think of is type asserting input from other js code, but surely you just want that on the entry function to your code, not in the internal bits.
TS compiled can still be faulty if the app is injected with data of different type

Runtime type checks is the lost link between static checks at compile time and arbitrary i/o data type in compiled js code.

You define a type in a similar way you would define TS types. At compile time it would give you the typescript type checks. At runtime it will help you determine if data has the correct type exactly as your typescript app expected.

There is extra work though, handling error if there is a faulty data type

Runtime type checking is necessary to ensure data received from untrustworthy sources matches compiletime types.
> Still compiles Down to js where you lose all type checking.

Haskell compiles down to machine code, so...

This is an often expressed sentiment that I feel misses the point by a mile, which is that Haskell (as well as other languages in its class) _forces_ you to actually write programs that are soundly typed at compile time. TypeScript very explicitly does not, instead taking an approach to typing that's more in line with languages like Java or C# (which rely on both compile time and runtime type checking to enforce a program's types) but failing to provide the runtime type checking to back it up. And even that wouldn't be so much of a problem if it didn't compile to a language as dynamically and weakly typed as JavaScript.
> I feel misses the point by a mile

I disagree. The point to which I refer was stating that because language A with type safety of some degree compiled to another form B without it obviates A's type safety altogether.

The destination isn't the point; it's the compilation of the beginning.

(comment deleted)
> Still compiles Down to js where you lose all type checking.

While true, what's the point of this argument? You cannot get language-level runtime typechecking in the browser. At all. With any toolchain.

Several compile-to-JS projects do ship a type-checking (amongst other things) runtime to the browser.
It's better to do it statically and save yourself the overhead at runtime. I don't see why you'd even want it the other way.
> Still compiles Down to js where you lose all type checking.

Yes and C still compiles to assembly with no type checking. What's your point?

C is an infamously unsafe language, so the question might rather be what _your_ point is.
> C is an infamously unsafe language, so the question might rather be what _your_ point is.

Feel free to insert Rust or Haskell or whatever your language of choice is in place of C and my point remains the same. Sure you can include type information inside the assembly output and some sort of runtime code to handle that, but that doesn't change the fact that your compile target has no type system. Typescript could do the same if they desired, and in fact I think they offer the option.

Compiling a language with a type system to a language without is not a knock against that language's type system. Compiling typescript's decently comprehensive/expressive compile-time type system to javascript's basic runtime type system actually seems like a step up compared to a lot of other compilation targets.

Like I have said elsewhere, Haskell and Rust and other such languages actually force you (to varying degrees) to write programs that are soundly typed at compile time. TypeScript explicitly doesn't, and instead treats program types in a way that's far more like Java or C# (which rely on both compile time and runtime type checking to enforce a program's types, and even at that are not entirely sound) without the runtime type checking to back it up.

> Typescript could do the same if they desired, and in fact I think they offer the option.

They very explicitly do not and have stated severally that it is not within the goals of the project to do so, which is something anyone actually familiar with the language should know.

> Compiling a language with a type system to a language without is not a knock against that language's type system

When you don't cherry-pick statements out of context, the point of what I said becomes rather obvious; if a language doesn't actually guarantee type safety at compile time, having a primary compilation target that takes a fascinatingly lax approach to data types is objectively worse (wrt type safety) than having one that's more strict.

Your posts don't make any sense. Rust compiles down to assembly too, but it's a safe language, so what? The compilation target has nothing to do with the relative safety of the language.
> The compilation target has nothing to do with the relative safety of the language

Yes, obviously. Almost as though the fact that all languages end up as x86 or ARM opcodes at the end of the day has literally nothing to do with the merits and demerits of their respective type systems, but people keep bringing it up like some kind of gotcha when type systems get criticized.

Mostly due to the way it's used for performance reasons. You can take other typed languages and pass around object information in byte buffers instead of structs as well, and subsequently lose all your type checking.
No, mostly due to actually having a weak type system. I have no idea why people think C is in any way strongly typed just because it has static types.
> I have no idea why people think C is in any way strongly typed just because it has static types.

Do you think that's an opinion I have expressed here?

> Still compiles Down to js where you lose all type checking.

And C, Rust, Haskell, etc., all compile down to native code where you lose all type checking, so what? Outside of compiler bugs, what is statically proven at compile time doesn't change at runtime. (Yes, C also has a notoriously loose and abusable compile time type system, but that's a different issue.)

> Things like spreading objects I to each other

> {...a,...b}

> End up being a nest object assign which is actually slower than spreading.

Yes, if you use the default target (ES3), typescript emits JS compatible with downright ancient browsers which is super portable at the cost of being less efficient than targeting modern JS features, like spread syntax which was introduced in ES6.

You can also set the target to newer ECMAScript levels, including “ESNext”, at the cost of compatibility.

Another Typescript fan here. The title didn’t match the post content but I agree with everything he wrote concerning type “soundness”. From my POV it’s a downside of typescript but it makes sense since it’s just a superset of JavaScript
Yep, it ain't perfect but it is so much better than not having it. Anyone being concerned about TS adding overhead please take a look at how generics in Java work - both struggle with erasure of type information at compile time. Java may be sound but doing anything non trivial with generic types gets complex quickly. Compared to Java generics doing TS advanced types is almost a joy.
nope - the loss in velocity is not worth using it unless you're a massive team. TS is for Java programmers who are mad at Javascript for existing and don't know how it works well enough to grok overloading etc. plus you get a lot of the benefits of TS in an IDE like VSCode without actually having to use types at all. if you're a javascript native, typescript is constricting and eliminates one of the best parts of javascript - no types!!
I'm not quite sure why you consider "no types" to be one of the best parts of JavaScript, but TypeScript certainly doesn't require the use of types. It just makes the functionality available.
(comment deleted)
I think this depends on where you are measuring costs in your codebase. If you're strictly looking at time to market on your first iteration, then you're right, types are going to require more of your team's time up front. If you look at the total lifespan of a "real project", then type safety is likely going to represent a cost savings regarding time your team spends on bug fixes, refactoring, and maintaining legacy portions of your app.

For context, I base the above sentiment on our team's last two years of effort in migrating key parts of our app to TypeScript (25 devs, ~500k lines of code). We've found that it has substantially reduced the number of defects that make it into production, and it has also reduced the number of round trips through QA. Clearly, this is only one data point, but hopefully it offers some perspective on why TS can be valuable in a codebase.

sounds like its working for you, so i'm glad. you're about what i'd consider a big team / codebase. i can't recommend TS to people trying to create product from nothing. if your startup is doing well, you can hire someone to annotate your code w/ types later ;)
My experience is the opposite. One of the best things about TS is yhe refactoring experience - change a function signature for example and TS will tell you all the places you need to update. In a fast moving greenfields project with various requirements in flux, I’ve found this invaluable
I agree on this too. Oftentimes, we'll change a type and then just follow the errors in VSCode. It's a pretty great way to track down all the codepaths that are affected really quickly.
https://www.tutorialsteacher.com/typescript/function-overloa...

TS/JS don't allow for overloading the way that other languages do - it does not allow you to differentiate between functions via number of parameters, only types of parameters. If you try to differentiate based on number of parameters, it will simply use whichever definition of that function name it finds first.

(comment deleted)
That's not [entirely] my experience. Here's a quick example:

Declaration file style:

---

    // add.d.ts
    export declare function add(a: number, b: number): number;
    export declare function add(a: number, b: number, c: number): number;
    export declare function add(a: number, b: number, c: number, d: number): number;
---

    // add.js
    function add() {
     switch (arguments.length) {
      case 2:
       return arguments[0] + arguments[1];
      case 3:
       return arguments[0] + arguments[1] + arguments[2];
      case 4:
       return arguments[0] + arguments[1] + arguments[2] + arguments[3];
      default:
       throw new Error("Too few or too many arguments. Number of arguments: " + arguments.length);
     }
    }
    module.exports = { add };
---

    // adding.ts
    import { add } from "./add";
    
    console.log("4 + 4 =", add(4,4));
    console.log("4 + 4 + 4 =", add(4,4,4));
    console.log("4 + 4 + 4 + 4 =", add(4,4,4,4));

---

(Gist: https://gist.github.com/Robert-Fairley/98a2da3f0361e524f4e05...)

It will default to the first function name, but it definitely allows for overloading the number of parameters.

It's not perfect as you do still have to implement the function as it would need to be implemented for JS

JavaScript has types. typeof {} === 'object'... Sure, they're very weakly held, which can be a lot of fun when you accidentally add a string and a number, but variables have types.
> no types!!

This might be news to you, but JS is full of types regardless of whether you document and automatically check them.

I prefer to document my types and have them properly checked, plus the IDE advantages.

I'd say it's definitely worth it.

I wrote a blog post recently about my experiences learning and using TypeScript as both an app dev and a library maintainer for Redux:

https://blog.isquaredsoftware.com/2019/11/blogged-answers-le...

As part of that, I listed some of the tradeoffs and takeaways. In particular, I strongly recommend a "pragmatic 80%" approach to tackling TS.

The author's example is a trivial corner case that can be easily avoided.

It is nowhere close to important enough to fuel the question of 'Is TypeScript Worth It?'

In general it seems hard to be in-between dynamic typing and static typing (compiling). As the article implies, one typically has to do more testing if dynamicness can allow "bad data" in.

The trade-off for dynamicness is more productivity and (potentially) easier-to-read code because less code is needed on average to express an idea since type-related code isn't needed as often. But if TypeScript is not type-safe enough to reduce the need for fine-grained testing, then you get the worse of both worlds: the verbosity of types and the busy-work of micro-testing.

That being said, I wish JavaScript would add a feature to allow optional parameter checking such as:

function foo(a:intReq, b:datetime, c:datetimeReq, d) {...}

Here, the "Req" ("required") suffix means the field can't be null or white-space. Parameter "d" has no type indicator. It's still "soft" type checking, but would catch a lot of problems earlier.

Typescript users: LOVE IT!

Non-typescript users: ANOTHER layer? /sigh/

Cynical, yes, but that's because I haven't had a smooth onramping experience the times I thought about switching. Would love a recommendation.

When I introduce it to a new team, leaving js/jsx as valid and making TS optional is usually good enough. Whether through peeking at your TS code or just being curious, usually they come around, and if not, they are still responsible for their code. The tradeoff is theirs to make
Using TS since ~2017.

Moving an existing codebase to it is a giant pain, otherwise it's not bad.

It fits you into better practices, but don't treat it as types are always true. When debugging issues I'll commonly assume Typescript's types do not exist or how it would act if they are wrong. This is notably true of consuming API responses with mixed data types.

Generally, what I really like about typescript is that you normally blend it with linting, minifying, and strong IDE integrations. These things, when combined, really help code readability and adherence to TS conventions and eventually save you from massive bugs that might not have been caught prior. Obviously, at the end of the day, how you use it really defines how helpful it is.

All that said, I haven't ever really had major issues with the tools around TS.

If you’re writing JS, then adding TS will give you a lot more reassurance when writing new code or refactoring old one. Even if it’s not perfect, I don’t see a reason why you’d choose JS over TS today other than maybe you don’t want to go through the first month of getting used to. Once you’re out of that, you’ll end up with code that’s much easier to reason about and also debug, in my humble opinion.
TypeScript feels worth it until you use something like Elm, then you realize just how lacking and TypeScript's type system truly is.

I have grown rather weary of type systems that don't require you to be exhaustive and, for my money, types like `Omit` and `Pick` are nasty hacks that just let you pretend like you aren't doing dynamic types, something TypeScript does a lot. The TypeScript pattern of `someFunc<typeof someInput>` is just dynamic types with extra steps. Using `any` wouldn't really lose you much of anything, the "type" is just "whatever this input is". That's not a real type, that's a dolled-up dynamic type. But hey, it typechecks!

TypeScript's major weakness is that it doesn't want to break away from Javascript. That strict dedication to being a superset means the type system explodes into ten thousand builtin types the come, seemingly at times, from nowhere, simply to control for the wildness of Javascript. It feels like a joke sometimes, and like I need to have encyclopedic knowledge of all these highly-specialized types that are included in the language and are often being used in the background.

This leads to nearly impenetrable error messages regarding types, especially if you have a stack trace involving more than one object/record type (whatever you want to call it), it will expand all of them and just, no. It's unreadable. It can be read, but it cannot simply be grokked.

TypeScript just doesn't impress me. I get far better results from other languages for a whole lot less work. TypeScript claims to want a balance between correctness and productivity but the language gives me neither of those things. My typical experience is that I wrote almost the same amount of actual code, but now also with types. Elm lets me write far less total code. Less code means less bugs and more productivity, and better types and better correctness is what gives you those things.

I'm not really sure why you were downvoted since you raised some pretty relevant points.

> TypeScript's major weakness is that it doesn't want to break away from Javascript.

This seems to also be the source of it's popularity, it's the path of least resistance, sprinkling types on top of your existing codebase is a way easier sell.

(comment deleted)
Maybe because it's totally missing the point? It's like if I ask "is mypy worth it?" and someone answers me: "not worth it, Rust has a better type system".

Also from a business/management POV, it's quicker to migrate JS->TS than JS->Elm and easier to find TS devs (because nobody uses Elm btw) or teach TS to JS devs.

Re: dynamic types with extra steps:

Nitpick: In the situations you described, the type is known to the compiler at compile time no? Do you have the same problem with "auto" in other languages?

I think this misses the point somewhat, I think even the typescript people will agree, yes, you CAN pick another language and get better type support ( amongst many other things ). But typescripts goal is to be a superset of javascript, which has resulted in many many libraries now shipping with typescript type definitions as officially supported things within libraries. Elm ( or any other client side language) simply hasn't had that kind of impact. Yes it does still have a bunch of problems inherited from the nature of javascript. Sure, you, as an individual might get good results with your tech choice.

So I think the main thing is typescript should make things better in the javascript world, but due to its ability to gradually adopt typescript syntax over javascript syntax you can live in a middle ground where it's not going to do much for you (or even make it worse).

Ultimately I think compiled WASM based languages are going to take over, but we aren't there yet.

TypeScripts pragmatism is indeed its biggest strength. It is very simple to get started with converting a JS codebase into TS. This means there is a _real_ and _practical_ migration path for all the mountains of existing JS. I haven't used Elm but I would assume the cost to convert existing code is much more significant.

But TS then goes further and layers a lot of really nice features on top of the JS underpinnings. Being able to strongly type string-ly typed things are, again, such a useful feature to have for a codebase that interacts with JSON/JS.

All this said, in my experience `Record` is not very heavily used, but is really useful when you do need to reach for it.

I haven't played with Elm, but my experience with ReasonML matches yours. I made some small but non-trivial side projects to learn ReasonML and TypeScript, and not only is ReasonML's type system vastly more satisfying, I actually found it much easier to learn (coming from JavaScript) than TypeScript is!

https://reasonml.github.io/

A big problem is that elm has questionable stability as a project. It's mainly one person, and not a lot of mainstream usage. The biggest projects mentioning elm are ones related to the main programmer.

Typescript on the other hand has had massive growth and has been adopted by a large amount of libraries. Now, web ts/js libraries usually suck but it still speaks to the overall health of the language in a community. Even then, typescript still only has a very small percentage of usage.

Elm is basically a rounding error on some of the surveys I've seen.

Long term support, tooling, etc are often much more important for languages past a threshold. Typescript as a language may not be super exciting. Typescript as a technology is wonderful.

The tooling and reliability of Elm over the last few years is quite a bit better than TS.
I was an early enthusiast of Elm.

Sadly, Elm was never able to build a true core development team outside of Evan. Or at lest it didn’t do it fast enough.

Evan is a smart programmer but not a strategic organizer or a communicator.

As a result, one of the most exciting and promising approaches to front end development was relegated to a nice-toy project status.

If you want to take the risk of building something using Elm’s approach, you’ll do it with Reason. Maybe not 100% as nice as Elm, but you’ll feel confident you’re not risking your code base and company on the whims of one person.

I still think Elm is a nice educational language for getting people (and kids) into FP.

Can I, in Elm, bring over my couple dozen NPM modules I use in my projects? At least what TypeScript has going for it, if not a more robust type system, is interoperability with all the other tools I need to build web applications.
You can.
Maybe not in the way JMTQ is expecting. You could bring your npm modules over and call them through Elm ports, but you’ll still have to declare type signatures of those ports and have a bit of ceremony about calling them.

It might be better to ask what those npm packages are doing, exactly. There’s a decent chance that either the npm package is filling a shortcoming of the JS standard library (e.g. moment) where that shortcoming doesn’t exist in Elm, or an equivalent Elm package exists.

Typescript is entirely about being a superset of Javascript with the unique ability to implement types from very strongly defined to completely dynamic, and this can be set function by function or even line by line.

That's what led to the wild productivity and popularity. If you're answer is to just use an entirely different language then why stop at Elm? There are dozens of strongly typed languages available.

> TypeScript's major weakness is that it doesn't want to break away from Javascript.

No, it is not. That is exactly the major strength of Javascript! It is an addition to the landscape, for people, who can make use of it.

For everyone else, there is already Javascript "successors", that break with Javascript, but compile to Javascript. But they also are a new language, or, another language. You named one: Elm.

Not so TypeScript. I can gradually upgrade my codebase. And I can reuse knowledge and components, I already have. I am so happy about Typescript. How else could I continue using ES3 on the Windows Scripting Host and have modern Javascript tooling? For me TypeScript is the "better" Javascript. An evolution, rather than an exit strategy.

Thing is you can now have many of the benefits of TS without using it in your own code. Most popular libraries come with type definitions now and an editor like VS Code will take advantage of this and give you the same autocompletions TS would.

TS introduces some friction for setting things up, it slows you down in initial development, increases LoC, etc. I do like the type documentation in function signatures, the autocompletion and the refactor support, but I think the project needs to be of a certain size for the cost/benefit trade off favors TS, typically a project that goes on for 6+ months and with 2 team members or more. Anything smaller than that and I wouldn't choose it myself.

Worth it at component level. Hassle at application level.

I use TS to write components and JS to write the app using those components.

Why do these sorts of articles always give vague definitions of Soundness and act like it exists on a spectrum?

Soundness = Progress & Preservation.

From Pierce's Types And Programming Languages:

"Progress: A well typed term is not stuck (either it is a value or it can take a step according to the evaluation rules).

Preservation: If a well typed term takes a step of evaluation, then the resulting term is well typed."

There are not degrees to Soundness and it is a well defined term.

You've shown that one can give a black-and-white definition of soundness and have it be useful.

That doesn't mean that there's no way to give useful meaning to "more sound" or "less sound".

Consider the following family of languages:

L has a sound type system.

LD (D for "dynamic") is L plus the ability to escape from it, in specially marked "unsafe" sections of code, into a dynamically typed system. (So evaluation can produce runtime errors, but they can be handled safely.)

LC (C for "crash") is L plus the ability to escape in "unsafe" sections into a system like Ld's except that now type errors aren't checked for and caught at runtime, they just produce memory-stomping and buffer overflows and the like.

D is just LD's "unsafe" language.

C is just LD's "unsafe" language. (Any resemblance to actual real-world languages named C, living or dead, is purely coincidental.)

The only one of these languages that is sound is L, of course. But there is some quality closely related to soundness by which there's an obvious partial ordering where L > LD > D, L > LC > C, LD > LC, and D > C, and if someone describes this situation by saying that LD is "sounder" than C then I don't see any reason why we should stop them.

Similarly: a federal state is either a "perfect union" or not, for any reasonable definition of "perfect", but it's reasonable for the US constitution to aim at a "more perfect union". A thing is either "unique" or not, but despite the cries of pedants it's reasonable to describe a limited-edition Ferrari of which only two were ever made as "almost unique" and as "more unique" than a standard-issue Ford Fiesta. A given region of space is either "vacuum" or not, but most of the things we describe as "vacuum" actually contain some very rarefied gas and it's common to use terms like "high vacuum" to quantify how vacuum-like a "vacuum" is.

The main problem with TypeScript is that at the end of the day -- you're still using JS.

Which outside of its natural realm of application (i.e. the browser) is a highly questionable value proposition.

"A bandaid over a machine-gun wound" is what we might call it.

Eric Elliot does a great analysis on this:

https://medium.com/javascript-scene/the-typescript-tax-132ff...

side note: but what the hell is hashicorp configuration language (HCL)?

It's apparently the second fastest growing language based on whatever metric the chart is using and I've never heard of it.

It's used with Terraform, amongst other things. So you might not have heard of it if you're not doing devops/infrastructure work.