Hacker News new | past | comments | ask | show | jobs | submit login
TypeScript 4.1 (microsoft.com)
338 points by shian15810 4 days ago. | hide | past | favorite | 270 comments





Something I love about advanced TypeScript types: they're easy to consume given some documentation, even when the programmer doesn't understand them. Defining the types is another story.

Editors give great autocomplete suggestions (usually covers it for properties and return values). Documenting with examples is often helpful. If all else fails, cast.

Without advanced types, code needs documentation to prevent invalidity. With advanced types, code gets logical guarantees, but it often requires the same documentation. It's not necessary for everyone on a team to have cutting edge knowledge when there's sensible documentation.

Types create a synergy with documentation. Provable(ish) guarantees with good documentation is the best of both worlds.


I've actually come to enjoy writing types as much, if not more, than the code itself.

Part of it is just being able to write code that is extremely dynamic, yet still provides an amazing developed experience.


I especially love typescript when it comes to refactors or API changes...on my team we strictly type as much as possible and it has made refactors and API changes so much easier and fail-proof.

For example if an API change occurs on backend, the first step is to make the change to the request/response typings in the frontend...from there the source code will light up with all the errors you have to fix. I can just click through each one in VS Code and fix them, then re-run tests to validate...love it.


JSON schema to provide documentation across system and runtime validation of both types and business rules.

Much more effective.


Using something like io-ts gives you the benefits of static types and runtime types. JSON Schema typically just provides runtime guarantees but doesn't come with the same development experience. I've been very happy with io-ts so far.

I know there’s a zillion libraries in this space by now (and so far I’ve found io-ts quite good), but I feel like there’s actually room for more, with some features currently missing from all of them. (So I’m working on one!)

The dynamic JS space uses things like JSON Schema because they provide documentation and enable runtime validation, in a similar way to libs like io-ts. But they also provide API/network boundary documentation, as well as enable automatically generating client libraries (regardless of the client’s stack).

There’s a good opportunity for a library with all of these value propositions:

- simple, declarative, composable definition interface

- runtime validation/decoding/encoding

- DRY static type generation

- DRY standards-based documentation generation

There are (to my knowledge) no tools available providing all of those in one package. But I’ve built one (it was built on top of io-ts), so I know it can be done. But it was proprietary, for an employer, so I can’t share it.

But! I learned a lot in the process, and I’m building a new one. Zero dependencies, fundamentally a different design/approach. But I’ll definitely be sharing it with HN when it’s ready.

Edit to add: another thing is that many of the tools that provide some subset of those goals (especially static types <-> JSON Schema tools) require is out of band code/file generation (eg supply JSON Schema file input, get .d.ts file output, or vice versa). IMO this is additional DX friction and more error prone than the runtime/compile time parity of the common TS libraries. So another goal for my project is that the default approach is to define one interface and have APIs available to access all the other representations in the same flow, with file generation a secondary concern if there’s a need/desire.


I'm using zod to do the same. If you aren't using fp-ts it will fit a lot easier into your ecosystem.

I use runtypes:

     import { Record, Number, Static } from 'runtypes';

     export const Thing = Record({
       thing: Number
     });

     export type Thing = Static<Thing>;
Then you can import it like this:

     import { Thing } from './thing';

     function (thing: Thing) {
       if (Thing.guard(thing)) { console.log('we have a thing!'); }
     }

You don't really need to be bought into fp-ts to use io-ts. Just create a helper function that morphs the native decode output, `Either<E, A>`, to something like `E | A` or `A | null`.

Or even `asserts value is A` if error handling is more your cup of tea/more idiomatic in your project.

We generate JSON Schema from our TypeScript interfaces, then use that to validate data at relevant points in the application. Best of both worlds, assuming the type definitions are relatively straightforward.

What do you use for that? I looked into it, and none of the alternatives sat well with me. They all felt like impositions on the build process that would make debugging harder.


Thank you

We've had pretty good success with using OpenAPI Specifications to document APIs and their schemas and then using generated clients in code.

I love typescript but its types are becoming so complicated it's turning into a dynamic language...

I mean one can't represent the all possibilities of JavaScript "types" without re-implementing JavaScript itself, only a subset. I wonder where Typescript will stop.

I'd like to see Typescript compiling to "native" as well.


The vast majority of people using TypeScript in their projects won't run into the deeper complexity of the type system. For the average TS user the only visible impact of features like template literal types and recursive conditional types will be fewer bugs because maintainers of popular libraries used them to provide better types.

You don't have to personally understand TS's more complicated features to benefit from using TS in your project. I hope they continue adding richness to the type system so I can eliminate more bugs from my code.


Always when typescript is is compared with js, people talk about you get less bugs in typescript. I am honestly curious to see some evidence. In my opinion application bugs are usually in the flow itself and have nothing to do with wrong types. I work a lot with both plain and Typescript projects. I used to like Typescript. But not anymore. With typescript applications tend to become bigger and more complex. Most devs using it have never seen vanilla js and are more java minded that prototype minded. IDE code completion works better for typescript. For sure. But is that really essential? Sounds a bit like the typical Java developer who works with giant spring applications containing classes with hundreds of methods spread in many files in a 10 layer deep folder structure.

> bugs are usually in the flow itself and have nothing to do with wrong types

I'm not sure what you mean by "in the flow itself" exactly, but just yesterday a teammate of mine was using one of our libraries in a small project without TS. They first called getThing(), which might return null, and then passed the result to doSomethingWithThing(), which did not expect a null. That sounds like a "flow" problem to me, and TypeScript literally would've warned them of it.

