Hacker News new | past | comments | ask | show | jobs | submit login
All I want for Christmas is these seven TypeScript improvements (effectivetypescript.com)
178 points by soheilpro on Dec 25, 2022 | hide | past | favorite | 165 comments



I just want the map, reduce, filter functions to be acutely aware of the narrowed types. The moment I use Object.entries and then work on the data, the key has been widened to string.

Okay and maybe I also want it to be aware of type assertions that happened outside the array functions. You should already know that x cannot be undefined anymore. I tested that. I know you don’t think you can be sure you’re actually being called after the assertion, but you are! It’s obvious!

Oookay and maybe just one more (sorry Santa!): I want a ‘pure’ keyword. Any function that is pure can only call functions that are also keyworded as pure. No side effects, guaranteed.


> I want a ‘pure’ keyword. Any function that is pure can only call functions that are also keyworded as pure. No side effects, guaranteed.

Pipe dream, unfortunately. If pure functions accept objects as arguments, those objects could have impure getter methods, or perhaps be proxy objects that have impure methods. Pure functions could also receive Function objects, so you'd have to indicate that the passed arguments are pure as well.

Unfortunately, because TypeScript is optional, such a declaration on the functions and their arguments would also be optional, meaning that you couldn't even rely on this as a language feature (your caller could pass you an impure function even if you specified you didn't want it).


I'm not sure how much value such a feature would have, but both of your points are already addressed by the original wish. A passed in callback would have to be pure too and a getter function couldn't be called unless it's marked pure too. WebStorm already colors property access differently then the custom getter, why wouldn't Typescript be able to differentiate?

Your last argument is confusing: the same applies to everything in typescript. You can also pass in a string to a field that's demanding a number by using "as" so you never have any type guarantees in TS, there is value in it nonetheless.

And the same would surely apply to pure functions (I.e. inpureFunction as PureFunction)()...) It could still be a valuable feature anyway


Any property access could be a getter function, and TypeScript doesn't know which properties are getters and which aren't [0]. Similarly for Proxies, anything could be a proxy and the design goal of Proxies is that they cannot be differentiated from regular values.

But imagine that TypeScript, in addition to pure functions, also allows you to tag objects as "POD objects" or "JavaScript objects". The POD ("plain old data") objects can't be proxies or have impure getter methods, and the "pure" keyword actually means "pure if all arguments are POD objects" (this is to avoid having to write N purity overloads for an N-ary function).

You want to call a pure method from your impure method, and pass it one of your arguments. Your arguments now need to be declared to be POD objects, which means they are incompatible with the rest of your code base. This has to go all the way up... it's basically "colored functions" except it's colored objects this time, and the colors are completely incompatible (async functions can call sync functions, but impure functions can't call pure functions with impure arguments, and pure functions can't call impure functions at all).

[0] https://www.typescriptlang.org/play?#code/C4TwDgpgBAkgYgewVA...


> Any property access could be a getter function, and TypeScript doesn't know which properties are getters and which aren't

I'm aware. That doesn't make it impossible to differentiate as Webstorm shows by coloring the bar differently for fooA vs fooB in your example. But yes, it would have to be implemented as part of that pure feature.


All properties are potentially getters and any value can be a proxy.


The point is about only being able properties marked as pure with special syntax, whatever their implementation


If a compiler makes safety guarantees that aren't actually guarantees, like "this function is pure because you made some naive promises you can't keep", it undermines your ability to rely on any of the verification it does.

It's especially bad to do this if you're going to perform optimizations based on those safety guarantees (thankfully, typescript doesn't do that.) You can say 'don't use the pure keyword then' but the fundamental design of JS means that in practice almost no functions can be pure. The runtime has to identify cases on the fly where pure optimizations are actually valid.


Isn’t that the model of TypeScript though? If your code ever brings in data from an outside source, somewhere in it you’re saying, “trust me, it’s typed as X.”

Though now I’m doubting myself because maybe it’s possible to use complex enough assertion functions to really prove that. But even if you don’t, it’s still an incredibly useful language. You’re basically saying, if X is true then the rest of this program should fit together like so.”


Right, the thing is that a lot of typescript's assertions are valid even without complete top to bottom knowledge of the code being executed. Object shapes, for example, are pretty clean to reason about (proxies do complicate this some, but not nearly as bad as they do purity), which means structural typing stuff can just work. It doesn't matter whether property 'x' is a getter or a value as if all you need to know is whether an object has a property named x, and if you need to know the type that also is something you can check without needing to know whether reading the value has side effects. And shapes/types can be verified at runtime as long as state isn't mutated between verification and use.

You're right that in the end once you pull in outside data you're having to make promises that you may not be able to keep - I just think purity is an especially difficult set of promises to keep, and the existence of proxies and getters in JS (along with how they work) makes purity basically impossible, so it's just a footgun waiting to be used. And TS isn't going to optimize your code using it, so it's not really delivering value either.


Proxies and state-mutating getter functions are pretty edge features of JS tbh


doesn't `readonly` solve this already?


"pure" in the context of functional programming often means "without side effects", so writing a log file, interacting with a database and so on would also make the function not pure


fair enough, thanks


> the moment I use Object.entries ... the key has been widened to string

This is correct behavior though, and what you propose would be wrong. The type isn’t narrowed. Type U satisfies type T when the keys of U are a superset of the keys of T, so long as the keys map to the correct value-types in the intersection of (keyof T) and (keyof U)

This is because the only time extra keys are outlawed is when you instantiate an object of type T and explicitly declare it as a T. I can link if you need but this is an important subtlety which is why Object.entries returns string instead of keyof T, and it’s not borne of MS/TS developer laziness


You’re right about how it works today but it seems to me this is an odd choice to make. Why not enforce object types?

Could they add a strictness flag that lets objects actually be type safe and enables related functions to work out of the box? For me this stuff is a regular pain point as you end up typecasting at every call site :(


> Why not enforce object types?

It would be incredibly painful to write idiomatic JS with constant narrowing required and would lead to huge performance issues.

Imagine you write some generic sort capabilities, sorting by label, createdDate, etc., and then you declare some types

    interface SortableByLabel { label: string }
    interface SortableByCreatedDate { createdDate: Date }
    ...
Then you implement a method

    function sortByLabel(a: SortableByLabel, b: SortableByLabel) { ... }
I've written enough, I think you can see how easily this works today and how painful and un-performant it would be to do this if you had to narrow your objects or constantly write extra info into your types to allow them to not enforce narrowing, and then there's a whole can of worms around your "widen"-able object types being illegal to functions that require exact types, ...

Instead you can just write arrays...

    interface MyInterface {
      key1: Type1;
      key2: Type2;
      ...
    }

    const myKeys:Array<keyof MyInterface> = [
      'key1',
      'key2',
      ...
    ] as const;

    function operatesOnKeys(object: MyInterface) {
      // don't use Object.entries...
      myKeys.forEach((key) => {
        object[key];
        // TS is happy, and bonus:
        // this will be type checked later if MyInterface changes
      });
    }
I think TS has some issues but this isn't one of them.


> I want a ‘pure’ keyword. Any function that is pure can only call functions that are also keyworded as pure. No side effects, guaranteed.

It's better to default to the best practice. What you should wish is for an `impure` keyword and then all impure functions that are not marked as such would raise a compiler warning.


I agree that'd be better, but that ship has long since sailed. I for one specifically don't wish for that because a breaking change like that would create a new python 2/3 situation.


Doesn't fit with the Typescript philosophy though: It should be possible to gradually add Typecript annotations to existing JavaScript code. It wouldn't work with 3rd party libraries either.

If you want something like this you might as well use a completely different language.


I agree that it’s not practical. Ship has sailed a long time ago.

But one solution might be a “noImplicitImpure” option.


Still wouldn't work, since there is no way for the compiler to distinguish between a pure function and an impure function which just don't have an annotation - e.g. a third-party library written before "impure" was introduced to the language.

If pure functions were explicitly marked as "pure" the compiler could just conservatively assume all other functions were impure. But the opposite does not work.


Oh. You’re right. The moment you turn that flag on, it can’t complain the away implicitAny can. Therefore you need “pure” rather than “impure.”


Ezno a semi compatible TypeScript compiler does effect tracking. It’s possible but you do have to handle the way functions handle parameters which is why it’s type system is different to the way tsc handles things. And for functions outside of the user code you have to know what effects are internally run. Things like JSON.stringify look pure until you pass it an object who has a toJSON method that does something silly. A little complicated to show what stringify internally does but possible if you add additional metadata to the definition files.

Then rather than marking a function as pure you instead check it is pure by looking at what effects its function type has!


    [1,2,undefined,3]
    .filter(<T>(x: T|undefined): x is T =>
        typeof x !== 'undefined'
    );
Yes builtin lang support would be better, but that gets you out of trouble in a pinch, without adding more 5 line npm baggage.

N.B. if you are in a JSX file, you'll need to add a comma: `<T,>`


You can implement a type-aware map, reduce, filter, etc. if you use point free semantics with higher kinded types:

https://code.lol/post/programming/higher-kinded-types/

I have to admit it's a bit heavy, but this is totally representible in today's Typescript if I understand your ask correctly. It would be great if it was a more obvious / automatic inference, though.


Yea - filter especially doesn’t seem to work so well when you filter out null/undefined values then chain to another map


for the situation of filter, map, then knowing x is present, there is

https://www.npmjs.com/package/ts-is-present

that particular case is a small snippet but the package also comes with helpers for reaching into objects to do a similar thing


The opposite would be better: an "impure" keyword for specifying impure functions.

Or maybe, you could pass some kind of "scope" interface that provides functions for executing side effects. So "impure(IO) function printStuff()" brings all the functions in IO into scope for the body of printStuff.


Why would the opposite be better? Aren't most functions impure? The original proposal made sense to me, but I am curious as to why you believe impure is better. Can you elaborate?


Pure being the default would lead to most functions being written to be pure. Same idea as immutability by default.


> Pure being the default would lead to most functions being written to be pure.

More likely it would lead to most functions looking like they are pure, whether they are actually pure or not. For example, any function written before the "impure" modifier was introduced would look pure.


Deducing types from Object.entries would be pretty awesome. That would feel like black magic. I had a lot of fun messing around with Copilot seeing if it could guess exactly that kind of thing, sometimes directly from raw results of SQL queries once it understood the DB table structure. Occasionally it could. (Nonetheless, I don't feel like paying for it).

Still, it feels dangerous to be able to feed a generic object method in and expect the return to be typed based on a previous typing of the composite instances. Or maybe it feels like something that should always pass a compile check - because Object.entries can always be any - and only fail at runtime. Even if it was a feature, it's something I wouldn't implicitly trust a compiler to catch all edge cases of.


> Deducing types from Object.entries would be pretty awesome. That would feel like black magic.

Not really. Typescript already has the

  keyof
keyword. For some reason though

  Object.entries(T)
returns

  string[]
when it could easily return

  keyof typeof T
I've had to write a helper function that essentially does this for you with a generic and a cast, but it feels unnecessary.


I love the pure function idea.


The reason you don't want a pure keyword is because suppose you're doing some sort of stateful mock. Or, maybe you have a configuration that start outs having a fixed compile time lookup in a passed data structure, but then you evolve it to where it looks it up via a web service. If you were making assumptions that it was pure in either situation, your refactoring is going to be crazy painful.


Highly recommend rambda, the types are better than the native functions.


I’ve got many TypeScript Christmas wishlist items, too, but I have to admit I disagree with a few of these!

A new syntax for tuples is overkill. TS is already getting quite syntax heavy and I feel that the current methods for defining tuples are more than adequate—either with “as const” as described in this post or like “const foo: [number, boolean] = [5, true]” as the TS docs recommend for tuples.

Runtime type evaluation—please no! This is the perfect domain for libraries to exist in. When you import a library, it’s explicit what’s going to happen at runtime. Libraries are easy to debug, forkable, and swappable. Baking this behavior into the compiler not only foregoes those benefits, but also makes the output less easy to predict and will of course add overhead to the compile.

My own wishlist top 3:

1. Better following of type narrowing through branches and functions. After I filter out all nulls in an array or prove that some optional variable is definitely not undefined, there are still times when ts can’t tell that that’s the case. I can force it to with workarounds, but wish I didn’t have to.

2. Compiler perf improvements. Hard to disagree with you there! Esbuild locally and tsc in ci is okay-ish, but not an ideal state for the ecosystem to stay in for the long term.

3. Consolidation of tooling. For node do you go with tsc and run the output? Or ts-node? Esbuild locally and tsc in prod? Deno? Browser-side, similar questions but throw in Vite and all the other options, too, and even for someone working in this environment regularly, setting up a new project with all the proper tooling is far too much effort.


> 3. Consolidation of tooling. For node do you go with tsc and run the output? Or ts-node? Esbuild locally and tsc in prod? Deno? Browser-side, similar questions but throw in Vite and all the other options, too, and even for someone working in this environment regularly, setting up a new project with all the proper tooling is far too much effort.

I actually really agree with the last sentence, and think an officially endorsed set of projects would be pretty useful. The first part of your wishlist though, I don't think is a big issue. I'd say close to 100% of the people using a bundler are using tsc as a linter. Esbuild, babel-typescript, rollup, swc, these all strip TypeScript types and don't raise TypeScript errors at all. Those that aren't using tsc as a lint step are going to be directly running the output of tsc directly, given that the only way to not do this is ts-node, the documentation of which advises using tsc as a linter.


I agree about the record and tuple syntax, but that's actually a JavaScript TC39 proposal, not TypeScript. We already have # for private, and now they're adding the same symbol for different semantics, oof.


But you can type 'pipe' correctly in Typescript, without resorting to overloads:

https://code.lol/post/programming/typesafe-function-composit...

It's even more powerful if you restrict yourself to point free semantics - then you can use higher kinded types to do generic-level variadic composition.

As well, optional generics are trivially achievable in TS via default type parameters, unless I'm not understanding that section of the article.


> As well, optional generics are trivially achievable in TS via default type parameters, unless I'm not understanding that section of the article.

He links to another post that goes into more detail: https://effectivetypescript.com/2020/12/04/gentips-1-curry/

In particular the question and answer in the comments should make it more clear.


Good write up.

I had a feeling some form of this must've been possible, especially that with newer versions of TS the compiler has become increasingly better at handling recursive types and doesn't throw the dreaded TS2589 error nearly as often.


It may be a rendering bug, but this page is incredibly frustrating to read on Firefox mobile, with the Buy the Book/ Subscribe banner sitting directly in the center of the screen when I scroll down.


Same for me. If you use uOrigin you can select and hide the element, but I didn't even bothered. Now I now I won't be visiting this site anytime soon, at least not on mobile.


Most of that not gonna happen by ts ideology. But the reason why people need that is unclear to me. Typescript already is 99.9% typing improvement over javascript. Chasing that 0.1% out of pure perfectionism makes one an enemy of the good and of themselves.

Please remember that these extends keyof and co were included in ts not because it’s so super cool with types. They are there to cover common dynamic patterns of existing js codebases and standards. You don’t have to make same mistakes again, and definitely aren’t expected to make this problem worse. There’s already enough popular projects with perfect multi-page meta-type gibberish instead of plain human-readable types. Which wouldn’t require rust-tsserver running on a supercomputer in the first place.


You'll have to pry my meta type garbage out of my cold dead fingers. :)

I agree with you - while neat types are cool from a mathematical perspective, they impose a huge maintenance burden. Most times, it's much smarter to accept a bit of type unsafety than to try to achieve perfection.

Pretty much the only exception is in library code where you can use advanced types to provide improved developer experience or prevent user error.


uh... people want them because they run into pain points every day, in a project that's actively developing and can solve those pains. Why wouldn't people want their pain points solved? They're not, like, rare issues. They happen every day, or at least every few weeks. A month can't go by without my putting up a code change that has "unfortunately, TS doesn't support this, so <workaround>" in the description. I love TS but it can certainly be better.

edit/afterthought: I just do not care about JS-compatibility per se. It's a nice guideline but I'd like the language I use to be as good as possible, because I'm trying to use it to do things, regardless of their ideology.


If you don't care about JS compatibility, that opens up lots of choices. Many languages compile to JS and interoperate without being constrained by syntax-level compatibility. Kotlin [1] is a nice statically typed language, for example.

[1] https://kotlinlang.org/docs/js-overview.html


TS is way better than Kotlin, imo. At least last I checked.

I mean that I don't care about JS compatibility _exactly_. I still want to write JS, just with all the problems... fixed.


Whatever they do, you’ll still have pain points, because people tend to ignore the good and focus on what’s left, and what’s left is never zero for that’s how the universe works. Gods run into pain points all the time.

The correct question is not where our pain points are, but how far these pain points are from what we want to do, i.e. are these points a real pain or just our own perfectionist anxiety. Typing for the sake of typing may not help doing the job before thursday.


Yeah but when you fix this round of pain points and then people complain about the next one... then that means I got a bunch of improvements? Is your point "because you'll still have pain points after improving things, don't improve anything?" that makes no sense.

In no sense are the issues with TS "perfectionist anxiety". They're real, fixable problems that make it harder to write safe or simple code.


> The sluggishness of tsc remains a pain point for many developers

tsc isn't just slow, it's SLOW.

    % : >file.ts
    % time tsc file.ts
    tsc file.ts  3.26s user 0.15s system 145% cpu 2.347 total
3.2 seconds is just embarrassing. Nothing is even close to this; every other compiler or interpreter is in the dozens or maybe low-hundreds of ms. clang++ -O2 and rustc are both ~150-200ms, and these are not compilers known for their excellent performance. The startup time of tsc is an order of a magnitude slower than anything else. I didn't measure lines of code/second performance, but with such a slow startup time it's not that important because no matter how small your project: typescript will be ridiculously slow.

I don't want to run any "watchers" or special dev servers for the frontend or all of that ridiculously complex circus to work around this embarrassingly slow tooling. I just want to request "/file.ts" and have it serve a compiled version on the fly. This is simple, easy, and even with caching just a few lines of code. It's how everyone was using coffeescript back in the day.

What we have now is millions of lines of code and a world of complexity to work around an embarrassingly slow compiler.


Is it really much of an issue? I work on a large TS project on a 2020 m1 MacBook and I’ve never been aware of the ts compilation. It seems snappy enough.


> Is it really much of an issue?

Yes, because I don't want to wait seconds every time I change a typescript file and I don't want to world of complexity to make it have the same performance every other compiler has out of the box. My "solution" for this is to not use typescript. I'd like to, but it's just too painful.


Your setup must not be right. The one I’m using at work reloads the page when I save in the editor so by the time I switch to the browser window it’s already loaded. Basically no perceivable compile time.


Yes, and that is a world of complexity that manages all of that behind the scenes.

The fact that you need to "setup" anything is already a problem. I want stuff to Just Work. No extra processes. No extra steps. Nothing to update. Works today, works in five years. Just start one process and it will work. I don't understand what's so hard to grasp about that.

This is trivial to do with a compiler that has an okay-ish (not even "fast") performance. It's not feasible with TypeScript today, even for fairly simple applications with a few hundred lines of code.


tsc is slow (and that's a shame), but there are Typescript compilers like swc that are quite fast.

It's totally reasonable to keep your setup simple, all that complexity is often not required. It only really pays off on larger projects (it's super simple to set up nowadays though).


I believe swc only compiles TypeScript, but doesn't actually perform any type checking (same as esbuild)? You can make tsc a bit less slow by disabling type checking (it's still slow, but less), but it seems to me that kind of defeats the point of using TypeScript in the first place. It's nice for creating builds, but not very useful during development.


There are other, arguably better workarounds than not using Typescript. The main reason that so few people are bothered by tsc being so slow is that they don't actually use it for compiling! Because Typescript does absolutely nothing at runtime, you can just strip the type hints, get valid JS and be done.

The type checking usually only happens during development time, continually or on demand. Shifting type checking to the IDE is how Vite can be so fast.

I don't know if there's a popular solution for immediately seeing changes in production like you seem to be looking for (running dev servers in prod is probably not a good idea), but regardless I think Vite is worth a look. It gets rid of a lot of the cruft people criticize about web development.


> I don't know if there's a popular solution for immediately seeing changes in production like you seem to be looking for

No, I just want to run it in dev. There is no good way to do this currently because it's just too slow to be smooth.

Vite is undoubtedly an improvement over what came before, but the entire method of operation is just not what I want. I don't want to run a Vite process in a second terminal. I don't even use npm for half the things I work on, because there is no need for it. I just want to compile a file every time I load it on my dev machine (i.e. on 'GET /file.ts' runs 'if (hash("file.ts") != cached_hash("file.ts") { tsc("file.ts", "file.js"); serve("file.js") }'). For production, I just compile the files in the binary and serve those.

There are other reasons one might want to use Vite ("hot module replacement" and such, although I personally dislike it) but currently you need to use Vite or something like it, with no realistic option to keep it simple if you want to. If tsc was faster it will become realistic to make everything a lot simpler, with a lot less tooling, and a lot less complexity.

> other, arguably better workarounds than not using Typescript

My current plan is to look at rescript when I have some time. It seems a lot faster and offers mostly the same features. Though the big advantage TypeScript has is that it's JS-compatible and "upgrading" a project is a lot easier, and that's it's kind-of standard whereas rescript is kind-of obscure (which doesn't matter for my personal projects, but does for some things I work on as a contractor, where I want to make sure the next person can work on it in 2 years after I'm gone).


What's funny is that everyone was attacking Jonathan Blow for being against LSP, but his primary argument is all that computation is going to be slow.

He has a point, although the advantages outweigh the disadvantages for sure.


So you are writing naked JS or what?

A lot of developers decided slow compiles - I don’t know what pathological case you stumbled on to make a single file take 3s tho - is far superior to needing to suss out runtime errors, and I find it hard to believe you made the choice to write naked JS instead of TS. If you picked a different language, fair enough I suppose, but I have to admit I’m not that sure why someone would tap JS/TS if another language is equally suitable. I am a front end guy after all, so I don’t particularly have a choice


> I don’t know what pathological case you stumbled on to make a single file take 3s tho

It's an empty file. It's not a "pathological" use case. It's simpler than "hello world". It's literally the simplest possible use case. I do have to say I have a fairly slow laptop, but everything else is plenty fast enough.

> I find it hard to believe you made the choice to write naked JS instead of TS

Why is this hard to believe? Most things I work on are a few hundred lines, maybe 2,000 lines at the most. Not huge, but not trivial either. Some of these are personal projects, on some I work as a contractor. Many have an uneven pace of development: lots for a bit, then nothing for months or years. I want stuff to "just work", even after 2, 3, or more years, and I don't want to update loads of tooling every time I come back to it. This is already a bit of an issue in JS-world in general, so I try to keep things simple. This means, among other things, simple build steps. With a okay-ish performance (doesn't even need to be "fast") you can keep builds very simple. With the current performance this is a lot harder and you need a lot more complexity to make it work smoothly.


tsc is the reference implementation written in JS so of course it's slow. It has to boot up the nodejs runtime first

Use swc or esbuild if you want fast TS compile times and use your IDE or eslint for type checking during development


Python or Ruby isn't that slow. Coffeescript isn't that slow. Several seconds really is very slow. It's a tsc problem, not a nodejs problem.


Python and Ruby are implemented in C, not interpreted languages

CoffeeScript simply compiles code and does not perform static analysis

tsc is implemented in JS and performs static analysis which makes it (unsurprisingly) slow. I suppose you can make the argument that tsc should have an early exit for empty files but that's just unnecessary bloat to the code base.

Surprisingly, tsc doesn't have an option to skip type checking [1]. The best you can do is run using --skipLibCheck to skip checking node_modules.

[1] https://github.com/microsoft/TypeScript/issues/29651


A better comparison would be if the Crystal compiler was implemented in Ruby, how slow would it be?

Node is fast but there is an initial startup penalty, especially if the project has many files to load at runtime for interpretation. I bet it's spending a lot of time on require().


> A better comparison would be if the Crystal compiler was implemented in Ruby, how slow would it be?

Faster than tsc because Ruby doesn't need several seconds to start up and neither does node. Opal translates Ruby to JavaScript (and is implemented in Ruby) so we don't need to engage in unfounded speculation about non-existing implementations:

    % :>a.rb
    % time ./bin/opal --compile a.rb > a.js
    ./bin/opal --compile a.rb > a.js  0.54s user 0.12s system 90% cpu 0.735 total
It's not very fast, but still several times faster than TypeScript, and it includes a large runtime (the resulting file is 2M); disabling that speeds it up by ~150ms (most of that seems to be in the source map generation for the runtime, since --no-source-map with the runtime gives similar results). Opal is also a fairly small not-very-active project developed by a few people in their spare time, not a major language sponsored by a major company used throughout the world.

Coffeescript, which uses Node, also doesn't support from multi-second startup times:

    % :>a.coffee
    % coffee a.coffee
    % coffee a.coffee  0.10s user 0.03s system 101% cpu 0.125 total
> especially if the project has many files to load at runtime for interpretation. I bet it's spending a lot of time on require().

How often do I need to repeat it? It's a single empty file. Are people even reading what I'm saying or what because I've had to repeat this more than once now. It's a a file with zero bytes. No content in the file. No diskspace is used. The number of bits in the file is zero. A single empty file.


When I say this:

> especially if the project has many files to load at runtime for interpretation. I bet it's spending a lot of time on require().

I am talking about tsc itself. If there are hundreds/thousands of modules to load, node will spend time on that. coffeescript is a much smaller transpiler, with just a handful of source files.

EDIT - looks like tsc might get distributed as a single file, which helps here. However, it is a 7mb source file :)

https://raw.githubusercontent.com/microsoft/TypeScript/main/...

When I execute it locally, with no arguments, it finishes instantly as far as I can tell. So maybe tsc itself is indeed super slow for just an empty file.


I suspect you have a different development style than the person you replied to. For test driven development or any development style that validates constantly on file updates seconds is bit frustrating.


Yes it is. My work laptop is 8th gen i7 (yeah, I know) and every time I have to recompile the whole project I'll have few minutes of downtime. It's too little time to do something else but too much to just stretch, yawn and continue. It's ridiculous. Yeah, most things can be solved throwing more horsepower at it, but currently it's as slow as gradle building with my java projects ffs.


Not sure but I think startup time is impeded by the fact that tsc has to parse and synthesize around 20k+ lines of TS definition files. These require a parse for hoisting, require scanning lots of comments which are only used in LSP mode and are not a optimised format to parse.

On the other hand Ezno a semi compatible TypeScript compiler can use a more low level binary format which is only one pass and whose name references are already resolved https://twitter.com/kaleidawave/status/1596445852918325250?s... . This is only really possible because it is written in Rust so low level byte and memory management is relatively simple


TypeScript the language works well for me. All I really want is a more robust incremental mode, especially with respect to renaming or deleting files. I've been bitten enough by this that I blast the cache directory in my pre-push hook.


These are all really weird and not idiomatic. Most of your weird quibbles would be solved by just writing a type like

const pt: [number, number] = [1, 2]

As for piping functions... that's just how functions work. Not sure what the point of that piping syntactic sugar is.


The pipe is one of those things that once you've tried it you never want to go back.

The pipe came to R a few years back and I would argue that it is one of the best features. I admit that data munging is a very stepwise process but I find myself frequently missing it in Ts when I code. It makes the code denser as you use fewer intermediate variables while the code id still easy to debug.


It doesn’t have to be the only way that functions work. Pipeline operators are idiomatic in other languages, and the linked proposal in that section of the article is worth reading.


Sure, but not in JavaScript.


I’m admittedly not primarily a TS dev, but I agree with your points completely. Even though you’re reading the piped functions right to left, and most code is going to be read from left to right, in order for a parameter to be passed like a value it’s gotta be computed.

If I’m solving a math equation, I know that I need to follow the order of operations—I don’t need to rewrite the equation in an entirely new way. It’s more intuitive for me, at least, to wrap my head around the current approach.


It takes about thirty seconds to become used to writing nested function calls as a pipe - it's purely syntactic sugar. It's not what I'd call an 'entirely new way' in the sense that changes nothing about evaluation order or anything like that.


I've been bashing my head trying to create some sort of typed pipe for a while, with a syntax matching Elixirs, to avoid having to create curried functions all the time. The farthest I've gotten is that I achieve typing, so long as the function isn't generic. So this works:

  const plus = (a: number, b: number) => a + b;

  pipe(5)
    .thru(plus, 3) // Pipe(8)
           //   ^---- this is inferred as `number`
But this doesn't

  function plus<T extends string|number>(a: T, b: T) {/* ... */}

  pipe("five")
    .thru(plus, "three") // Pipe("fivethree")
             //   ^--- this isnt inferred or narrowed,becoms `unknown` iirc


Have you tried this:

    function doSth<A, B>(a: A, b: B): B { /* ... */ }

    function callFn<Args extends any[], Out>(fn: (...args: Args) => Out, ...args: Args): Out { /* ... */ }

    const foo = callFn(doSth, 'asdf', 'bsd' as const) // a: 'bsd'
    const bar = callFn(doSth, 'asdf') // error


I've tried something similar, but splitting the args into Head and Tail and extending the Parameters<F> of the input function and some other trickery Been a little while since I looked at it tho


The one thing I want is operator overloading, so I can write vector calculations in a natural way. But this would need differing behavior depending on types, which is something the developers don't want to add in principle. They say it has to be added in JS first, but this is something that makes not much sense in JS but is possible when you have type information at compile time. (You don't want to add the penalty of dynamic dispatch to every addition, just rewrite v1+v2 to v1.__plus__(v2) when you know statically that v1 and v2 are Vectors.)

I thought about implementing it myself (maybe as a babel transformation) but I couldn't figure out a way to access the type information.


I want actual reflection a la python so i can use my types to generate all sorts of artifacts for runtime validation, serialization, testing, etc. It's frustrating to make the effort to create the types and then not be able to touch them at runtime


How would Typescript do that while transpiling to JS? It would need to add a whole lot of boilerplate code to the generated JS, slowing the execution down.


Something like typeinfo<T> being inlined into an object describing my type. Totally opt-in, no need for every type to be tracked at runtime.


Does it not already do this? I don’t use TS if I have the choice due to all the runtime complexities, including the endless house-of-cards shimming it does to prop up all the features of TypeScript.


No, typescript has full type erasure and almost no type-directed emit. The shimming you're seeing is probably the result of tsc downleveling newer javascript features to older browsers: typescript itself has nothing to do with it.

https://www.typescriptlang.org/tsconfig#target


Try running tsc in ES2020 mode, the output will be surprisingly clean JavaScript. Older versions include a lot of hacks for dynamic module loading, etc.


Why should types even exist at runtime? Everything should be validated at compile time and then the whole concept of types no longer needs to exist.


You are sometimes interacting with external data. Particularly in web land, you don't know for sure if the payload meets expectations until you check.


Plenty of solutions here such as code generation or Elm style combinators. I’m not convinced that Python style runtime meta programming is a particular robust solution since it is not (easily) checked statically.


Sure. But it would be nice not to need to describe the structure of your data twice if you need runtime validation. Since TS is such an ergonomic way of describing the structure of your types, runtime availablility is more attractive (to some) than relying on external libraries for that functionality.


You might be aware of it already, but I just wanted to mention that the Zod library could potentially solve some of your needs.


Yes, that and the openapi plugin is what i use right now. Before i used to load the ts compiler at runtime and tell it to parse my file and got the types from there. But it was a stupid overengineered brittle solution.


Can we talk about grammar? I've noticed it is becoming normal not to use the plural 'are', this title is triggering for me. Even on primetime TV I'm hearing 'is' everywhere, does anyone else complain or am I just getting old?


It's a play on the song "All I want for Christmas is you"


The original play might have been this 1947 classic All I Want for Christmas Is My Two Front Teeth.

https://en.wikipedia.org/wiki/All_I_Want_for_Christmas_Is_My...


I think this feels grammatical to native speakers because "my two front teeth" is interpreted as a single item or gift. There's kind of an elided "to have" there: "all I want for Christmas is to have my two front teeth". "these seven TypeScript improvements" don't form a coherent whole in the same way, so it feels ungrammatical.


I think it’s because “all” is a singular noun and the subject of the verb “is.” I would use “is” even with a list of items at the end of the sentence.


Nice, I'm grumpy about hearing that song every 5 minutes as well. :)


“All” is acting as a singular noun here, so the grammar is correct: https://www.grammarphobia.com/blog/2013/01/all-for-one.html


Damn now I'm triggered and confused. I'm not sure if I believe this, will have to look closer. Grammarly accepts either way.


Hah, funny! I genuinely did not notice until you mentioned it and I reread the sentence using ‘are’.

Extra funny because I’ve been caught noticing myself using english plurals in my native language. (so for 'lantaarnpaal' the plural would be 'lantaarnpalen' but I’ve been saying 'lantaarnpaals')

I guess it might just be how language evolves, it’s interesting to see it happen. I wonder if grammar books will ever adjust to these changes?


English is not my native language, but looking at the French equivalent, wouldn't the choice of 'X is Y' or 'X are Y' be based on whether X is plural, as opposed to Y ? "Bob is three kids in a trench coat" sounds better than "Bob are three kids in a trench coat"

What's the rule for cases where it is not known whether X is singular or plural ? Phrases like "What we found" or "All I want for Christmas", for example. French grammar has several rules where, if a word must agree with a still unknown number or gender, then singular masculine is used: "J'ai mangé une pomme" vs. "La pomme que j'ai mangée" (the verb agrees with the apple only if the apple appears before it in the sentence).

EDIT: I just realized that "All I want for Christmas" may actually be a plural, but "All I want for Christmas are a cookie" sounds strange...


Not a native speaker, but I feel the distinction between “these are …” and “all I want is these …”. In the first case the verb works (for me) with the pronoun’s own plurality, but in the second case “all” accumulates an inanimate multitude into a singular set.

Does English have a special rule? English.SE doesn’t seem to have a clear answer for this.


If its plural it should be 'are'. https://www.grammarly.com/blog/is-vs-are/ I think its clear but just comes from using the language a long time. Like I said its very common in the USA for people to use 'is' everywhere now.


I would rather give everyone a shock collar that triggers when they add -ish to an adjective or adverb instead of using proper vocabulary.


Are we doing wishlists? All I want is the possibility of doing nominal typing.

Hrmph: https://github.com/microsoft/TypeScript/issues/364 - https://github.com/microsoft/TypeScript/issues/4895


I dont use TS, just vanilla JS mostly. As a vanilla JS user, the pipe operator is much harder to understand from a first glance because its effectively a primitive being added to the language.


There is a js proposal to add this to language: https://github.com/tc39/proposal-pipeline-operator

There are few exceptions (namespaces, enums) but typescript has been quite averse to adding non-standard language level features outside type annotations. This feature will likely come to ts after this proposal has been accepted for js.


Firefox already supports the pipe operator. There is a Babel transformation for the pipe operator. However, TS does not support it, even as an experimental option.


My wish list:

- Type the default export in a single expression (issue #13626, open for 5 years)

- Negated types. E.g. `string & not 'abc'`. `Exclude` only works for union types

- Allow 'never' to take a type parameter that will show up in my IDE when a type resolves to it. 'never' is often used in more complex type construction, and if something is unexpectedly resolving to 'never' it would be nice to know which 'never' it is, a bit like an error message


I just want higher-kinded types to be supported. There is an issue[1] about this which has been pending for 8 years.

1. https://github.com/microsoft/TypeScript/issues/1213


> Why doesn't TypeScript infer the type of square (and hence x) from its usage on the next line? Anders is famously skeptical of "spooky action at a distance" where changing code in one place can cause a type to change and produce errors in other places that aren't obviously related.

I feel it in rust when current error spot jump wildly across all of my code as I'm fiddling trying to fix it or modify it. It's not pleasant, but funny when you fix the last few red spots in one place only causing the huge red bloom in far away place or red jumping back and forth between two far away places as you type.


the final request is a big advantage Python currently has over Typescript's otherwise dramatically superior type system. The utility of types at runtime for dynamic ser/de is huge for a lot of common application domains.


I’m also excited for records/tuples, but I’d really happily settle for an intermediate “constAsConst” setting: as in if I assign a const, I’d like to be able to tell the type checker that it really is constant, that I don’t want to change any data in a collection type, essentially that it should treat the const keyword as an `as const` assertion. I’d have to fight with a bunch of other lint rules, but it’s what I mean ~99.999% of the time if I have my druthers.


Maybe I’m doing it wrong but when I try to handle this kind of thing, I get bitten by “readonly” and now I need to put readonly keyword in all kinds of functions. You’d think it could implicitly tell it’s readonly. Or maybe that’s a feature of my “pure” keyword idea.


As an obsessive readonly-er, yeah this pain is real. It basically colors your functions the way async does. There is a TS issue I haven’t checked on in a while about readonly-by-default and a corresponding mutable keyword. I doubt it got much traction, but it’s a shame.

Writing Clojure I really liked that there was a baseline assumption that functions are pure and a clear signal in code when it’s stateful. It doesn’t have to color functions (but ahem it works great for Haskell), but it seems like a reasonable opt-in default that shouldn’t be so painful.


They heard yer request yet kept immutable.


Give it a few more years and every language will be OCaml :)


Hope to see you again at the Idris stop!, or will you be getting off the bus after everyone has turned into OCaml?


Does Flow do a better job of the tuple examples? I feel like it inferred types on variables without annotations quite well -- `x=[1,2]` being something you could pass to a function expecting `$ReadonlyArray<number>` or `[1,2]` or `[any, number]` or anything in between. I think the type on the variable is often something complex or narrow at the point of definition and getting progressively wider as it's used in various ways.


Flow is / was awesome but Microsoft killed it using VS Code.


Yeah. I think typescript has better predicate functions, maybe a slightly worse behavior in most places, and Flow is much much faster. But all of those differences get swamped by the "everyone is using it" effect. Typescript "won" in some vague narrative/consensus sense, so we standardise on it, and we get the ecosystem benefit of types existing for every NPM package.


Zod-like runtime parser/validator but built in and automatically inferred from type definitions so I can do MyType.parse(scaryUserInput).


Yeah it would be great if TypeScript just became Elm.


Why? Cant you just use Elm if you prefer that? Typescript is specifically constrained by being a superset of JavaScript, Elm does not have that limitation.


I suppose I should have appended "/s".

I find it amusing/sad that there is simultaneously reticence in adopting Elm and an eagerness to gradually copy all of its features as the JS crowd learn that typed pure FP isn't such a bad idea after all.


There are significant downsides to using a niche language.


Ok, and there are significant downsides to using JavaScript, so pick your poison.


That goes without saying :)


A big YES for getting access to type information at runtime. We can’t trust the client. So all messages needs to be validated when received. I am currently doing this by code generating the protocol types and verification code from a custom spec file. It works but it’s a clunky way to do it.


Two biggest quality of life improvements for me would be:

Fixing very slow intellisense in VSCode when large numbers of types are imported. Such as from AWS SDK or CDK.

Show resolved types ie {...A, ...B} vs A & B.. There are hacky workarounds that shouldn't be necessary.

Issues open for years on both.


Out of curiosity, what languages have type systems that can accomplish the "typed pipe" described in the article? There are plenty where you can specify a fixed number of arguments like (A -> B, B -> C), but how do you do that in a variadic context?


If the type of C is a type D -> E -> F... then you're already there.

Or I'm misunderstanding the question. (In which case, please, somebody correct me.)

But if I'm not... Elm, Haskell, F#, and probably most other FP langs support this.


Sorry, I just realized that the pseudo-notation I was using to represent a partial type signature is similar to the actual type signature notation that Haskell and Elm use, so that's probably confusing things.

What I specifically meant by "(A -> B, B -> C)" was "two arguments, each of which are unary functions, and the type of the argument accepted by the second function is the same as the the return type of the first function." I think in formal notation, the full signature would be "(a -> b) -> (b -> c) -> (a -> c)" but I'm not overly familiar with that notation so I may have gotten that wrong.

The goal here is to be able to describe a function as mentioned in the article, that can accept any number of functions (either as varargs or a single list of functions), and ensure that the input of each provided function has the same type as the output of the function that precedes it.


Haskell can do this, although it's pretty unidiomatic:

    data AlignedFunctionList a b where
         Identity :: AlignedFunctionList x x
         Pipe :: (x -> y) -> AlignedFunctionList y z -> AlignedFunctionList x z

    pipe :: AlignedFunctionList a b -> (a -> b)
    pipe Identity a = a
    pipe (Pipe f g) a = pipe g (f a)


I must be missing something, but in Haskell this “typed pipe” (which is just reverse application) already exists in the base package as Data.Function.&.

  (&) :: a -> (a -> b) -> b


> that can accept any number of functions

So, `(a -> b) -> (b -> c) -> (c -> d) -> ... -> a -> z`.


Ah, right. In that case I suppose you'd want some kind of fold.


You can write a type aligned list of functions datastructure in any language with generics and some form of interface, the same way you'd write a cons list. (To write a generic version of it you need HKTs, but with the type fixed to -> that's not an issue)


You can actually accomplish this in Typescript, I linked a blog post in one of my other comments.

The key is mapping through all of the arguments and enforcing that argument N's output is a subtype of argument N+1's input.


C++ can do it with variadic template overloading.

https://stackoverflow.com/questions/35959979/functional-comp...


If the pipe is simple sugar then it trivial to fit into a type system.

    let x |> f  = f x


Typed Racket, but you'd write the pipe operator as a trivial macro rather than as a function.


The only true thing that bothered me when using TS was the lack of pattern matching (the trick using the never type doesn't count), I can't believe a language with union types doesn't have a clean way to make exhaustive union discrimination


One of the 5 stages of grief: bargaining :)

I have tried to use a couple of tools with better types on the job and on the "productive/industry" side of the spectrum. That consistently led to disappointment. It's been a while since I gave up on TS; what drove me away then was the typings I had to download and update and debug.

Nowadays I'm working on learning the purescript terrain for frontend development. In that world, the limitation is not the language but my ignorance. That feels better.


It is the nature of programming that we manipulate symbols.

It is the nature of programming that there are tools that solve these problems. Tools that aren’t TypeScript but can be used on top of TypeScript. Exactly like TypeScript is to JavaScript.


It is the nature of programming that the programmer will complain about just about everything, until it gets fixed. At which point the programmer will begin to complain about the fix anew.


A lot of the strands in the notice-understand-fix loop are simple transformations, almost mechanical – deterministic ones corresponding to a simple formal grammar underneath. As long as you can cleanly and concisely discuss and point to the symbols in question across programming scopes, and as long as the programming language is able to manipulate these scoped structures. TypeScript on top of JavaScript is one layer of this. Things like OCaml generating Javascript can be seen a higher layer as the OCaml type system is strictly more powerful and complete than TypeScript. Then dependently typed languages close a certain loop here, as dependent types are programs, and dependently typed compilers concisely and deeply capture this kind of program-in-the-program transformation.


The fun fact is: Scala can do all this (and lot more). And it can be compiled into JS through Scala.js. It doesn't require a runtime library and has decent interop with JS.


The only one I need from this list is optional generic arguments.

The `satisfies` syntax from 4.9 was a huge win, and the `const` indicator on generic arguments coming in 5.0 is huge for me.


Tuple+Records would be huge. Pattern matching would nice too as well as a dense form to write tagged unions, but I want these things in JavaScript first and not just TypeScript.


I'm a simple guy. Excess property checking not just on object literals would be a wonderful Christmas present.


Sounds like what you really want is c#.


The pipe wish can be accomplished with a class. Not the most ergonomic but works well. You just need to explicitly cast `return <?, ?>this`.


That means all the functions you are piping have to have the same return type (i.e return an instance of your class). Piping is more generic.


All I want is nominal types, if this is possible already, somebody please let me know!!


Typescript does not support nominal typing except for enums.

It can be emulated by giving types an unique property, but this is kind of a hack.


Test comment


Test Reply


>did that red squiggle go away because I fixed the error, or because I'm waiting for tsserver to catch up?

Shouldn't you know without red squiggle if you fixed something?

That's like waiting for the red squiggle in Word to know if you spelled a word correctly.


when I see a an error I build a hypothesis about the error before reading into it. then I solve the hypothesized error and verify that it indeed was solved.

this works in 9 out of 10 cases and is hugely effective - as long as the ls is fast.

this is like branch prediction. better the faster you can verify that the branch you predicted was the right one.




Consider applying for YC's W25 batch! Applications are open till Nov 12.

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

Search: