Hacker News new | ask | show | jobs
by Waterluvian 1200 days ago
Typescript is one of those things that makes me so productive that I have absolutely nothing to argue or prove. Their loss.
1 comments

Not a rebuttal, just curious, how do you find your productivity compares to a "real" statically typed language (e.g. Go, Java, etc.)? Does your increase in productivity compared to JavaScript simply come from static typing, or is TypeScript itself the source?
Both. Static typing makes JavaScript well more than tolerable. Dare I say enjoyable! But I also love TS' duck typing and I love how it's totally optional.

The optionality is a special trait that most "real" static languages don't get to enjoy (for better or worse). By being optional, I can simply ignore types when I'm hacking/prototyping something and type safety/soundness is the last thing I care about. Rust, for example, can feel really tedious when it makes me "show my work" and I'm just trying to scribble something up quickly. If that makes sense? I just make `any` a linting error so I can use it during my experimentation/prototyping but I cannot ship it.

Typescript has quite a few pitfalls like the uselessness of Readonly<T>, which can just vanish with an assignment to a variable typed T where TS will neither warn nor error out nor require an explicit cast.
I agree. I feel like sketching in JS and solidifying in TS is super productive. Being able to go from a plain old JS object to a type is really satisfying. I feel like full stack TS might be a great choice for new web projects. And if you find a bottleneck, you could rewrite in Rust.
> And if you find a bottleneck, you could rewrite in Rust.

Sounds pretty extreme..? What kind of bottlenecks would you be talking about that would prompt me to re-write my entire application in another language?

I'm pretty sure they mean rewrite the bottleneck.
no just rewrite the bottleneck/hot-path
Not GP, and not a direct answer to the question… in these discussions a lot of the “benefit of static types” gets boiled down to:

- documentation

- aids tooling (eg improved static references in editor, static analysis by linter, etc)

- catches a whole class of problems before runtime (eg a typecheck-time substitute for certain runtime checks and their corresponding tests)

I often like to add:

- promotes better design

This puts TypeScript’s type system in a unique position, where it’s simultaneously expressive and complex in ways many others are not (because it’s designed to express extraordinarily dynamic idioms used in JS) and equally mundane and simple if you use it from the start (because all that complexity is a good forcing function for deciding whether you all of that dynamism it can express is of value for the thing you’re actually building).

In a more general sense, it strongly encourages developers to think about their interfaces explicitly where they might otherwise “rapidly prototype” a monstrous one.

The vast majority of the time, TypeScript will encourage simpler APIs that don’t need most of TypeScript’s “advanced” type system features. And in terms of productivity, that’s a benefit you only get if you’re starting from the proverbial Dynamic Wild West. So it might not be absolutely more productive than other statically typed languages, but still relatively an enormous productivity boost in its context.

Not OP but a well-typed codebase means you have to remember less, your broken code gets caught before you even run/compile it, and you can just lean on the type system to write the code for you.

And TypeScript has a much more expressive type system compared to Go, Java, and frankly most languages. Java in particular still has pretty bad type reification (where ArrayList<Integer> and ArrayList<Float> are essentially the same and cannot be used to overload methods, for example).

I use a lot of the utility TypeScript functions (i.e. Partial<>, Record<>) and it catches all sorts of mistakes that might be made by myself in the future or someone not familiar with the codebase. For example, if there has to be a A->B mapping somewhere, I would type B so it would raise a type error if something was added in A but not B. Most type systems are not flexible enough to do anything remotely like that and instead you have to write a bunch of tests where you end up 20 lines of very basic test code for 1 actual line of code.

I have a bit of experience with TypeScript and I think that its types are pretty good at catching mistakes. You need to run linter (like eslint) and some strict compiler options to take a full advantage of it, but that's not a big deal. And TypeScript type system is miles ahead of Java. I'm not sure if those types provide any productivity increase compared to Java, that's questionable, but at least it's not JavaScript and that's good enough for me. Now JavaScript is truly atrocious hit to productivity. I just can't stand it.

My only issue with TypeScript is its strange design with regard to interface and types. Those are pretty fundamental concepts and they're absolutely similar. I don't understand this design and I think that only one thing should have left, but may be I'm wrong about it. Also I think that TypeScript documentation would benefit from more examples with hard concepts, I didn't fully understand its advanced generics concepts. But that's not a big thing and probably more on me.

> My only issue with TypeScript is its strange design with regard to interface and types. Those are pretty fundamental concepts and they're absolutely similar. I don't understand this design and I think that only one thing should have left, but may be I'm wrong about it.

I feel the same way. IIRC, earlier versions of Typescript were much more clearly influenced by .NET and other Microsoft idioms, and there were a few features that didn't seem to make much sense coming from the Javascript side of things. I also had a buddy of mine that's a Java developer be very confused about how they were supposed to work.

> Also I think that TypeScript documentation would benefit from more examples with hard concepts, I didn't fully understand its advanced generics concepts. But that's not a big thing and probably more on me.

Absolutely agree on this, although things are getting better. For awhile it seemed like the only documentation for more advanced features was the main website's blog posts and release notes.

One major downside of TypeScript is that types are not available at runtime and that computations on types can't be done by writing regular TypeScript (both possible with code generators of course).
Types are absolutely available at runtime, you just have to start from the runtime. Type guards are the solution everyone wants +- a tiny bit of ceremony, and adding type system information to the runtime would just hurt performance for no good reason.

This:

  type Foo = {
    bar: string
    // …
  }
Can just as easily be addressed as:

  const parseFoo = parse.object({
    bar: parse.string,
    // …
  })

  type Foo = Foo<typeof parseFoo>
(Where parse methods here are type guards for their respective parsed return types, and can similarly be used to serialize runtime values as needed.)

For that little bit of extra ceremony, you get to isolate runtime stuff to the boundaries you need to care about, and let the type checker deal with everything else (typically most everything internal).

Have you ever used a language where types can influence runtime behavior? It's an extremely powerful tool to do things like choosing a specialized sort algorithm based on the type of contents of array you are sorting. This has zero overhead iff types are known at compile time and can yet hit different code paths at runtime. To just dismiss this as not needed is extremely short sighted in my opinion.
Having types at runtime is a big deal and I lament that typescript can’t do it (for understandable reasons.)
I have. And I think the limitation is good for a language that doesn’t have that. I don’t want TypeScript to lie to me about its capabilities, I want it to help me make good decisions with the runtime code I actually have.
You can always use zod or io-TS.
> Types are absolutely available at runtime

TypeScript types are not available at runtime. As the name implies, runtime type information requires runtime support. Since TypeScript compiles to JavaScript, the only type information available is that provided by JavaScript.

Types are absolutely available at runtime. You just have to start with the runtime.

  const parseStr = (value: unknown): assert value is string => {
    if (typeof value === 'string') return value

    throw new Error(…)
  }

  const str = parseStr(anythingYouCanThrowAtIt)
I can 100% guarantee you str is going to have the same static and runtime type once you’ve checked it, or parsed it from whatever type you’d accept as a string. TypeScript won’t do the parsing for you, because JavaScript doesn’t have clear semantics for runtime casting that anyone wants or would accept. But type guards are exactly the solution to that and incredibly composable. Once you accept that reality and embrace it, getting the static type out of your runtime parser is a single added line of static type code. And all of this is almost exactly equivalent to what languages with runtime casts do, but you have complete visibility into it because you determine how casts behave. There are whole libraries which do this for you so don’t worry about rolling your own unless you have very particular needs. But TypeScript definitely has the facility to align static and runtime types however you see fit. You just need to tell the type system what types the runtime conveys, same as every other aspect of the type system.
You seem knowledgeable about TypeScript, so you certainly very well understand what people mean by "types are not available at runtime". Once your typescript is compiled to javascript, there's nothing left of your user-defined types and interfaces. Type guards are a security that is very much a workaround for the lack of types at runtime.
You can use discriminated unions with TS (https://antman-does-software.com/typescripts-discriminated-u...), and you can even parse them from unknown data using tools like zod (https://zod.dev/?id=discriminated-unions), io-ts, or the likes.

I can't think of anything I can do in .NET (which has runtime types) that I can't do in TS with the help of a library or two.

> I can't do in TS with the help of a library or two.

Libraries like io-ts and zod embed the type information into JavaScript, so the information is available at runtime but only for wrapped types. If TypeScript provided runtime type information, such libraries wouldn't be necessary.

Can only speak for java. But I'm way more productive with typescript. Typescript has 2 things going for it:

- type system is way more flexible. You can create, merge, pick keys and manipulate types in all kinds of ways. You have better type inference, type conditionals and lots more

-there's an escape hatch. Sometimes you just want to quickly try something, or you're under time pressure and need to either break the types or do a bigger rewrite, or you just don't care about a certain variable's type. Then there's the any/unknown/plain js/@ts-expect-error escape hatches to do just that.

It's a bit of both for me. I'd say 90% of it is from simply having types; 10% is from some of the cool things you can do with TS types that aren't common in most statically typed languages.
> cool things you can do with TS types that aren't common in most statically typed languages

like what?

For me (coming from C++) being able to easily union and intersect types, and being able to specify certain values (not sure the right name for this). For example:

    type ServerResponse = {success:true, response:JSON} | {success:false, error:string};
In C++ I would probably have to type success:boolean, and make response and error optionals, but the TS type is more expressive.

if success==true, then response must exist. if success==false, then error must exist.

    const sr: ServerResponse = {success:true, error:"moo"); // won't compile, doesn't match
TS understands this type really well, and type narrowing makes it easy to work with.

    // NOT LEGAL; error might not exist
    console.log(sr.error);

    // IS LEGAL; we test for success first, which narrows the type. 
    if(sr.success){
        console.log(sr.response);
    } else {
        console.log(sr.error);
    }
Too late to EDIT: but "literal types" was the word I am looking for.

I used "success:true" and "success:false" as part of the two types I was combining; it seems like "success:boolean" would server the same function, but it does not. The type I created has more information that that.

https://www.typescriptlang.org/docs/handbook/literal-types.h...

I'm a big fan of TS, but discriminated unions are available in a lot of languages and when not, there's usually some library adding it like boost::variant if the language has runtime types or templating/macros.
C++ has std::variant these days, no need for Boost… however, neither version has particularly good ergonomics.
If I understand std::variant, it THROWS if you try to access the wrong type?

I should have been specific, but all of the errors I mention above are compile-time errors, not run-time.

> boost

No, thanks.

Some folks have built whole SQL databases and DSL compilers in the TS type system. These tend to be toy projects with disclaimers not to use them. But the type system being Turing complete[0] (for better or worse), pretty much whatever you can imagine. This project[1] is one I actually return to frequently for practical ideas.

0: https://github.com/microsoft/TypeScript/issues/14833

1: https://github.com/sindresorhus/type-fest

- easy referencing the type of another struct's field. As in `function parseInput(input: SomeType['someField']) { }`

- it's ability to infer types in general

- parsing. given a route string like `/users/:userId` TypeScript can force you to always pass `{ userId: 1234 }` when actually building the route without having to declare the interface anywhere

- conditional types

I think the biggest benefit I've found is TypeScript's inference is extremely smart and in fact it's considered a best practice to basically write as little TypeScript as possible and rely as much as possible on TS's inference

In general, if a "best practice" of a language is to write less code that tends to translate to great productivity

> Does your increase in productivity compared to JavaScript simply come from static typing, or is TypeScript itself the source?

As far as I can tell, TypeScript doesn't add any new features or behavior to JS other than static typing.

If you ask me, of course TS isn't as good as the real deal, but it's darn close and way better than vanilla JS or compilers for other languages that target JS.