1. 30
  1.  

  2. 19

    Soundness is definitely a thing that can come up, though in handling tens of thousands of lines I don’t feel like I’ve hit it directly.

    On the other hand, Typescript’s type system is still the most useful typesystem I have ever used. The way unions, “everything is a dictionary”, being able to filter out keys and the like, and everything else work together mean that I can write business logic that handles really dynamic data shapes, and still get guarantees that are basically impossible in other languages without generating loads of wrapper types.

    For example just being able to inline declare a variable of having “type A and type B” is such a great way of allowing for new APIs.

    Ultimately Typescript not seeing typing as a mechanism for implementing the language execution semantics, but really as a checker of code validity, opened it up to really practical advantages.

    1. -2

      On the other hand, Typescript’s type system is still the most useful typesystem I have ever used.

      I have a canary of sorts which I use to guess whether I will appreciate a type system.

      1 == "1"
      

      The correct way to handle this is to issue a type error at compile time (or at least runtime if the language doesn’t have a significant check phase in the compiler). The two ways to fail this are:

      • typecheck, return false (python2, python3, ruby, crystal do this)
      • typecheck, return true (typescript, javascript, php do this)

      I haven’t made final decisions which of these is scarier.

      Interestingly, some of the above languages do fine if we use inequality instead:

      1 >= "1"
      

      Some languages from the top of my head that do this the way I like it: Haskell (and I assume many of the other strongly typed functional languages as well), Common Lisp (at least sbcl, dunno if it’s standard), Rust, Zig, Nim.

      1. 9

        To be fair you should always use === in TypeScript, then it works as expected.

        1. 6

          To be fair you should always use === in TypeScript

          To be even fairer, this is not even a Typescript thing, it’s a Javascript thing? A linter on a JS project will complain about the use of ==, without going near Typescript.

          1. 3

            Throwing my 2-cents in here, PHP is the same. Using === will enforce type checking on the comparison.

            1. 3

              I don’t think it “type checks” per se in PHP, but rather avoids doing an implicit conversion.

              1 == "1" evaluates to true, while 1 === "1" evaluates to false. It won’t error out, like a type checker will typically do for you.

            2. 1

              To be fairest, Typescript actually does this the way I want. See https://lobste.rs/s/qfpbk9/is_typescript_worth_it#c_hs0olb

            3. 2

              Does it error, or does it return false like python, ruby, crystal (and js with ===)?

              1. 2

                It does error at compile time.

                1. 1

                  Yeah, seems like both == and === work as I want in Typescript. See https://lobste.rs/s/qfpbk9/is_typescript_worth_it#c_hs0olb

                  I’m liking Typescript right now.

              2. 1

                Ah, forgot about that. Do ts linters warn about ==?

                1. 2

                  Yeah they do.

              3. 2

                I don’t understand why a typecheck of the integer numeral 1 and a string happening to contain the numeral one returning false is scary.

                I know nearly zero about type theory. Could you please explain?

                1. 6

                  Because it’s a category mistake – it’s a nonsensical question.

                  1. 5

                    Ah I think I see, so that’s what demands that the response be an exception rather than false.

                    It’s not just “Is a number equal to a string?” “No.” it’s “That’s not even a sensical question to ask.”

                    I guess I feel like that’s a bit counter intuitive, since I generally want boolean operators to provide answers in boolean form, but then I don’t know anything about category theory either.

                    sigh clearly I have a lot to learn :)

                    1. 9

                      Well, it depends. Mathematically there’s nothing “wrong” with it, but it raises some uncomfortable questions. Like “what’s the domain of the == function?” Turns out, equality is not a mathematical function! If it was, everything would be in its domain, aka the forbidden Set Of All Sets. There’s a couple ways around this: the type way is to say that == is actually a collection of functions, one for each type. This ends up working out, and makes comparing different types impossible.

                      But there are other formulations! Like there’s nothing wrong with saying int and str always compare false, you just have to be clear what your mathematical axioms are.

                      1. 4

                        I think it might help to look at the Haskell signature for ==:

                        (==) :: a -> a -> Bool

                        It makes it clear that you can only compare two values of the same type.

                        1. 4

                          In Lisps, you typically have = and equal?/equalp. With = you’re asking “are these two things numerically equal”, which is a nonsensical thing to ask about a string and an integer (or a vector and a list, or a character and a boolean, or two other non-number objects). With equal? you’re asking “are these two objects structurally the same thing?”, which makes sense and “false” would be the obvious answer for objects of different types.

                          So presumably in many languages, == is the equal? in the above description, but there’s no separate = predicate.

                          And then there’s eq?/eqp, which asks “are these things the same object?”, which is a question of identity, not equality. That’s similar to what the is operator does in Python, for example. But yeah, this stuff is confusing if you’ve only been exposed to languages that conflate these different ways of comparing because you’re not used to having to make the choice of which kind of comparison to use in different situations.

                          1. 1

                            so that’s what demands that the response be an exception rather than false

                            I think no one wants it to throw an exception, but to simply not compile.

                      2. 2

                        1 == "1" is a classic JS gotcha (double equals is not “check for equality” but “check for equality and if it fails check for toString equality”) and 1 === "1" does what you expect.

                        I have decently used “fancy type” languages like Haskell, Purescript and Rust. Their type systems still make me somewhat unhappy compared to TS for “enterprise apps” (the one thing that I kinda wish that TS had but would bef impossible given soundness and how stuff works is the return type polymorphism).

                        1. 2

                          1 == "1" is a classic JS gotcha

                          I call it a classic maldesign, but ok :)

                          1. 3

                            I mean it’s not good, and there’s a reason you don’t see == anywhere in most codebases.

                            I guess it’s just not an issue that one hits in practice if you’re an experienced practitioner (compare to…. array bounds errors that even experienced C programmers will hit often). It’s a thing you set your linter to notice, and then never do it.

                        2. 1

                          Couldn’t edit this any longer, so will make an errata to my comment and mark it incorrect:

                          Contrary to what I stated, Typescript does this exactly as I think is best, i.e. signal an error at compile time.

                          vegai@Vesas-iMac ~/tmp> cat derp.ts
                          
                          let derp1 = 1 == "1";
                          let derp2 = 1 === "1";
                          
                          
                          vegai@Vesas-iMac ~/tmp> tsc derp.ts
                          derp.ts:2:13 - error TS2367: This condition will always return 'false' since the types 'number' and 'string' have no overlap.
                          
                          2 let derp1 = 1 == "1";
                                        ~~~~~~~~
                          
                          derp.ts:3:13 - error TS2367: This condition will always return 'false' since the types 'number' and 'string' have no overlap.
                          
                          3 let derp2 = 1 === "1";
                                        ~~~~~~~~~
                          
                          
                          Found 2 errors.
                          

                          Don’t even need the strict mode. So apologies to everyone for that bit of bullshitting – I went to check the behaviour in the first typescript playground I found, which for some reason did not behave like this.

                      3. 11

                        What I found to be of highest importance for maintaining large applications is the ability to reason about parts of the application in isolation, and types don’t provide much help in that regard. When you have shared mutable state, it becomes impossible to track it in your head as application size grows. Knowing the types of the data does not reduce the complexity of understanding how different parts of the application affect its overall state.

                        My experience is that immutability plays a far bigger role than types in addressing this problem. Immutability as the default makes it natural to structure applications using independent components. This indirectly helps with the problem of tracking types in large applications as well. Not having to track types across the entire application to reason about a particular piece of code allows for local reasoning within the scope of each component. Meanwhile, you make bigger components by composing smaller ones together, and you only need to know the types at the level of composition which is the public API for the components.

                        1. 1

                          Sticking “readonly” and “Readonly<>” in front of / around types in TS is nice for enforcement. ;)

                          Using immutable data in most places makes you much less likely to run into some of the unsound corners in TS’s type system.

                          There are some synergies between “annotate with types” and “write pure functions”.

                          1. 0

                            Sticking Readonly<> in random places only gets you so far, and doesn’t really give you many guarantees in practice. Also worth keeping in mind that naive copying gets pretty expensive. Meanwhile, ClojureScript is built around immutability from ground up, and it’s backed by persistent data structures that use revisioning for changes instead of naive copying.

                            Personally, I also find runtime contracts like spec to be more pragmatic than a full on type system. I tend to add spec definitions around component APIs after I finalize how a particular component should behave. I also find that spec is better at expressing semantic constraints than a type system.

                        2. 4

                          Yes, if you want soundness, you described some of the strict compiler options - https://www.typescriptlang.org/docs/handbook/compiler-options.html ctrl+f “strict”, which go more indepth

                          Typescript needs config if you want that but it being a non-goal imo is a good thing since you can turn any js project into a ts project with almost no config/setup. There are many things that aren’t touched on here that really make TS shine - union+intersection types, conditional types, mapped types, the fact almost all values (minus instances of classes) are valid types. The typesystem is one of the most powerful among modern languages.

                          The main downsides to me are 1 - doing run time polymorphism of type aliases (note I never actually run into this in real use cases) 2 - inference isn’t always spot on yet, but they are making a lot of progress here and there’s a feature that will dramatically improve this https://github.com/Microsoft/TypeScript/pull/26349

                          The nice thing imo of typescript is that it has such a powerful typesystem, yet it makes it somehow super intuitive coming from JS

                          1. 4

                            The title is weird. The post itself says several times that Typescript is worth it. Just that it’s not good enough compared to what we may want.

                            1. 3

                              Runtime type checking is not necessary in the example provided by the author. A better approach is to generate typescript definitions from the API contract you have with a backend, either from GraphQL or from a Swagger definition. This way there is no overhead in the runtime but you still get the benefit of having static types for server domain models. Allowing one to easily perform refactors for frontend code once the models change.

                              1. [Comment removed by moderator alynpost: This site is not for low-effort, non-substantive cheap shots.]

                                1. 3

                                  Without an explanation that is not very helpful.