Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

> in various cases the types are wrong and the compiler doesn’t care

It's disturbing how often I encounter this in TypeScript. Or its inverse: the types are correct and the compiler is wrong. Or a third common problem: the types for a library are incorrect. The unsoundness of TypeScript is not merely theoretical. The compiler is frequently just wrong. For that reason, I am mystified by the amount of enthusiasm for TypeScript that I encounter online.



The most important thing, if you haven't already, is to set the noImplicitAny and strictNullChecks compiler flags. I find that not having these flags set is a common source of Typescript type unreliability.

The flags prevent two behaviors that are a major source of type failures: defaulting some types to "any", and allowing null/undefined to be valid values for ANY type (!). Enabling these flags for an existing codebase is likely to uncover hundreds of type errors that are papered over with the default compiler settings.

W.r.t. upstream types for libraries being wrong, I find the silver lining here is that libraries will usually be happy to accept patches for their types, often with significantly less fuss than a patch to the actual code.

edit: Even with those in place, I also notice that Typescript types are not 100% trustworthy. This often comes down to unsanitized input, use of the "as" keyword, or hidden type violations. The conclusion I've come to is that it's just very hard to type Javascript code, partly because the values themselves are so prone to being mutated. So I agree that Typescript doesn't feel like the perfect solution to me. (I usually use Clojurescript these days).


Thanks for the tip! I'm gonna try this on Monday.

To be clear: I'm not complaining about these third party types. I really appreciate that people took the time to write and share these. My gratefulness is definitely not contingent on perfection. I fully expect human work to be imperfect. Type systems exist to guard against this imperfection. A type system in which type annotations are maintained separately from the implementations, and are routinely debugged is highly suspect.


Speaking for myself, the key selling point for Typescript (and Flow) is its pragmatism. While it might not have the soundness of a Bucklescript or Purescript, it does what its alternatives have chiefly failed to do for me: Allow me to see immediate incremental benefits without having to refactor an entire codebase.

While the alternatives might be in some ways a better start a greenfield project in, the ability to gradually add types to an existing codebase with developers who are just learning the language with the ability make the system sounder over time is a killer feature for me. Also, the big-company backing helps corporate slaves like me trying to sell a new technology to management.


You make a good point. I don't mean that TypeScript yields no benefit. But if I can't trust the compiler 99.99% of the time, then I find that I'm double-checking my work _and_ the compiler's work. It's not clear to me that the benefit outweighs this uncertainty. When you say that your system is "sounder", you don't really know whether that's true if the type system is unreliable.


Weekly, sometimes daily, I run into a situation where flow is straight up wrong in baffling ways, or needlessly developer-hostile, and I end up having to help my junior team members write worse (or at least more verbose) code to compensate.

It's still a million times better than JS without TS/flow.


100% agree with this. If you're using redux the boilerplate of Typescript is huge. To do that all that work and still have the compiler miss problems at the state level deemed Typescript pointless to me.


Not sure which specific issues you encountered, but I had issues with defining actions and using their types in a union to the reducer.

There is a nice little library [1] that fixes this issue.

So a general redux setup looks as follows:

    const FETCH_USERS_BEGIN = "@@FOO/FETCH_USERS_BEGIN"
    const FETCH_USERS_SUCCESS = "@@FOO/FETCH_USERS_SUCCESS"
    const FETCH_USERS_ERROR = "@@FOO/FETCH_USERS_ERROR"

    const fetchUsersBegin = () => action(FETCH_USERS_BEGIN)
    const fetchUsersSuccess = (users: IUser[]) => action(FETCH_USERS_SUCCESS, users)
    const fetchUsersError = () => action(FETCH_USERS_ERROR)

    type IActions =
      | ReturnType<typeof fetchUsersBegin>
      | ReturnType<typeof fetchUsersSuccess>
      | ReturnType<typeof fetchUsersError>


    interface IUser {
      id: number
      name: string
    }

    interface IState {
      readonly isLoading: boolean
      readonly isErrorLoading: boolean
      readonly allIds: number[]
      readonly byId: {
        readonly [key: number]: IUser
      }
    }

    const defaultState = {
      isLoading: false,
      isErrorLoading: false,
      allIds: [],
      byId: {}
    }

    const reducer = (state: IState = defaultState, action: IActions) => {
      // narrowing on types
      switch (action.type) {
        case FETCH_USERS_BEGIN:
          return { ...state, isLoading: true, isErrorLoading: false }
        case FETCH_USERS_SUCCESS:
          return {
            ...state,
            byId: action.payload.reduce(
              (acc, u) => ({ ...acc, [u.id]: u }),
              {}
            ),
            allIds: action.payload.map(u => u.id),
            isLoading: false,
            isErrorLoading: false
          }
        case FETCH_USERS_ERROR:
          return {
            ...state,
            isLoading: false,
            isErrorLoading: true
          }
        default:
          return state
      }
    }



[1]: https://github.com/piotrwitek/typesafe-actions#1-classic-js-...


I've found myself preferring type guards for checking action type, since it avoids you having to have keep one big `Actions` union around that takes X amount of lines and really doesn't serve any other purpose.


Is Redux a TS native library? Angular has typing issues of its own, e.g. Forms are typed with 'any', no easy access to certain HTMLElements' properties, etc., but the rest is okay. (Meaning hard to do bad things without early warning/failure during compilation.)


This was a deliberate design choice by Anders. It is quite difficult to make a sound type system for JS that has nice interoperability and ultimately adoption was more important.


I would not use typescript without the "strict" compiler option. It enforces lots of things that should have been default and covers a lot of the issues you mentioned.