(Since this particular combination of functions is likely to be common, we added a runtime check for people who aren't using TypeScript now.)

> Most devs using it have never seen vanilla js and are more java minded that prototype minded.

I think you might be underestimating the number of people who strongly dislike Java's type system but very much appreciate TypeScript. They're very different, and hence it's very much possible to like the latter for reasons that do not apply to the former, or to dislike the former for reasons that do not apply to the latter.


>> They first called getThing(), which might return null, and then passed the result to doSomethingWithThing(), which did not expect a null

When i think of a flow issue it's more things like "i forgot to check if the user was authorised to perform doSoemthingWithThing()"


And with TypeScript I get notified that I forgot something and it shows me exactly where.

Can types solve that problem?

    ...
    // if(isAuthorised(user)) {
        doSomethingWithThing(thing);
    // }
    ...
How would typing detect the missing authorisation check in the program flow?

Surely this is a case where testing is the correct tool to use?


When User is a type of:

Guest | AuthenticatedUser

and isAuthorised is a type guard like:

function isAuthorised(user: User): user is AuthenticatedUser { return user.isLoggedIn /* or whatever */}

as long as doSomethingWithThing accepts just AuthenticatedUser, yes it can be caught. You can also use discriminated unions with implicit type refining from TS without type guards.


And beyond the isAuthenticated() / isAuthorized() call in the flow there only needs to be the use of AuthenticatedUser.

Wait

    doSomethingWithThing(thing)
Doesn't know about user, why does it need to be polluted with the concept of an AuthenticatedUser type which it does nothing with?

If that action has a dependency to a user (an authenticated user or a user with a specific permission), it's a good practice to establish this dependency also in the code level.

"doSomethingWithThing" probably does IO, and the changes need to be traced back somehow (e.g. a ModifiedBy column in a DB). You can solve this context problem through a DI container, or by direct parameter passing, but whatever you do, there will be a layer which can expose the requirements as types.

I'm not a know-it-all who'll dictate how you need to structure your program on HN, so what I stated above is just me thinking out loud, but let's say it's up to the programmer to make everything safe-r with the tools available, be it runtime checks or the type system.


I think GP may have meant to say that whatever code is _calling_ doSomethingWithThing() there should only accept AuthenticatedUser. It hoists your original check to the calling function’s signature.

I agree with you that this doesn’t really solve the flow problem though: someone still has to decide that the function only receives AuthenticatedUser and not Guest. That’s not a net gain, it’s just shuffling responsibility around, and it could be a net loss if the calling function does “a bunch of identical things for any user type” and then “this one extra thing if user is authenticated.” But I’m not a Typescript user or a big fan of complex type systems either.


There's a correlation between lines of code in a file and bug count. Does the typing outweigh this?

If all the time spent writing all those types was spent debugging, would TS win in actual development time?

If you're writing good unit tests already, why are you running into so many type issues?

If you already have to spend time writing tests, why spend more writing types?

TS types aren't really documentation. For other devs to use your stuff, you need JS Docs to actually explain things. Why write all those types too?

If I'm writing a quick and dirty project, it feels like TS types help a little, but if I'm following best practices on an important project, TS seems like pointless ceremony that slows things even more.


I don't have numbers on that, though I think a sibling comment did refer to some.

I do have my intuition though. (One day I'm going to keep track of the mistakes TS catchers early for me in a week.)

So yes: I think the typing lowers the correlation between lines of code and bug count, and that that outweighs the bugs introduced by the additional lines. (It is similar in that regard to many unit tests, I'd say.)

As for unit tests: it's not that I write unit tests first and add types later. The types are written as I code, and they allow me to not write some of the unit tests I'd written otherwise. The upside is that they're easier to write and easier to keep aligned with the code than unit tests.

So yes, I still have to spend time writing tests, but less of it than without types, and I save more time than the writing of types costs me.

> TS types aren't really documentation. For other devs to use your stuff, you need JS Docs to actually explain things. Why write all those types too?

I don't understand what you mean by "write all those types too"? Yes, I still have to write documentation, but just iterating the types is not documentation?


> I don't have numbers on that, though I think a sibling comment did refer to some.

I've read that paper in the past and it doesn't actually answer most of the questions I asked above. It only says that X bug existed in some prior git commit. Maybe the dev caught that bug and fixed it before the PR with the commit was merged. Maybe it was caught in code review. Maybe it was theoretically possible within the function, but not within the actual program's use of that function. Maybe the time spent catching those bugs was less than the time it would take to add types.

> they allow me to not write some of the unit tests I'd written otherwise

A `typeof` assertion or similar is hardly more work and continues to function once the TS types have been stripped. If you expect to interact with the outside world, then you must test against unexpected types. If not, then good docs are still better (see below). Meanwhile, in every real-world TS project I've worked on, you wind up with tons of "template soup" where devs spend tons of time trying to find out which variant makes the type checker happy (or just giving up and slipping in an `any` type)

> Yes, I still have to write documentation, but just iterating the types is not documentation?

I have a function that takes a string and returns a boolean. What does it do?

It's likely that I can pass it any string, but there's a strong possibility that the function can't handle any random string. Does that boolean mean it's a test, that something was successful, or something else? What about side effects?

By the time you're done documenting this, when someone glances at the docs, they'll probably not worry very much about the types because they'll be obvious. Why write up a bunch of complex types when a simple, human-readable doc string does types and so much more?


> I've read that paper in the past and it doesn't actually answer most of the questions I asked above.

OK, well I still don't have the numbers, so we have nothing better than intuition to go on regarding whether it saves time/improves quality or not.

> A `typeof` assertion or similar is hardly more work and continues to function once the TS types have been stripped.

Yes, and if a typeof assertion is enough, than you don't need any additional syntax in TypeScript either. But a `typeof val === "object"` doesn't tell me a whole lot though.

> If you expect to interact with the outside world, then you must test against unexpected types.

Agreed. That said, with TypeScript, you only have to do it once, at the point where you interact with the outside world. Once I've verified that e.g. my API response contains all the properties I expect, then I can pass it on to any other function in my code safely. Whereas without TypeScript, I have to be aware at the points where I access those properties that the original source of that value might have been the outside world, and to explicitly verify that it looks as expected. (Or alternatively, I need to still verify the object at the boundary, but have to manually know what properties of it are accessed in the rest of my codebase.)

> Meanwhile, in every real-world TS project I've worked on, you wind up with tons of "template soup" where devs spend tons of time trying to find out which variant makes the type checker happy (or just giving up and slipping in an `any` type)

Yes, I've seen that happen to. I will not argue that you don't have to learn TypeScript, and that if you do not (want to) put in the effort (or are unable) to do that, it might be counter-productive. In fact, I advised another team in my company to move off of TypeScript for that very reason.

> By the time you're done documenting this, when someone glances at the docs, they'll probably not worry very much about the types because they'll be obvious. Why write up a bunch of complex types when a simple, human-readable doc string does types and so much more?

Yes, type annotations are not a replacement for documentation. They help your tooling help you. So the reason to write up a bunch of complex types (well, preferably simple types most of the time, of course) is that your tooling can help catch mistakes early - I'm not working off of documentation most of the time. I read it once, refer back to it every now and again, but more than that would be a massive waste of time. My memory is a major asset in being able to quickly type out a bunch of code, but my tooling helps me by removing the need to memorise some things.


> There's a correlation between lines of code in a file and bug count. Does the typing outweigh this?

This is where the famous 'compiler' steps in and catches your typos and errors with "types" at compile time.

> If you're writing good unit tests already

Compilers are not fragile. Unit tests are fragile. IMO, a significant factor contributing to relation between N (lines of code) and B (expected number of bugs) is human error. Humans write unit-tests. For a fun spin, consider the fact that "more unit-tests" means "more lines of code" and thus "more bugs", but now in your "test suite".

[p.s. fragility above refers to the inevitable drifts between the original test-subject (and associated test code) and subsequent changes to the codebase that require updates to the test codebase.]


> This is where the famous 'compiler' steps in and catches your typos and errors with "types" at compile time.

The worst bugs aren't types

* Syntax errors -- caught by JS runtime

* Math errors -- caught by unit tests

* Logic errors -- caught by unit tests

* Resource errors -- caught by profiling tools and unit tests

* Interface errors -- caught by integration tests, documentation, profiling and/or types

Of all the major types of programming errors, types only help with the least impactful one, the most obvious one, and the one for which there are other tools available.

In the end, the dynamic vs static typing argument has likely been going on since before most HN user's were born (since at least the 50s). I suspect we aren't going to reach a definite conclusion today either.


> In the end, the dynamic vs static typing argument has likely been going on since before most HN user's were born (since at least the 50s). I suspect we aren't going to reach a definite conclusion today either.

Sure thing, but wanted to insert a few facts informing your assertive OP regarding "lines of code" and "unit-tests".

p.s. "worst bugs"

The worst bugs in my 3 decades career have involved reliance on a broken test-base "verifying" widely shared code in an evolving codebase.


> caught by JS runtime

This is a major point: "caught by JS runtime" means you're only able to see it when you actually run your application and exercise that code path - or worse, that your user does so. Where the alternative is your editor literally indicating the error the moment you write it, the fix costing you so little time that you can fix them practically subconsciously.


Catching syntax errors is the realm of linters, but if you don't have a linter, the parse phase will immediately tell you that you messed up.

That's interesting, cause you mentioned the JS runtime earlier. In any case, unless you'd argue that you never get a single runtime error, I'd encourage you to take a moment every time you encounter one whether that could have been caught by TypeScript. In my experience, that is the case more often than you might think.

>If you're writing good unit tests already, why are you running into so many type issues?

>If you already have to spend time writing tests, why spend more writing types?

Because unit tests and types solve different problems and if you use one as a substitute for the other, you're doing it wrong.


What problem is types solving that aren't solved by the combination of function documentation, sanitizing external input, and unit testing?

Types define explicit boundaries for application data structures, tests verify software behavior at run-time, they both provide unique benefits that cannot be achieved with either alone, and they in-fact greatly compliment each-other.

Types exponentially reduce the set of possible inputs a function can receive, thus greatly reducing the number of tests that need to be written to achieve the same level of safety. Types are also much more precise and less brittle than tests, they establish clear contracts that reflect the actual structure of the program whereas tests provide assurance that some minimum subset of behavior is correct enough for the software to meet the expectations of users.

Ultimately, a lack of types is a form of technical debt because the data structures do exist whether you spend the time to formally acknowledge them or not.


Bug reduction estimates range from 15% [1] to 38% [2]. One can also argue it greatly improves refactoring speed.

1: http://earlbarr.com/publications/typestudy.pdf 2: https://www.reddit.com/r/typescript/comments/aofcik/38_of_bu...


> But is that really essential? Sounds a bit like the typical Java developer who works with giant spring applications containing classes with hundreds of methods spread in many files in a 10 layer deep folder structure.

It's not only your stereotypical Java developer who has to maintain large code-bases. There are much more monolithic code-bases than there are micro-services. Also, probably you have a much better memory than me, but I, like many others have trouble working with random objects with no contracts.

Additionally, try using a library like io-ts and you also get validation against types.

Typescript made me love JavaScript again.


I have worked with Javascript for frontend quite extensively since 2005 and with Typescript since about half a year. In the general case, I don't have strong opinions about strong vs dynamic typing, or getting error during compile- or runtime.

But my gripe with Javascript has always been, that you often don't get any direct errors at all but instead end up with unexpected values in a different place that you then have to tediously backtrack.

A contrived example to illustrate what I'm referring to:

  some_obj = {day: 2, ...}
  next_day = some_obj.daz + 1 // typo in attribute gives you `undefined`, adding 1 to it results in `NaN`, not a number.
  date = Date(2020, 11, next_day) // returns a`Invalid Date` object
  console.log('date: '+ date) // outputs "date: Invalid Date"
In Python, every line after the first would raise a runtime exception and is thus in my opinion much easier to debug.

What I particularly like about Typescript is that I can use as much or as little of it as I deem necessary. E.g. I start off a prototype using the `any` type a lot, and only in future iterations, I start tightening the definitions as the requirements for the project become clearer. So far the overhead has been quite minimal.

The one downside so far is, since I learned Typescript by just starting using it, without a deep dive in documentation, books, etc. trying to grok code, well especially type definitions of libraries (which I often prefer over reading documentation) that use the more advanced features has been quite a challenge.


> What I particularly like about Typescript is that I can use as much or as little of it as I deem necessary. E.g. I start off a prototype using the `any` type a lot, and only in future iterations, I start tightening the definitions as the requirements for the project become clearer. So far the overhead has been quite minimal.

I started working this way and it was fine, that's how I always worked anyway. Then I started leaning towards working the types first and the code later, and I've found it works better and better for me.

Much of today's programming is "stitching pieces together": pieces from the libraries you are using, from external services you use, from your platform's APIs, and then creating the few extra pieces that are unique to your puzzle.

When you code first, you are directly painting pieces while trying to picture how the final puzzle will look like in your head.

When you type first, you are just cutting the shapes of the missing pieces and already assembling the puzzle. Once this is done, you are left with a full picture that has some blank spots, and it becomes much easier to just paint those in.


> The one downside so far is, since I learned Typescript by just starting using it, ... trying to grok code ... that use the more advanced features has been quite a challenge.

I'd suggest skimming this page (a concise overview of the advanced features): https://www.typescriptlang.org/docs/handbook/advanced-types....


Typescript already stops bugs right when I'm typing, so it helps before compile or run time.

Also, reliable auto completion is very helpful not just for the method names but for properties etc.


I share this point of view. For the last few years, this perfectly reasonable and balanced viewpoint was taboo so it's good to see more and more people approaching the debate rationally as opposed to waging a religious war over gut feelings.

Personally, I have no issue with TypeScript purely as a language and I concede that it provides much better IDE code completion than JavaScript. That said, I don't agree that its benefits offset the drawbacks of the transpilation step, the source mapping and the added versioning complexity between JS and TS.

I also miss the way that JavaScript encouraged people to define very simple function signatures (e.g. strings, numbers, plain objects/clones as arguments and return values).

For example, I really liked how the React/Redux community came up with a philosophy around cloning state objects before returning them from functions in order to prevent unexpected mutations later (essentially force everything to be pass-by-value). I think this philosophy does not translate very well to TypeScript which encourages developers to pass around complex live instances (which have their own methods) instead of raw objects and other primitive state representations.

As Alan Kay pointed out, OOP is not primarily about objects, "The big idea is messaging". Instances should communicate with each other via simple insterfaces using simple messages; they should avoid passing complex live instances to each other. If an instance has methods, that is a complex live instance and it shouldn't be used for messaging between different components.


> I also miss the way that JavaScript encouraged people to define very simple function signatures (e.g. strings, numbers, plain objects/clones as arguments and return values).

I wish this were true, and maybe it was in some cases, but not broadly. Many "types" used in JavaScript libraries are horribly complex. For example, the type signature of jQuery's `$` function, or moment.js's main function, or `server.listen` in Node.js, etc.

In fact I think the opposite argument from yours could be made with a straight face: not having to declare types encourages more complexity. I don't really agree with that either, though; in my experience this is more a matter of developer discipline and API design skills.

One thing I'm certain of is that if libraries are going to have complex types, I'd much rather have them be explicit and machine-verifiable vs hidden/implicit/hope-the-developer-wrote-a-good-docstring.


I maintain Redux, and I can confirm that TS does _not_ "encourage devs to pass around complex live instances" any more than normal JS does. You can do FP and immutable updates in TS, just like you can do any other code. See our "usage with TS" docs pages for examples:

- https://redux.js.org/recipes/usage-with-typescript

- https://react-redux.js.org/using-react-redux/static-typing

- https://redux-toolkit.js.org/usage/usage-with-typescript


What you're claiming here does not match the evidence from your links.

For example, in your second link, one of the functions expects a 'complex' instance DispatchProps which has a method toggleOn... This abstraction doesn't make sense conceptually. What is a DispatchProps? It doesn't adhere to Alan Kay's notion of a 'message', it's clearly a structure (it's a complex one because it exposes a method). Components should communicate to each other via messages, not structures.

Also, the method builder.addMatcher(...) accepts a function as an argument - It doesn't seem like an ideal abstraction either. Functions are not messages.

Also, the thunkSendMessage function signature is very complex; the return type is highly convoluted. That's definitely not a message.

Overall, I see a lot more of these complex instances being passed around in these examples than used to be the case with JS when mostly just raw objects were being passed as arguments and returned.

You can already see the complexity seeping into the interfaces. Just a few years of TypeScript is distorting the original philosophy. I remember Dan Abramov was very careful about what to pass into functions and what to return from them and made it a point to encourage cloning objects using the ... spread operator.

That philosophy appears to have been forgotten.


Wow. I'm sorry, but you've completely misinterpreted both what I was trying to say, and what those Redux-related APIs do.

A lot of people seem to assume that "using TS" means "must use the `class` keyword and deep inheritance chains", ala Java and C#. Redux, on the other hand, is FP-inspired. Nothing about the Redux core involves classes in any way - everything is just functions, including the middleware API, with an emphasis on immutability.

You brought up Redux's use of immutable state updates, but then said "that doesn't translate well to TS". I was attempting to show that it _does_ translate just fine to TS, because TS lets you write functions and make immutable updates. `return {...state, field: value}` works as fine in TS as it does in JS. I wasn't trying to touch anything about "what a message is".

Having said that, the rest of your observations about the Redux core and React-Redux APIs in this comment show a general misunderstanding of what Redux is and how it gets used. After all, Dan and Andrew came up with the actual React-Redux API, the concept of `mapDispatch` for passing action creators as props to React components, and the thunk middleware. The Redux Toolkit `builder.addMatcher` API is a recent addition, but all it is is syntax sugar for "if the dispatched action is any one of these types, we want to update to it", same as if I wrote a multi-condition `if` statement by hand.

None those have anything to do with TS, and they did not become more complex because we're now using TypeScript. In fact, it's the other way around - the complexity of the dynamic JavaScript behavior actually requires us to write much more complex TS types to capture how the code actually works. (There's a good reason why the React-Redux TS types are insanely complex, and I'm so glad they're maintained in DefinitelyTyped instead of by us! `connect` has so many overloads and different options that affect downstream props values, it's almost impossible to capture that with static types.)


Maybe you also misunderstood my initial comment. I can say for sure that of all the programming trends, there has been almost no discussion around interface complexity. There has been plenty of discussions around FP, dependency injection, type safety and a host of other trendy programming topics but I've never heard anyone point out interface complexity as a problem.

Complex interfaces lead to 'tight coupling'; developers have known for decades that this is bad but we don't seem to be discussing it much anymore. The reason why JSON-based REST APIs became so successful is because it greatly reduced interface complexity (compared to XML-based SOAP) and in doing so, it loosened the coupling between different services.

Until we start discussing interface complexity, nobody will fully realize what the drawbacks of TypeScript are. My main problem with TypeScript is that it is most useful when code quality is low (high interface complexity; tight coupling). That's what I mean when I say that it encourages bad programming practices; the people who find TypeScript most useful are those who tend to produce the worst code in terms of interface complexity.


> For example, in your second link, one of the functions expects a 'complex' instance DispatchProps which has a method toggleOn... This abstraction doesn't make sense conceptually. What is a DispatchProps? It doesn't adhere to Alan Kay's notion of a 'message', it's clearly a structure (it's a complex one because it exposes a method). Components should communicate to each other via messages, not structures.

the `mapDispatchToProps` idea has been in `react-redux` since the very beginning. I don't understand what are you complaining about here. If you prefer, you can ignore this practice and pass the whole dispatch function to the component, and then call `dispatch(actionCreator(params))` directly. Typescript won't get in the way of you doing that.

> Also, the method builder.addMatcher(...) accepts a function as an argument - It doesn't seem like an ideal abstraction either. Functions are not messages.

The addMatcher API is not concerned with sending messages, it is concerned with receiving them. You are specifying what should happen when a certain message type arrives. Hence it seems normal to me to specify that as a function that given the message does some stuff. Since these are reducers, the "does some stuff" part is: given a previous state and an action of type X, compute and return the new state Y.

> Also, the thunkSendMessage function signature is very complex; the return type is highly convoluted. That's definitely not a message.

Of course not. It is a function that sends some kind of message after asynchronously calling some API and getting its response, or another kind of message if the API call failed. That's what the complex type says. The usefulness of thunks is that you do have this notion of "ongoing asynchronous process that may succeed or fail" explicitly represented (so you can for instance cancel it), which you'll have a much harder time if you want to represent by using just actions and reducers.

> You can already see the complexity seeping into the interfaces. Just a few years of TypeScript is distorting the original philosophy. I remember Dan Abramov was very careful about what to pass into functions and what to return from them and made it a point to encourage cloning objects using the ... spread operator.

I think you are misremembering some things here. Dan Abramov used to advocate for a strict separation between presentational and container components [1], including the use of `connect` in exactly the same fashion you are criticizing in your first point.

Likewise, cloning objects using the spread operator was never a goal in and of itself. Dan advocated for it as an easier way to avoid mutating the state. Not mutating the state was the point here. Redux-toolkit accomplishes this point by using immer, a library that conceptually gives you a copy of the state so you can just mutate and return that instead (with the advantage that non-modified parts are not cloned).

Also, you can just not use that and return spread-operator-cloned objects from your reducer functions instead. Everything will keep working normally (and passing the type checks) in that case.

[1] https://medium.com/@dan_abramov/smart-and-dumb-components-7c...

PS: Thanks for your work Mark!


My personal expeirience with TS is that when I applief it to my JS code I always found additional bugs in it that I wasn't aware of.

Some of the bugs exposed were the bugs in the 'flow'.


As someone that's extensively testing their application, I don't get the bug argument either. Sure some bugs are found during compile time. Actually, it's not bugs the compiler finds. Rather it's inconsitencies between types and the rest of the code.

But when testing properly, these should anyways be found later. Or am I missing something?


It's a given that you can reduce the number of bugs in your app eventually, with any method/tool you can imagine, given enough time. That'd be still true if you were designing using sticks and stones on sand. Why not use another tool that helps you fix bugs easier, faster and sometimes spare you from writing them at all?

Why so aggressive? I asked a simple question out of interest.

Again, I think there's a difference between bug and a coding error. A bug is a mismatch between a software's function and the user's expectation.

And a typed-inferred error during compile time is just a hint that some code is incorrect. It may lead to a bug, but at the point of compilation, it's not a bug.


oh I didn't realize I sounded aggressive, sorry if it came off that way, cheers!

I'd like to point out that if you agree that typescript prevents stuff that may lead to bugs, without getting into the semantics of what a bug is, you may agree that it's useful.


Inconsistencies between types and rest of code _is_ bugs. I'm willing to bet my house that "TypeError: object doesn't support property or method" is the #1 bug in JavaScript's sphere.

100% agreed. But my point is that I usually find these types of bugs really easy when unit testing.

Real unit tests (not integration tests) are poor man's types. Usually when you have very small unit tests asserting inputs and outputs of methods you would simply assert those with types in more powerful languages

Haskell has pure functions everywhere and a much stronger typing system than TS, but unit testing is still considered important.

Someone else will be changing that code later. Knowing (for example) that the parameter is a string will be zero help in knowing what is being done with that string. It also won't help know what edge cases the code was handling.

Also, types give a false sense of security. A new project was integrated into an old website (one with legacy dependencies, but doing millions in transactions every day, so unchangeable). It broke and they couldn't figure out why. They had the TS types for functions, but an old framework (prototype or moo iirc) overrode js built-ins with incompatible versions. Later they got bitten again when that code changed the object types. If they'd been writing js, they would have written dynamic checks from the start, but it's easy to forget that once you compile, it's just js.


You can always describe what has been done with the string by boxing it into an expressive type. Your other example is that when dealing with bad external code you need to do additional checks. That's a specific scenario and every language which exists has to deal with it in the same way. In more powerful languages you could infer which checks need to be done from the type and do the automatically for external code

The assumption here is that there are unit tests that cover the areas containing these sorts of bugs.

It can prove to be false because many projects don't have sufficiently good code coverage.

Of course, it's also possible that there simply are no unit tests at all.

In those circumstances, TypeScript might just result in less bugs overall. While one could argue that unit testing should be commonplace, reality doesn't always live up to that standard.


Types are just declarative unit tests for inputs and assignments.

Let's say you refactor code, then you might be inclined to change a type signature somewhere, which means you'll adjust your program until the compiler isn't complaining anymore. You'll work until the compiler is silent.

The conpiler being silent, here is only an indication that your types and code match. Not that your assumptions about the program match with its outputs (e.g. the user interface).

However, unit tests are usually written to assert assumptions. Or to "freeze" certain parts of the code so that these mismatches don't happen.

In contrast: Types within the source code and depending on your way of thinking about types, many programmers will not see them as "declarative unit tests".

In practice, this means that you sometimes get "surprised" by some unit tests that are failing after a refactor. That's good because it sheds light where you've made mistakes when changing your code.

To some degree, of course, this is true for types. E.g. they will always help you to point out when two APIs mismatch. However, a test usually is contained within a unit with a clear description motivating its existence. It's so much harder to accidenitally changing a test for the worse than it is to change a typed function signature for the worse.

Lastly, very often functions are crucially dependent on input values and not their types. So even if, in a dynamic language, you get b=0 input into div(a, b) return a/b and it's a valid type, you should test for values as in this case as you can't divide by 0.

So in many cases, even with inputs it'd be necessary to unit test e.g. function signatures etc..


>However, unit tests are usually written to assert assumptions.

As you say, types are a more declarative (rather than procedural) way of asserting assumptions.

>In practice, this means that you sometimes get "surprised" by some unit tests that are failing after a refactor. That's good because it sheds light where you've made mistakes when changing your code.

>To some degree, of course, this is true for types. E.g. they will always help you to point out when two APIs mismatch. However, a test usually is contained within a unit with a clear description motivating its existence. It's so much harder to accidenitally changing a test for the worse than it is to change a typed function signature for the worse.

I'd argue it's just as true for types as it is for unit tests, if not more so. You can get "surprised" by the compiler when refactoring methods in just the same way. I'd argue you get more information with types, because it hooks into the LSP and identifies everywhere in your code that now fails. In contrast, a unit test only tells you that unit test failed. It is still up to you to find the actual locations in the code. In this way, unit tests can be thought of as a parallel program. This isn't true of types, which are directly embedded into the program. Put concretely, if you removed a property on a type then the LSP and tsc would tell you every single place that property is missing.

That's the crux of my argument. Types have better tooling, which helps with both the "delclarative unit test" part and equally importantly with refactoring. You can check for more things with types, like whether your `switch` statement exhaustively goes over every option or is missing any. Your tools also understand the types (like the LSP), which helps with refactoring.

I'd also argue that types are usually easier to understand than tests. While good unit tests can provide good examples on how to use an API, types exhaustively tell you what a thing is and what it is capable of.

>Lastly, very often functions are crucially dependent on input values and not their types. So even if, in a dynamic language, you get b=0 input into div(a, b) return a/b and it's a valid type, you should test for values as in this case as you can't divide by 0.

That's a great point. There's absolutely still a place for tests. Types are not a like-for-like replacement, and each have their strengths, but there is significant overlap. If one wanted to exhaustively enumerate in options in a `switch` statement, and ensure division by 0 issues, then they would need both.


Then the compiler is your unit test framework

Typescript is approaching C++ complexity since this is pretty much the same answer that people give when someone complains that C++ is very complex.

That's like saying any country that talks about how democratic it is must be like the Soviet Union. Sometimes you have to actually look at the details behind the claims. There are specific problems with C++, and its features are often not orthogonal enough to use independently. That doesn't mean that it's impossible to add orthogonal features that don't complicate a language for people who don't use them, it only means that C++ has failed to.

How could typescript be approaching C++ complexity when it's just JS with types? The advanced typing features don't affect the semantics of programs. They just make the type system more expressive so more things can be validated by the compiler.

In other words, typescript doesn't make the language any bigger. If you're struggling to understand typescript code you could just ignore the types, and you're back with regular JS. This is different than the issue some people have with a big language like C++, where the sheer number of features can make it hard to get a grip on unfamiliar codebases.


You’re right about typescript, but in my opinion the same thing could be said about C++. You can say that, superficially, C++ is also just “C with types”. You can write “C in C++” and ignore the types, as many developers do, and risk runtime crashes or errors.

I have to deal with this sort of code on a daily basis. When I talk to the people who wrote that code, they mostly shrug and say that C++ was too complicated for them, they just used what they know.

The flip side of this is people (like me) who know C++ pretty well, keep track of most of the advanced features, and use them semi-regularly. Unfortunately, this effectively creates a barrier to entry to those same other devs. You can blame those other devs all you want for not keeping track of the language, but what if they don’t want to and have other, more important thing in their lives than to keep track of the language? So I’ve come to the conclusion that in my opinion the problem is you (or in that case, me), not them. If you want your code to be long lived and maintainable you have to take the people around you into account. I’ve come to consciously limit the number of C++ features I use in code other people see to a bare minimum.


C++ is not just C with types.

Yeah, you're absolutely right. I tried to equate typescript adding types to javascript with … all the things C++ adds on top of C (and then some), but the analogy doesn't really stand if you take my words on face value.

But the basic premise holds, I hope.


Well, for one thing, I want my code to pass typescripts type checker. So I can't just ignore the types and go back to regular javascript. I've got to understand the sometimes complicated static typing semantics

The TS devs adding support for more advanced types in tsc doesn't make it any harder for your code to pass type checking. That just depends on (1) how strictly you have configured the type checker and (2) how specific you make your type definitions.

The more advanced types just allow more specific type definitions. So if try to you them, and you can't get them to work... you can just avoid the advanced features and leave the type definitions more vague. Just like how if you can't get the basic types to work, you can always use "any".


And now you are changing your claim, showing, quite frankly, that I was right. Your original claim was bogus.

Furthermore, your new claim is also nonsensical. The fact that I don't have to use all the advanced features of my languages doesn't alter the complexity of the language.


I'm not sure what you mean by my claims. I'm just saying there is a big difference between adding features to the language and adding features to the type system.

You said "How could typescript be approaching C++ complexity when it's just JS with types?" The implication being an assertion that typescript cannot be getting as complicated as C++, and that's simply isn't true.

Given that the type system is part of a language, I have no idea what you mean by trying to draw a big distinction between adding features to a language and a language's typesystem.


That seems like a pretty superficial connection.

I've seen the same claim made about the more obscure features found in application frameworks, and in both cases the problem is the same: there will always be that one guy on your team who takes delight in finding a complicated and mentally burdensome solution to an otherwise simple problem, which requires everyone on the team to at least be aware that certain constructs exist.

It's great that Typescript is catching on, and we're starting to adopt it in my team, but I hope the TS people don't think they can keep adding new features to it in perpetuity, otherwise it's going to turn into C++.


Stage 0: JavaScript without types is too hard to maintain.

Stage 1: Put types into JavaScript.

Stage 2: TypeScript types are not expressive enough.

Stage 3: Put JavaScript into types.

I'm looking forward to stage 4 where the expressions inside TypeScript type annotations get so complex and hard to maintain that someone creates TypeTypeScript that lets you put type annotations inside the code inside type annotations.


Well, in all seriousness that’s basically what dependent types are.

It will be like “there is an error compiling your compiler sir”

Or even the language. On this point: type systems are hard to get right, we know that the Java type system is unsound, even ignoring its covariant array types.

https://news.ycombinator.com/item?id=13050491


This is called dependent typing, and is supported by some languages such as Idris (https://www.idris-lang.org/)

And 10 years later, someone will eventually bring back dynamic typing and every one will go monkeys

This is the feature of TypeScript that I'm most looking forward to. Default all variable and constant declarations to type 'any' so that TypeScript can look identical to JavaScript. Then we can remove the transpile step from our projects.

You can already do exactly that. Just put .js at the end of your filename.

Or set `noImplicitAny` to false if you want to retain some type checking.


I need to use the '.ts' extension because my boss demands it. TypeScript needs a feature to allow me to fool my boss into thinking that I'm using TypeScript while actually using JavaScript.

TypeScript has dynamic types. It's a superset of JavaScript, not a separate language.

It's still there. `any`.

but you don't have to type it now! it's literally a revolution in developer efficiency!

...and all the older folks just keep pointing at lisp and shaking their heads.


You don't have to type it in typescript either.

Just don't turn on the `noImplicitAny` compiler option. Yes, that's right. It's the default. https://www.typescriptlang.org/tsconfig#noImplicitAny

Shake away, lisp folks. If you can show me how to run lisp in a browser with tolerable performance and debugging, I might understand why you're shaking your heads.


I think the lints are on by default, though, so it'll compile but you still get warned.

That is ingenious! That would get me excited about TypeScript! Dynamic typing, no cumbersome transpilation step, no source mapping, 100% backward compatibility with JavaScript. That would be a dream come true.

We need a way to go back to JS without hurting the egos of TS community. It will require a massive marketing innovation to achieve this.


As a member of "the TS community", I hereby authorize you to go back to JS. My ego remains un-damaged.

Thanks but I already took matters into my own hands and quit my last company which forced me to use TypeScript for several years.

Can you tell all the companies to stop obsessing about TypeScript and to allow developers to use JavaScript too? That would be awesome because now it's almost impossible to find a JavaScript job.

I'd rather get social security and live under a bridge than go back to TS. That would be a more productive use of my time.


> Can you tell all the companies to stop obsessing about TypeScript and to allow developers to use JavaScript too?

I don't have that much influence. Most of those companies have come to their own conclusions.

At my company, the reason we wouldn't allow that is that it makes the codebase more difficult to approach for a new developer. Knowing the types accepted and returned is helpful for reading unfamiliar code.


As a toy project, I'm actually experimenting with this, but instead with Lua.

https://github.com/capsadmin/nattlua

You can see some examples in the readme.

To me it makes sense that the typesystem is basically lua rather than a mathematical ish looking language.

It could prove to be more difficult to work with but I guess I'll find out.


> Put JavaScript into types.

It's not without precedent - Haskell has a Turing complete type system. (There's also Scala, Rust, and C++ templates which are all arguably Turing tarpits.)


TypeScript also has a Turing-complete type system, if I remember correctly.

The joke in the release notes here asking developers not to publish Fibonacci computations in the type system to npm is not entirely a joke.

So, generics?

At some point it would be better to just ditch Javascript, which was a mistake anyway, for Typescript or a (non Google controlled) language as default browser language.

I don't understand this type of cynical criticism. You don't have to use features you believe are "so complicated". Just don't use them. You said you love typescript, and everything you love about typescript remains intact to your satisfaction. It's not as if these features emerge spontaneously from a vacuum, the typescript team is working with the community to address specific needs. You didn't even offer any specific criticisms, just a vague dismissal. Just don't use it.

> I don't understand this type of cynical criticism.

I don't think my comment sounds cynical.

> You don't have to use features you believe are "so complicated".

One has to be fluent with reading type definitions thus have good understanding of the type syntax. I must admit, from time to time I could not make sense of them easily when reading declaration files from third parties.


You don't typically need to examine 3rd party types, consuming them requires little cognitive effort because the IDE/typechecker will generally tell you everything you need to know at the consumption site.

If you ever do have to dig into a really complex type, that might be painful, but keep in mind that the reason we have more sophisticated types is so that we can maximize correctness and reduce the chance of bugs, the fact that you're getting a type error at all means whatever you wanted to do is definitionally unsafe, so it's good that typescript is empowered to tell you not to do this, even if you don't immediately understand why.


> You don't typically need to examine 3rd party types, consuming them requires little cognitive effort because the IDE/typechecker will generally tell you everything you need to know at the consumption site.

Yes you do, you need to be able to read complex type signatures in order to know what a function or a method accepts, and for that you need to understand Typescript's type syntax, it what it is all about at first place. In case you have a compiler error you need to understand how the type system works to make sense of an error. You can't deny that simple fact, that's the whole point of typescript.


That said, you're still generally stuck between a rock and a hard place when it comes to a complicated type definition as the non-annotated alternative (e.g. in a dynamically-typed language) isn't necessarily any less obtuse.

Lots of people simply prefer to know at compile time that their assumptions were wrong than having to reverse-engineer how they were wrong from stack traces at runtime.


> Yes you do, you need to be able to read complex type signatures in order to know what a function or a method accepts

If you don't understand the type of input that's appropriate for a function, a lack of type annotations does not increase your understanding, instead, your incorrect assumptions are literally left unchecked to wreak havoc in live systems. Like I said, it's better that typescript stop you from doing the wrong thing even if you don't immediately understand why it's wrong, but when in doubt most people just check the relevant documentation, same as if they lacked type annotations.


You're entirely missing the point of my initial comment, which is about the complexity of Typescript's type system since it has to be able to represent a large set of complex types since dynamic languages such as Javascript are highly polymorphic. And next time stop accusing people of cynicism randomly.

Chiding a static type checker as a dynamic language based on the sophistication of its type system reads like a cynical joke to me.

I didn't miss your point, but maybe I didn't explain mine clearly enough. You don't have to use complex types, even with 3rd party libraries, just figure out what goes into the function the same way you would if there were no type annotations.


> Chiding a static type checker as a dynamic language based on the sophistication of its type system reads like a cynical joke to me.

And that's not what I said. Stop trying to pick up a fight with me or something by misrepresenting what I've said just for the sake of hearing yourself taking, it reflects bad on you, especially you doubling down.

I only talked about the fact that there is a limit to the degree of complexity Typescript types until it itselfs turns into a javascript interpreter. your answers have absolutely nothing to do with that matter.


At the usage site generics can usually be fully inferred, so something that uses these features may look like a horror show at the implementation site, but when you actually use it it will just look like normal string literal types.

> One has to be fluent with reading type definitions thus have good understanding of the type syntax. I must admit, from time to time I could not make sense of them easily when reading declaration files from third parties.

That makes sense when comparing it to other languages, but compared to JavaScript: those type definitions are a pure bonus? In other words, if they'd have been written in JavaScript you'd still have to make sure to pass in the correct data, but you'd have only the documentation to guide you there.

(Of course, if you yourself are using TypeScript, you do have to know how to make sure that TypeScript knows that you're passing in the correct data. But the flip side is that the more complex types will allow for better inference that helps your editor to provide better guidance on how to give it the correct data.)


I think is valid criticism ... even if you pick a subset sometimes the types are "forced on you" by libraries or even by code written by coworkers.

TypeScript advanced types are there to model existing JavaScript codebases, but when designing software from scratch I've seen that is too tempting to go crazy with the types, ending with something pretty messy (JavaScript! :-).


All that flexibility only has use when the TypeScript code needs to interact with something terribly unruly: JavaScript code or some API that isn't typed properly. While it's nice that you can model in your code, it brings the distinct possibility that people will start using it to write very sloppy types and/or patch things up afterwards, leading to worse code.

There are research teams at Microsoft exploring a native form of TypeScript ( https://www.microsoft.com/en-us/research/publication/static-... ) but the TypeScript team will never probably look at native support given the goal is to explicitly be JavaScript + Types, and that domain helps balance well with all the other ecosystem players ( https://www.youtube.com/watch?v=8qm49TyMUPI )

Fascinating - thanks for mentioning this work at Microsoft Research.

> [A Static TypeScript] program is compiled to machine code in the browser and linked against a precompiled C++ runtime, producing an executable that is more efficient than the prevalent embedded interpreter approach, extending battery life and making it possible to run on devices with as little as 16 kB of RAM (such as the BBC micro:bit).


The expressiveness of the type system far outweighs its complexity, imo.

To give a commonly-encountered example, rather than the easily-introspectable options object whose schema is specified by a zero-runtime-cost TS interface, in a traditional OOP language like Java you would write an XOptions / XOptionsBuilder class to maintain type safety, which is at once more opaque, with less predictable behavior.


It's an unfortunate situation, because so long as people are writing popular frameworks/libraries that horribly abuse the dynamic nature of JavaScript, the TypeScript team sort of has no choice but to try and wrestle the language into supporting it. But I agree that this is getting out of hand. I still like TypeScript but with each new release it's getting harder to claim with a straight face that it's "easier" than vanilla JS when you have to grok this increasingly convoluted type system.

> horribly abuse the dynamic nature of JavaScript

It's not a horrible abuse if you aren't trying to make the framework/library statically typed in the first place.

Idiomatic APIs for dynamically typed languages use the flexibility of dynamic types. Idiomatic APIs for statically typed languages use the structure of static types.

The problem comes when you try to put one into the other's world. It's like watching a fish flop around on land. There's nothing intrinsically wrong with fish locomotion, it's just not optimized for that environment.


>It's not a horrible abuse if you aren't trying to make the framework/library statically typed in the first place.

Of course it is, because even if there's no type system the people using the library still have to make sense of the complexity. A function with a dozen, context-sensitive overloads that accepts data without a clear pattern isn't just hard to type, it's also hard to understand for a user. Even with dynamic typing you should be able to clearly state what a function does.


Presumably, to work with the original JavaScript, you still had to understand the convoluted type system that you created, or else be ok with not being able to reason about your code. At least with typescript you have the computer helping you reason about it.

Yup. It's an attempt to formalize the mess that already exists in many JS libraries.

That's why, even if I don't plan on using Typescript itself for a project, I prefer libraries that have type declarations or are natively Typescript. It shows the library authors at least attempted to understand the type system they were creating.


My favorite part about typescript becoming ubiquitous is that it is making people into more disciplined JavaScript programmers. The amount of cutesy overloading that was common in the early node days seems to be slowly waning.

> cutesy overloading

Haha, great way to put it


I watched a great talk at TSConf about playing Tic-Tac-Toe using the type system. Not TS itself, purely using the type system.

https://blog.joshuakgoldberg.com/type-system-game-engines/


Type-level SQL interpreter: https://github.com/codemix/ts-sql

Lots of other great examples here: https://github.com/ronami/meta-typing

> I love typescript but its types are becoming so complicated it's turning into a dynamic language...

Except all of it can be tested and validated at compile time so... not dynamic?


I love how TypeScript makes the rich semantics of JavaScript visible.

When some programmers favored dynamic languages over the statically typed others looked at them like at mad.

The thing is that dynamic languages fans just wanted to have semantics as rich as this one and were willing to sacrifice even type checking altogether for it because no statically typed language was willing to deliver this semantic in any reasonable manner.


What? It only turns back into a dynamic language if you give up and start using “any”. The more detailed your type system, the more distinctions you can represent in it, and the more unintended behavior you can catch early. It can become more challenging to work with, but you’ll get end results that are more robust, which is one of the goals of strong typing.

I think a lot of the complexities of the type system come from the fact that types have to describe complex behavior in a static way, while still being useful (you could type everything in your code as `any`[0], but that wouldn't be very useful). JavaScript is complex, so statically describing its behaviors is also complex. At the same time, most of the complex types are types you never have to see or use, unless you're the maintainer of a complex library and you want to provide really useful types.

[0]:https://github.com/DefinitelyTyped/DefinitelyTyped/blob/mast...


I'll take 70% type safety over 0% any day

With C++ the usual solution is to use only a defined subset of it and make sure that lint checker is used in presubmit checks. Exceptions to other types should be allowed, but strongly discouraged when working in a team (which TypeScript is used for)

> I'd like to see Typescript compiling to "native" as well.

Yeah I'd love something that just compiles .ts to a binary. But I can already hear the cries of "embrace, extend extinguish" if that happened.


This is actually why I stopped using it. Forcing types on a dynamic language like this seems to inevitably lead to really, really complex type definitions which just confuse things tremendously. It almost seems easier and better to just straight up use JSDoc with some good standard documentation to back it.

Could you give an example?

In years of using TS I have had to craft a few exotic generics, but have otherwise never ran into "really complex" types.


Angular + rxjs.

You end up with write only code that while typed is overly complicated.


My point is obviously subjective, so you may not agree, and we may have different tastes for what simplicity/complexity looks like.

https://github.com/DefinitelyTyped/DefinitelyTyped/tree/mast... felt like to me as one popular example for what should be really simple in practice.


> what should be really simple in practice

I thought you were talking about types in your own code. Lodash being a large, heavily overloaded and generic library it seems fair that the definitions are complex.

When it comes to external code like that, I don't think you can argue against the value of intellisense and safety (especially when upgrading versions). Those are in my opinion the biggest benefits of having strong types.


It could depend on the alternative proposed. Adding types to dynamic languages might actually exhibit this behavior, of inevitably leading to really, really complex type definitions which just confuse things tremendously.

In your case, your alternative is back to untyped, unsafe JavaScript. Maybe that's not so much better. Another alternative would be to move to a language designed with types in mind from the get go, say OCaml (with ReScript) or Java (with JSweet), or others.

Bottom line, I think that claim is still true:

"Forcing types on a dynamic language like this seems to inevitably lead to really, really complex type definitions which just confuse things tremendously"

But if that is a good or bad trade off for you to make, I think that's very contextual, it depends. Maybe that's okay for someone, and they'll use TypeScript. Maybe that's not okay, and they'll go back to JavaScript. Maybe they'll instead explore a ground up typed lang like Java.


It's more characters/more noise to the code to add a JSDoc comment block than it is to add a colon & typename next to the variables/values they belong to.

Different things; the JSDoc is purposely bloated-looking on an initial generation because it's meant to give you the space to detail out the semantics of the parameters. You would frankly have to do the same thing with any type system anyway, JSDoc or similar just gives you that combined without any build system or tooling.

Types say what, docs say why.

The semantics of a number or a string aren't described by their basic type.



Of course it's Yusuke Endoh. :)

My brain hurts, this is amazing.

I was talking to a friend the other day about the backlash against Clippy in Office. Reading these release notes, can't help wonder if IDE's will soon need an equivalent.

Something like -> "Hey! it looks like your function only accepts a fixed set of strings. Would you like to use String Literals?"

I don't only code in Typescript so it's not practical for me to keep up with every language I work with.

Search works great if you know what you want, then you'll end up on these excellent release notes or a stackoverflow post and off you go. The trouble is when you don't know what you don't know.


Have you ever used Visual Studio? It sounds like you're describing the code analyzers that have been available for several years now.

For example, with an opportunity for string literals, there will be a light bulb in the left border. Pressing "Ctrl ." will bring up a list of suggested fixes. You can opt to select one, hit Enter, and the changes are automatically applied.

I believe there are similar extensions for VS Code.


VS Code does this for TypeScript out of the box. For example if you have a function with Promises it will suggest it be rewritten (and rewrite it for you) using async.

It's very dumb compared to what's available for C#, though. C# code helpers are able to code a simple class with their suggestions.

The only reason you need your IDE to write classes for you in C# is because C# has nominal typing and tons of boilerplate (ex: getHashCode()).

I've written millions of lines of C# and hundreds of thousands of TypeScript, and I use JetBrains JDEs for both these days. Class generation is absolutely not something I need or miss in TypeScript.


I also write TypeScript, daily for the past 5 years. I wasn't talking about boilerplate, the C# analyzers like ReSharper can determine which variables are you probably going to use, what methods are you going to call, and what parameters will you give them. I'd love to have them in TS.

JetBrains IDEs do all of that. They just don't really generate code for the most part.

That boilerplate is automatically added by the compiler. You don’t need an IDE to do it. There are tons of examples of very lean C# that can be written in notepad.

Before records, the compiler did not generate code required to properly use classes (or even structs) as value types. There's a reason everyone is so excited about records.

I have apps with thousands of lines of implementing comparison/equality interfaces, overloading operators, etc. for my data objects. You can definitely write it all in Notepad, but it would be a massive waste of time.


Modern IDEs already have all that, it's a basic feature of being an IDE in the first place as opposed to just a text editor.

I use modern IDEs exclusively, and yet I have to fish for features like this (typically by searching for an error message) all the time. I'm sure there are many instances where I've come out with sub-optimal fixes because I didn't not know about a feature and my IDE didn't either.

IDEs will make suggestions for fixes to apply after you've written the code. Clippy made suggestions a lot earlier than that.


Automated code sniffing sounds like a fantastic idea. Perhaps we’ll look back to the 2010s as the dark ages, when all we had at our disposal was peer review of our codes?

Review early and (fairly) often, lest you embark on implementing a FrobNibulatorFactory from the ground up only to be told there is one already in lib/util/frob/util/tools/misc/nibulation.

Not everyone has peers, alas. The imaginary ones help (rubber ducky, you’re the one...)


This has been around for years in .NET and Java IDEs and I'm sure in many more places. Jetbrains' ReSharper is famous for its complex code analysis with useful hints to improve code readability and robustness. A single click can refactor your code.

.NET's Roslyn compiler supports analyzers that you can write yourself which also support code fix providers to automatically refactor code. All analyzers and code fix providers are written in .NET languages (C#, F#, etc.), so the possibilities are endless.


Yes but how sophisticated is the code analysis? I’m curious if there are tools that can do things like: - detect and replace first party algorithms with third party libraries. For example, recognizing a hand-rolled binary search implementation and swapping it out. - recognize design patterns - apply suggestions based on solid principles

For performance you subscribe on a specific syntax kind in the syntax tree (like if, condition, operator, variable definition, etc.), the compiler calls your analyzer, and then you get a context object where you get full access to the syntax tree. After that you are only limited by your imagination.

I believe you can also filter by semantics as well.

Well in C# the collections have efficient searching and sorting built in :) but as for whether they can recognize design patterns — VS and R# can definitely move members and symbols around fluidly within a class hierarchy.

There's definitely teams at MS exploring about this problem (e.g. IntelliCode is my favorite for TS https://docs.microsoft.com/en-us/visualstudio/intellicode/in... )

They don’t cover everything and it requires setup, but I like using linters for this kind of thing - these types of suggestions can often be standardized in a codebase and auto fixed by tools like ESLint nowadays, it’s also a good way to make people aware of features.

Interesting, any examples you can share?

JetBrain IDEs are pretty amazing at this.

How I actually learn to use the new language features ; )

Rust has a tool called, you guessed it, Clippy, that does very similar things to what you suggest: https://github.com/rust-lang/rust-clippy

The Rust compiler exposes a lot of information, so it's actually quite powerful in my experience.


And the JavaScript/TypeScript equivalent would be eslint. (Which doesn't have as brilliant a name and has less information available to it, but it's still very helpful.)

I always joked about how IntelliSense is just Clippy for your IDE. But yeah, it's pretty useful. coc.vim and tsserver save me from getting fired on a daily basis.

For C# there is an extension called Sharpen [0] that alerts you of new features in the new languages, it is quite nifty when you are ready to move to the next version of .net core for example and want to know where you can remove code etc.

[0] https://marketplace.visualstudio.com/items?itemName=ironcev....


Actually, I think it would be nice to have a chatbot in my IDE that would analyze my code and help me best practices.

This is exactly what the Elm compiler does:

https://elm-lang.org/news/compiler-errors-for-humans


Would be a natural extrapolation of Elm typecheck ergonomics. But it may be frowned upon in shops because the IDE does too much ?

> The trouble is when you don't know what you don't know.

At least the pain is a good indicator that there's something you're missing :) IRC has been somewhat useful for TS, albeit with mixed results compared to say, the gentle gods you will find in #postgresql.


A good application for ML

its funny cos its true, just not the way you mean.

To pile on, Typescript has been a lifesaver so far. Yes, the type system is rather complicated, but one can progressively use the advanced type, starting with typing numbers, strings, interfaces etc.

Having worked with Clojure for 4 years, and a JS (ES6 and +) codebase, static typing is amazing.

There are far fewer bugs at runtime because of incorrectly setting or forgetting to set keys in a map or a property or accidentally passing the wrong value to the property. Yes, interop with JS is a little messy, because of the dynamic nature of JS, but a large class of errors can be significantly reduced by Typescript.

As for the argument , unit tests can catch most of the problems Typescript solves, my counter is that

It reduces the amount of tests you need, to check getter/setters,types etc SIGNIFICANTLY. You can focus your unit tests on the more functional aspects than having to worry about basic property access.

All high-level languages have a learning curve. I don't expect to pick up the nuances and all the little bits of Typescript in a matter of weeks or months.It might even take years, but I think Typescript and its experimentation with providing a well typed Frontend interface is amazing !


> You can focus your unit tests on the more functional aspects than having to worry about basic property access.

Exactly! unit testing a return type is such a backword step in programming


> incorrectly setting or forgetting to set keys in a map or a property or accidentally passing the wrong value to the property

When you wrote/write Clojure, did you ever use spec? Any insights into how it compares with TS? They seem to give similar guarantees for this aspect, but I haven’t played with spec yet.


It is kinda hard to compare both, as the paradigms are quite different (FP vs OO),

spec , IMHO, seemed oriented towards validating properties and values in maps, as Hickey mentions in his talk, it is all about manipulating data.

Spec is opt-in. unless you are working with code that has already been spec-ed , you will run into the same problem that TS + JS interop has.

I found spec a little too intrusive (especially the namespaced keywords) and sometimes I wasn't sure what it was meant for, TBH.

In my first company, it was a bunch of cowboy programmer that didn't really care about typing the codebases, too cool to write unit tests and simply wrote enough end-to-end tests cases to solve the problem. I was just starting out and had NO idea on what was good programming practice.

In the second firm , we had used schema to type the codebase partially and it worked. But the type checks weren't compile time , but rather during unit tests and when running with type-checking enabled (I think this is the same for spec as well)

I think the advantage of TS comes from the static types (+ checking). You get immediate feedback before you can even compile it into javascript.


TypeScript is so powerful at this point I think the only really big thing they're missing is typed exceptions. After that they maybe should just close it for new language features and just optimize performance and add editor features (more automatic refactors) :D

Better to return a union of custom result types. Save exceptions for when stuff goes really wrong.

Unfortunately that’s not idiomatic in many common JS/TS scenarios, and requires deep familiarity with the internals of any APIs or third party libraries that throw errors. Optional typed exceptions would be right in line with the TS approach of modeling real world JS usage, but for the syntactical question of exhaustive error handling. (Of course that could be addressed separately or not at all.)

It would be better to improve typing of Promise and try/catch than additional layers, IMHO

There are a lot of interesting possibilities with template literal types. I've discussed some of them here:

https://davidtimms.github.io/programming-languages/typescrip...


> function setVerticalAlignment(color: "top" | "middle" | "bottom

Why is alignment accepting a color? It doesn’t matter how good the type checking is if you are creating the wrong type to begin with.

That aside, congrats on the release!


What's in a name? That which we call a rose

By any other name would smell as sweet.

So Romeo would, were he not Romeo call'd,

Retain that dear perfection which he owes

Without that title.


Looks like it was a typo and they fixed it!

I don’t know anything about that particular function, but “color” has several distinct definitions (colors?), some of which even make sense here.

Finally noUncheckedIndexedAccess! Awesome, thanks TS team :)

A lot of typstronauts in the comments here defending all this insane complexity. There is something fun about complexity, but you don’t need to take a blind eye to how much harder it makes life to admit your appreciation.

Many TS advanced features are not designed for everyday end-products. They’re designed for:

- more accurately and more maintainably typing existing JS interfaces

- enabling more advanced compile time features for libraries

I’m probably one of these “typstronauts” you reference, I have extensively used nearly every advanced type feature in library code. But I’ve seldom found a need for any of them in consuming code. My approach since I adopted TS has always been:

- libraries can and should provide types at whatever level of complexity necessary to make library usage flexible, safe and simple

- end product code should use simple types wherever possible and depend as much as possible on the flexibility, safety and simplicity of the interfaces provided by those more complex libraries


The template literals living on the type side of the language looks immensely powerful and complex.

I hope I never have to use it but that its existence makes typed libraries more "correct".


On Reddit the other day, I saw a Typescript (type-safe) implementation of a `get()` function like `get({ a: { b: { c: ['foo'] } } }, "a.b.c[0]") === 'foo'` using recursive string templates in the type definition.

e.g. get({ name: 'Toby' }, "name.prop1.prop2") would fail as you're trying to call .prop1 on a string.

Really blew my mind. I'm used to having to give up simple APIs like that when moving to a statically-typed language.


You might see if your language supports lenses - a lot of ML-like languages can do something like:

    get({ a: { b: { c: ['foo'] } } }, a *> b *> c *> _0)
and a macro to unfold the lens from a literal string is probably something that exists as well.

For sure. I just think that sort of string API is a great example of something we've historically had to trade off for type-safety.

There are of course nice qualities of simple functional composition and custom operators, though it comes with another layer of overhead that you wouldn't necessarily always choose over a simpler API in every case. Richer typed expressiveness lets us pick the right trade-off without being forced.


My point is that it's something a lot of statically-typed languages have been able to do for a while - the classic lens paper/talk is about 10 years old at this point, and the literal-type support that you'd need to convert a string to a typesafe lens has been around for a pretty similar length of time at least in some places.

I like monocle-ts for lenses in TS https://github.com/gcanti/monocle-ts


I think Uppercase<T> and friends are really interesting (along with all of the other things, of course).

It will be interesting to see if they add metaprogramming capabilities next (allowing anyone to create a class like Uppercase<T>), although I’m not holding my breath.


Yes, this currently exists in 4.1, unless I somehow misunderstand you. I’ve been making heavy use of it with the beta, it’s extremely useful.

Edit: see this SQL made out of the type system: https://github.com/codemix/ts-sql


I still have problems with the false sense of security (ie, the type system lying to the developer).

https://twitter.com/toolslive666/status/1309533781137780741/...


That’s fixable in this release, though behind a compiler flag https://devblogs.microsoft.com/typescript/announcing-typescr...

Agree with the general sentiment though.


This might be your lucky day, check the `noUncheckedIndexedAccess` option introduced by this release!

Fortunately every new TS release (at least since I’ve been using it around 2.0) improves the safety of well typed programs, or at least allows you to opt in to that additional safety. There are risks of course in the hands of people who don’t know its limitations. But the more TS is adopted and the more strictness is embraced in the community, the lower that risk is.

I started using TypeScript about 7 months ago. And heavily in the last 3 months.

I'm up to speed now, but I still feel I'm missing an opinion.

e.g. I have naturally been drawn to "maximally infer" types, whilst I see other developers would rather re-create/co-locate a type with its function/component.

Does anyone have a repo where they think the TS is near-perfect? Or even better, a deeply opinionated Style Guide?


It's nice to explicitly state types at module boundaries, which will make it easier for other developers, theoretically allows some type-checking optimizations, and will prevent you from accidentally changing your API.

I've been starting to build speed-focused TS type-checker that is intended to be used in single-file editors like VIM to highlight errors, because it starts checking from a single file and just traverses files necessary to check types within that one file. If you defined your types at a module boundary it can bail out super early, since it doesn't need to look at the rest of the code of that module at all.

Most linters have rules that will force you to explicitly declare types at module boundaries.

Other than that, do whatever you prefer.


Very cool. I'd recommend reading the built-in vscode extension for TypeScript, because with 4.0 we added a feature very close to that for editors into TypeScript and you can rely on our work instead. Links in: https://www.typescriptlang.org/docs/handbook/release-notes/t...

I’ll second the point about explicit types at module boundaries and add:

- Ideally they should either not be derived types (using eg type parameters), or if they are they should be derived using types from the same layer of your project/service. This makes it much more likely you’ll notice if you introduce breaking changes.

- Types should also between network boundary layers and business logic, regardless of your module structure. This ensures your business logic’s interface is clear regardless of how it’s consumed, making it more easily testable and more portable (eg to other transport formats/protocols).


Go to the VS Code repo. It's written in TS and its one of the best examples of a well written TS app.

https://github.com/microsoft/vscode


IMHO, the next big thing for TypeScript is compilation to native targets. TS could have a "very strictly typed" language subset compilable to a few popular bytecode targets, wasm and jvm at least. Otherwise TypeScript is going to lose competition to Dart, despite being a better language.

I know it's not what Anders wants, but I can't help wondering why there's not an initiative to compile to native binary code from TypeScript... Dart isn't much different from TypeScript and has this ability.

TS typesystem is so flexible.. I feel like it's gonna end up dependent type in disguise.

> Often the appropriate fix is to switch from foo && someExpression to !!foo && someExpression.

This seems... Wrong. Better leave a comment because other people might assume it's a leftover from a refactoring or such.


I don‘t see a problem with it. !!foo and foo are in boolean logic the same, but as typescript does not allow (correctly) to use a string as a boolean value you‘re already doing !!foo for e.g. strings

Yeah, I've only been working in TS for about a year, but I still find this sort of thing wrong.

It's easy to get wrong since it overloads syntax (casting vs boolean ops) and is very hard to google (as many operators are).


Yeah, I've started to migrate to the habit of using Boolean(expr) instead of !!expr simply because it is explicit about intent. I've seen projects with that as a tslint/eslint rule to never allow !! and instead it suggests Boolean() and I haven't quite done that in my own projects, but have considered it.

Template literals... helllsss yesss, been waiting a long time for those suckers.

readability? who needs it when i can now say `this, but camel case and no nulls`

  export type Camel<T> = { [K in keyof T as K extends string ? `${Uncapitalize<string & K>}` : K]: T[K] extends Record<any, any> ? Camel<Exclude<T[K], null>> : Exclude<T[K], null> }

I gotta say I'm quite excited to the refinements to the literal string types.

I'm not. Look at the ridiculous hoops a programmer must jump through…

https://stackoverflow.com/a/63867074

… for what is literally a one-liner elsewhere:

    class User :ro {
        has name => isa => Str;
        has email => isa => Str->where('lc =~ /^[a-z.@]{1,32}$/');
    }
    User->new(name => 'Dave Morrison', email => 'Dave.Morrison@gmail.com');
    User->new(name => 'Van Morrison', email => 'Van-Morrison@gmail.com');
    User->new(name => 'Jim Morrison', email => 'Jim.Morrison.is.my.name.and.it.is.too.long@gmail.com');

You're comparing a type inference task on a use case it was not meant for against a runtime validation task that you can do in JS as well. It's unfair.

I really like typescript and I have used it fondly when developing Angular projects because it is one of the defaults.

In most other frameworks there seem to be some arcane incantations required to include typescript into the build system. I have not looked into this recently - is there a straightforward way of including Typescript into a Rails, Gatsby, Hugo, Laravel, Wordpress, etc. project?

I feel like Typescript popularity would explode if the above level of friction became near-zero.


That is a very nicely written post.

And they keep adding syntax. The complexity of C++ with the performance of JavaScript, excellent.

I would argue TypeScript is much more expressive than C++. Also, most of this complexity is used in type definitions of complex libraries that was designed without typing in mind. Now, normal programmers can enjoy the help from IDE of those libraries, without having to write many complex type definitions themselves.

TypeScript only provides the illusion of type safety. At runtime, it’s just plain old JS where all bets are off. I’ve been bitten too many times. As long as there’s no native TS runtime were stuck pretending we have type safety.

I'm not sure I understand this comment. I can believe that typescript might not be typesafe, but what does the runtime not having type information have to do with that? If a compiler only accepts sound programs, then it shouldn't matter whether the runtime actually knows about the types or not (unless you want to do some sort of reflection, but allowing that is arguably _less_ typesafe). By that logic, you'd also have to consider anything that compiles to native code also not to be typesafe, because there sure aren't types in assembly.

For me, the main place the issue above rears it’s head is in data retrieved from an API. A strongly-typed language would fail loudly and predictably if a blob of JSON didn’t match the expected format. In some situations, that’s preferable to having invalid data work it’s way through the system, or having to manually implement type checking

I’m a fan of the Runtypes library for this use case. If you design your typescript interfaces using the library (admittedly a bit magical to me) you can get runtime errors if API data doesn’t match the expected type


You can use guard functions to validate external data. This same problem comes up in other statically typed languages. You can't guarantee that external data will be in the shape that you expect so you must validate it. If you're just taking your `unknown` API response and immediately casting it to your desired type then that's your fault, not TS

That's what runtypes is.

Yeah, ideally JSON.parse() returned `unknown`, but unfortunately that was added after the current type definition was already in widespread use, and therefore would be too much of a breaking change. Perhaps some day with a compiler flag.

There are a lot of core language types that could substantially improve safety (not to mention inference), with anything explicitly `any` being obvious candidates. I feel like this should be a compiler/lib option for those of us who would opt into the breaking change for increased safety.

But since it isn’t, augmentations may be an option. I’m not at my computer right now so I can’t verify that this would work, but you may be able to add an overload that returns `unknown`. Unfortunately with overloads, I’m pretty sure the built in types would be selected first without some additional specificity.

This feels like a good opportunity for a safer standard library package wrapping globals, DOM, Node built-ins, etc.


Isn't that basically what the noImplicitAny flag does?

Unfortunately no: that only errors when you do not add a type annotation and TypeScript can't infer anything better than `any`. JSON.parse(), however, is explicitly annotated to return any. See [1].

[1] https://www.typescriptlang.org/play?#code/PTAEEkDsDMFMCd6wCa...


Yes, the type signature for JSON.parse() isn't inferred/deduced, it's explicit in lib.es5.d.ts (imported from lib.d.ts):

https://github.com/microsoft/TypeScript/blob/master/lib/lib....

You could provide your own lib.d.ts with `unknown` type returned for JSON.parse. I'm almost surprised there isn't a standard stricter lib.d.ts, though I suppose that linters fill enough of those needs with their rulesets.

Looking at GitHub issues surrounding it, it doesn't appear that there has been an explicit ask to switch the type signature to use `unknown`, probably the next closest was this one, where `unknown` is mentioned in a comment as a possibility but the comment is not addressed in follow up discussion: https://github.com/microsoft/TypeScript/issues/26993

It would be a big breaking change though at this point, so they would probably be hesitant to do so. I thought I saw somewhere there was a proposal to add it as a compiler flag to treat all uses of `any` in lib.d.ts as `unknown`, but I have no idea what happened to that proposal and quick searches don't turn up anything.


Yeah I'd swear I'd seen that too, but I can't find it quickly either.

Ah, I see my mistake. I have been getting warnings about JSON.parse, but they are coming from eslint@typescript-eslint/no-unsafe-assignment. It partially serves this purpose, but probably not as comprehensively as actually having it return unknown would.

You can use something like io-ts or zod to validate external data at runtime.

They are probably talking about accepting serialized data coming from a network request. Typescript doesn’t help at the edge if you just cast your JSON blob into your application model.

That's why you actually have to validate at the edge. TypeScript doesn't really change that fact. It only makes the decision of how you handle it explicit.

I feel like this is the same argument that people give when trying to justify not writing tests. Tools like type-checking and automated tests aren't a magic forcefield that will shield you from any and all runtime bugs. But they do protect against a good percentage of them, and as another commenter pointed out...70% type-checking is better than 0% type-checking.

The point of type-safety is to be able to describe more succinctly what the _programmer_ expects out of the code. As you said, when you get to production, "all bets are off". This still applies with any type-safe language. Types don't exist when you're compiling to machine code, either.


> 70% type-checking is better than 0% type-checking.

I'm not sure it is; the most dangerous things in programming are assumptions that are almost correct, so you don't notice the problem until it's too late. An error whose symptom is an Int actually being a String several files away from where the mistake actually happened can be harder to diagnose than the same error in a untyped language, just because it's so contrary to your expectation.


Huh? The whole point of static typing, in any language, is basically:

- Catch more errors at compile time

- Better tooling (better intellisense, “go to definition”, “find implementations”, etc.)

TypeScript provides both of these benefits just fine. Runtime types give you better runtime errors, and less undefined behaviour, but plain JS (or TS compiled to JS) does that bit just fine. TS types persisting at runtime could maybe be nice for certain use cases, like more powerful reflection, but that’s not type safety.

What’s a concrete example where your program would be more safe if TypeScript types were represented at runtime?


In my experience nobody is "pretending"; within epsilon of everyone knows this, and the experienced folks are writing code against it with something like `runtypes` or `io-ts` to sanitize on the edges of their application.

True, but it's the benefit of incremental adaptation of types on an old code based.

It also encourages better architecture in my opinion, as it forces you to reduce the importance of something being an exact type... As it's not strictly enforced at runtime.


Happened to me This helps me with it at the cost of a little bit more lines of code https://github.com/gcanti/io-ts

I agree somewhat, the errors it misses are precisely the ones which are confusing.

That’s what Deno is for

Deno is also "just JavaScript" with types automatically stripped off. The selling point being "automatically". It doesn't do runtime type checking.

Ah you’re right. Weird, I could have sworn during the original announcement they were going to have support for runtime checking

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: