Comment by epolanski

Comment by epolanski 6 hours ago

43 replies

> TypeScript is a wonderfully advanced language though it has an unfortunately steep learning curve

An extremely steep one.

The average multi-year TypeScript developer I meet can barely write a basic utility type, let alone has any general (non TypeScript related) notion of cardinality or sub typing. Hell, ask someone to write a signature for array flat, you'd be surprised how many would fail.

Too many really stop at the very basics.

And even though I consider myself okay at TypeScript, the gap with the more skilled of my colleagues is still impressively huge.

I think there's a dual problem, on one side type-level programming isn't taken seriously by the average dev, and is generally not nurtured.

On the other hand, the amount of ideas, theory, and even worse implementation details of the TypeScript compiler are far from negligible.

Oh, and it really doesn't help that TypeScript is insanely verbose, this can easily balloon when your signatures have multiple type dependencies (think composing functions that can have different outputs and different failures).

pcthrowaway 4 hours ago

> Hell, ask someone to write a signature for array flat, you'd be surprised how many would fail.

To be clear, an array flat type:

    type FlatArr<Arg extends unknown[]> = Arg extends [infer First, ...(infer Rest)] ?
      First extends unknown[] ?
        [...First, ...FlatArr<Rest>] :
        [First, ...FlatArr<Rest>] :
      [];
is far from basic Typescript. The average Typescript dev likely doesn't need to understand recursive conditional types. It's a level of typescript one typically only needs for library development.

Not only have I never been expected to write something like this for actual work, I'm not sure it's been useful when I have, since most of my colleagues consider something like this nerd sniping and avoid touching/using such utilities, even with documentation.

  • wk_end 4 hours ago

    If I saw that in a PR I would push very hard to reject; something like that is a maintenance burden that probably isn’t worth the cost, and I’ve been the most hardcore about types and TypeScript of anyone of any team I’ve been on in the past decade or so.

    Now, that said, I probably would want to be friends with that dev. Unless they had an AI generate it, in which case the sin is doubled.

    • probabletrain 3 hours ago

      I think there’s a difference between what’s expected/acceptable for library code vs application code. Types like this might be hard to understand, but they create very pleasant APIs for library consumers. I’ve generally found it very rare that I’ve felt the need to reach for more complex types like this in application code, however.

      RXJS’s pipe function has a pretty complex type for its signature, but as a user of the library it ‘just works’ in exactly the type-safe way I’d expect, without me having to understand the complexity of the type.

    • ibejoeb 3 hours ago

      If it's correct, it's not a maintenance nightmare, and it will alert you to problems later when someone wants to use it incorrectly.

      If you're writing first-party software, it probably doesn't matter. But if you have consumers, it's important. The compiler will tell you what's wrong all downstream from there unless someone explicitly works around it. That's the one you want to reject.

    • [removed] 4 hours ago
      [deleted]
    • 8note 4 hours ago

      looking back at them is also real hard to debug. you dont get a particularly nice error message, and a comment or a test would tell better than the type what the thing should be looking like

    • spankalee 3 hours ago

      What's the alternative? Have incorrect types for the function? That's not better.

      • wk_end 2 hours ago

        To answer this we probably need more details, otherwise it's gonna be an XY Problem. What is it that I'm trying to do? How would I type this function in, say, SML, which isn't going to allow incorrect types but also doesn't allow these kinds of type gymnastics?

      • epolanski 2 hours ago

        The alternative is what shows in the comment: go on HN and tell the world you think TS and JS are crap and it's not worth your time, while writing poor software.

  • epolanski 2 hours ago

    The version I was thinking when I wrote the comment is simpler

        type Flatten<T> = T extends Array<infer U> ? Flatten<U> : T
    
    > The average Typescript dev likely doesn't need to understand recursive conditional types.

    The average X dev in Y language doesn't need to understand Z is a poor argument in the context of writing better software.

    • furyofantares an hour ago

      > The average X dev in Y language doesn't need to understand Z is a poor argument in the context of writing better software.

      It's a good response to the claim that we'd be surprised at how many would fail to do this, though.

    • NooneAtAll3 2 hours ago

      as a person that never touched JS and TS... what's the difference between the two answers?

      • granzymes 2 hours ago

        For one, the simple answer is incomplete. It gives the fully unwrapped type of the array but you still need something like

          type FlatArray<T extends unknown[]> = Flatten<T[number]>[]
        
        The main difference is that the first, rest logic in the complex version lets you maintain information TypeScript has about the length/positional types of the array. After flattening a 3-tuple of a number, boolean, and string array TypeScript can remember that the first index is a number, the second index is a boolean, and the remaining indices are strings. The second version of the type will give each index the type number | boolean | string.
      • ameliaquining 2 hours ago

        First one flattens a potentially-nested tuple type. E.g., FlatArr<[number, [boolean, string]]> is [number, boolean, string].

        Second one gets the element type of a potentially-nested array type. E.g., Flatten<number[][]> is number.

        For what it's worth, I've never needed to use either of these, though I've occasionally had other uses for slightly fancy TypeScript type magic.

  • miki123211 3 hours ago

    I recently had to write a Promise.all, but using an object instead of an array.

    That was... non-trivial.

    • hdjrudni 3 hours ago

      If it's what I'm thinking, that one isn't too bad. I wrote it awhile back:

          export async function promiseAll<T extends Record<string, Promise<any>>>(promises: T): Promise<{ [K in keyof T]: Awaited<T[K]> }> {
              const keys = Object.keys(promises) as Array<keyof T>;
              const result = await Promise.all(keys.map(key => promises[key]));
              return Object.fromEntries(result.map((value, i) => [keys[i], value])) as { [K in keyof T]: Awaited<T[K]> };
  • kaoD 4 hours ago

    For those unfamiliar with TS, the above is just...

        function flat([head, ...tail]) {
          return Array.isArray(head)
            ? [...flat(head), ...flat(tail)]
            : [head, ...flat(tail)]
        }
    
    ...in TS syntax.
    • tomsmeding 4 hours ago

      Well, it is the type of that, in TS syntax. Few are the statically-typed languages that can even express that type.

afavour 5 hours ago

> Too many really stop at the very basics.

I don’t think that means it has a steep learning curve. It just means the basics suffice for a ton of TypeScript deployments. Which I personally don’t see as the end of the world.

  • vosper 5 hours ago

    Yes, to me this is a biggest feature of Typescript: A little goes a long way, while the advanced features make really cool things possible. I tend to think of there being two kinds of Typescript - Application Typescript (aka The Basics, `type`, `interface`, `Record`, unions etc...) and Library Typescript which is the stuff that eg Zod or Prisma does to give the Application Typescript users awesome features.

    While I aspire to Library TS levels of skill, I am really only a bit past App TS myself.

    On that note I've been meaning to the the Type-Level Typescript course [0]. Has anyone taken it?

    https://type-level-typescript.com/

mook 39 minutes ago

It's also terribly documented. As an example, I don't think `satisfies` is in the docs outside of release notes. There's lots more stuff like that, which makes using it kind of frustrating.

madeofpalk 4 hours ago

> Too many really stop at the very basics.

As someone who knows slightly more than the basics, and enough to know about the advanced stuff that I don't know about, this is the correct place to stop.

I would much rather restructure my javascript than do typescript gymnastics to fit it into the type system.

  • IceDane 4 hours ago

    [flagged]

    • ervine 3 hours ago

      You can restructure your JS to avoid some crazy verbose TS though, sometimes. I think that's the point they were making. Why be so hostile?

undeveloper 5 hours ago

these are things most developers don't know how to do in most language's type systems. I think only rust with its focus on functional roots has seen similar focus on utilizing its type system to its fullest extent.

stefan_ 3 hours ago

This is the bell curve meme, and you are in the middle telling us "template metaprogramming in C++ is amazing".

dzonga 3 hours ago

typescript is largely a result of solving a non-existent problem. Yeah JS is finicky & has foot-guns, however they're ways around those foot guns that don't involve typescript.

Rich Hickey in 10 Years of Clojure & Maybe Not then the Value of Values - lays this out - though not meant at typescript but static types in general.

the thing most people don't have proper Javascript fundamentals.

Function signatures: JSDoc works

Most types - use Maps | Arrays

if a value doesn't exist in a map we can ignore it. There's also the safe navigation operator.

Instead of mutable objects - there's ways around this too. Negating types again.

lloydatkinson 5 hours ago

TypeScript codebases I've seen generally seem to have the widest demonstration of skill gap versus other languages I use.

For example, I don't ever see anyone using `dynamic` or `object` in C#, but I will often see less skilled developers using `any` and `// @ts-ignore` in TypeScript at every possible opportunity even if it's making their development experience categorically worse.

For these developers, the `type` keyword is totally unknown. They don't know how to make a type, or what `Omit` is, or how to extend a type. Hell, they usually don't even know what a union is. Or generics.

I sometimes think that in trying to just be a superset of JavaScript, and it being constantly advertised as so, TypeScript does not/did not get taken seriously enough as a standalone language because it's far too simple to just slot sloppy JavaScript into TypeScript. TypeScript seems a lot better now of having a more sane tsconfig.json, but it still isn't strict enough by default.

This is a strong contrast with other languages that compile to JavaScript, like https://rescript-lang.org/ which has an example of pattern matching right there on the home page.

Which brings me onto another aspect I don't really like about TypeScript; it's constantly own-goaling itself because of it's "we don't add anything except syntax and types" philosophy. I don't think TypeScript will ever get pattern matching as a result, which is absurd, because it has unions.

  • ervine 4 hours ago

    On the other hand, would we even be talking about it if it hadn't stuck to its goals?

  • fourthark 3 hours ago

    It will get pattern matching when JS does. Not certain yet but in progress.

    https://github.com/tc39/proposal-pattern-matching

    • ibejoeb 3 hours ago

      That proposal is really dragging though. And typescript needs as much work because that's where the real power is. We need discern thing like

          match (x) {
            "bob": ...,
            string: ...,
            () => Promise<void>: ...,
            () => Promise<string>: ...,
          }
      
      with exhaustiveness checking for it to be truly useful.
      • ameliaquining an hour ago

        Discriminating a function or promise based on return type is never going to work, because JavaScript is dynamically typed and TypeScript erases types at compile time, so there's no way to know at runtime what type a function or promise is going to return.

monkpit an hour ago

> ask someone to write a signature for array flat

Out of curiosity - what do you think is a satisfactory answer here?

My answer would vary wildly based upon more details, but at the most basic all I can think you could guarantee is Array<unknown> => Array<unknown>?

imiric 3 hours ago

You're right, but that begs the question: does a type system really require such complexity?

I'm aware that type theory is a field in and of itself, with a lot of history and breadth, but do developers really need deep levels of type flexibility for a language to be useful and for the compiler to be helpful?

I think TypeScript encourages "overtyping" to the detriment of legibility and comprehension, even though it is technically gradually typed. Because it is so advanced and Turing complete itself, a lot of brain cycles and discussion is spent on implementing and understanding type definitions. And you're definitely right that it being verbose also doesn't help.

So it's always a bittersweet experience using it. On one hand it's great that we have mostly moved on from dynamically typed JavaScript, but on the other, I wish we had settled on a saner preprocessor / compiler / type system.

samdoesnothing 5 hours ago

I have mixed feelings about Typescript, I hate reading code with heavy TS annotations because JS formatters are designed to keep line widths short, so you end up with a confusing mess of line breaks. Pure JS is also just more readable.

Also you can so easily go overboard with TS and design all sorts of crazy types and abstractions based on those types that become a net negative in your codebase.

However it does feel really damn nice to have it catch errors and give you great autocomplete and refactoring tooling.

ForHackernews 5 hours ago

Honestly I just use TypeScript to prevent `1 + [] == "1"` and check that functions are called with arguments. I don't care about type theory at all and the whole thing strikes me as programmers larping (poorly) as mathematicians.

  • epolanski 5 hours ago

    I couldn't care less about mathematics, but I do care about making impossible state impossible and types documenting the domain.

    If you type some state as:

        isLoading: boolean
        result: Foo
        hasError: boolean
        errorMessage: string | null
    
    then you're creating a giant mess of a soup where the state of your program could have a result, be loading and an error at the same time. If you could recognise that the state of your program is a sum of possible states (loading | success | error), and not their product as the type above you could highly simplify your code, add more invariants and reduce the number of bugs.

    And that is a very simple and basic example, you can go *much* further, as in encoding that some type isn't merely a number through branded types, but a special type of number, be it a positive number between 2 and 200 or, being $ or celsius and avoiding again and entire class of bugs by treating everybody just as an integer or float.