On a brand-new project, I agree wholeheartedly. To do otherwise is to miss half the benefit, in my opinion.

However, those of us that are in love with TypeScript often want to refactor existing JS into TS, where allowing implicit `any` and allowing `null` make it a bit easier to convert larger codebases.


It's too bad the author chose specious and self-invalidating arguments for using Purescript and Elm as opposed to pointing out real issues with Flow and TS, isn't it?


That's usually a sign that you need to define your own types for the objects in question. I had to do this for immutable.js records, because the default types on its methods are basically lies.


Typescript brings much needed order to the complete (seemingly) typeless nature of Javascript. That is the source of enthusiasm. TS is the direct result of a development team working out the kinks of having to maintain a large JS codebase.


With regard to the third, this is a problem specifica with libraries written in JavaScript. Hopefully it'll become less common as more libraries are written in TypeScript and the types are automatically generated from the code.


In what cases have you seen the compiler refuse to accept something that’s correct?


Passing props through a higher-order component using JSX's spread operator instead of the attribute syntax, e.g. `{...{...props, foo, }}` and accessing that prop in the wrapped component. I found it was possible with the attribute syntax. More examples can be found in the issues for `@types/ramda`. In some cases, the types are incorrect. In other cases, TypeScript's type inference is too weak.


From your description, that sounds like the compiler accepting something that isn't correct. I'm more interested in the case(s?) mentioned where the types are correct and the compiler won't accept the code.

I will have a quick skim through the @types/ramda issues, thanks.


The compiler was telling me that there was no field foo on props but there clearly was.


Lack of inference is usually not considered a soundness issue.


Same here.

I have been using Flow (mostly) and TS (occasionally, including counter-checking problems in Flow to see what TS does in a similar case). Overall I would not go back to the time without a type checker, however, I too am mystified by the enthusiasm. Anyone who wants to check the state of static type checking for Javascript should take some time and read through

- https://github.com/Microsoft/TypeScript/issues

- https://github.com/facebook/flow/issues

I do that quite a lot myself and have also contributed a tiny bit to both projects (no core code, things like definitions, small doc improvements, quite a bit of answering to issues and often checking the posted code for myself, often in both Flow and TS).

Ignore the issues posted by people who really would just need a forum to ask questions, there are plenty of real issues left. Worst part: Many of them won't be solved (too hard, too much work, too many issues overall). You have to change your coding style and write in a way that the type checker can actually help you with. Also, it's easily possible to end up with types that are far more complicated than the code they are supposed to describe.

I spent more time working on the types than on the actual code. What makes it worth it is that one, I get some control over the code other people write using my library, if I insist they too use the type system I can prevent them from misusing the API to a degree. Two, those other people also includes myself in future incarnations. Three, refactoring can be significantly easier, if you have good types the checker will tell you all the places you missed changing.

So overall, at least for my situation, mostly for writing a library with few external dependencies (so I don't need the more or less unreliable external type definitions) that the business heavily relies on in many products, adding the very considerable additional effort is worth it. Still, I very much disagree with all the enthusiasm.

The type checkers are software trying to understand software, and that software that it's attempting to check is not just an already complex dynamic language, but in addition on top of it is people's code that comes in a million styles. Those type checkers can be valuable, but they are far (very far) from perfect, and they come at a considerable price (mostly in the time it takes to create and maintain the types, I think the additional step to remove type annotations for production code is pretty insignificant in comparison).


> I spent more time working on the types than on the actual code.

That's interesting. I very strongly don't, when writing TypeScript (but I don't then also mess around with a second type system on top of it). Better Intellisense and fail-up-front checking means I write code much, much faster.

I didn't have to change my coding style because this is already how I wrote code; I now have the tools to actually do it well.

TypeScript is not a "complex dynamic" language unless you step outside its bounds. Which, sure, sometimes you need to; it's not perfect, and one of its advantages is being able to opt out when absolutely necessary. Then you fence off that type-unsafe code by strongly testing before you hand something back into TypeScript because you're a competent programmer who understands the limitations of your tools. But that happens so very, very rarely that optimizing for that corner case seems foolish.


How much time you spend depends on your data structures. If the easy default options are good enough you don't need to sweat. We make heavy use of disjoint unions, and while the simple case here too is easy, some things that we do - although they still are very simple on the Javascript side - are hard on the type checker (Flow in this case).

> TypeScript is not a "complex dynamic" language

I don't know what you read into my comment, but if you just stick to what I wrote, Javascript certainly is, and TypeScript is just Javascript (the type annotations are a separate thing). I'm not sure what your point is overall I have to admit, it's a bit on the defensive side for no reason that I can see. For whatever reason you seem to feel personally attacked ("I didn't have to change my coding style")? I refer back to what I wrote, point for point. Would it please be allowed to write down my observations? Especially when it is base don years of practice in all the relevant technologies (JS, TS, Flow) and I'm not just making stuff up without having data (i.e. actual experience). Plenty of other people wrote similar comments here.


I don't feel defensive, but I do find your comments interesting and your experience almost diametrically opposed to mine--which is what I said.

JavaScript is quite complex and quite dynamic, but the reason TypeScript got me back into doing web stuff was, by and large, because it removes that except for in clearly delineated places (at least, once you turn on strict mode).

You're reading in some stuff that isn't intended.


> You're reading in some stuff that isn't intended.

What a coincidence, same thing I said to you! Why do you make a stupid reply when you know it's stupid?


We've banned this account for violating the site guidelines and ignoring our request to stop.

https://news.ycombinator.com/newsguidelines.html




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

Search: