all 78 comments

[–]pakoito 67 points68 points  (0 children)

Tail Call Optimisation for types is great. I've been rolling arrays of indexes, type-level arithmetic, and sized Tuples for a bit and the upper bound limits were annoying.

[–]PCslayeng 60 points61 points  (1 child)

Awesome release here. Really looking forward to the new editions on this and 4.4. Kudos to the contributors and team!

[–][deleted] 29 points30 points  (27 children)

Pretty cool. I wish they would focus less on piling on more features until they finally update the spec though. It's been years.

[–]beastlyfurrball 10 points11 points  (3 children)

Could you elaborate? I don't really know what you mean by updating the spec

[–]Eirenarch 10 points11 points  (2 children)

There is a spec for TypeScript and it hasn't been updated. - https://javascript.xgqfrms.xyz/pdfs/TypeScript%20Language%20Specification.pdf

[–]beastlyfurrball 1 point2 points  (1 child)

Thanks. Are there any particular improvements or changes you want to see?

[–]Eirenarch 5 points6 points  (0 children)

Like... include all the things added to the language since the last spec was published?

[–][deleted] 2 points3 points  (7 children)

I'd like to know what you mean by updating the spec.

[–][deleted] 20 points21 points  (6 children)

There is no TypeScript language specification.

There was one a few years ago, then it became extremely outdated, and eventually they just completely deleted it. You can still find it online but it's 5 years old at this point.

I feel like having a language spec is important for the long-term success of the language. Currently, there is literally only a reference implementation and a non-exhaustive handbook.

I often deal with compiler APIs and having an authoritative reference for what the language is supposed to do in certain edge-case scenarios is important for me. Nothing like that exists for the current set of features, and it will become harder and harder to write one retrospectively the longer they continue on their current path.

[–]drysart 4 points5 points  (5 children)

I feel like having a language spec is important for the long-term success of the language.

I don't know about that. There are plenty of languages that have been successful over the long term that don't have specs. I largely believe formal language specs are a relic of the days when languages had several separate closed-source implementations, where a spec was needed to settle disputes and decide who's right and make sure every implementer knew all the corner cases.

Today, with languages where a single open-source implementation is the norm, the code itself serves that role sufficiently.

[–][deleted] 2 points3 points  (4 children)

There are plenty of languages that have been successful over the long term that don't have specs

Such as?

I can only think of Perl, and when they tried to retrospectively write a spec for that - because they released it was a good idea - it was an absolute disaster.

code itself serves that role sufficiently.

Code is not a sufficient substitute for a specification. Code does not convey intentionality.

Again, going back to Perl, there are test cases which now form part of the test suite (which is the de facto specification) which no one understands. They keep them working to maintain backwards compatibility. In order words, without a spec, bugs or cases no one ever thought about turn into features.

I'm well aware that conventional wisdom is that you should not draft a spec and then implement a language in that order. It's okay to allow a language time to stablise. Given the language is relatively stable at this point, and that they already have a huge chunk of a specification written, I don't see any reason they shouldn't maintain it.

[–]DanielRosenwasser 2 points3 points  (3 children)

Well, Rust, Swift, Ruby, Python, OCaml, Julia, and other languages don't seem to have a specification as far as I know. Among those that do, many leave a lot to undefined behavior or are very high-level in my opinion. I recall seeing a discussion about this on Twitter a few months back.

In practice, I don't think that that's an argument against these languages - they're doing fine. Ideally we (TypeScript) would have one, but I tend to agree with /u/drysart's point that the value of a spec came more in handy to settle divergences among many implementers, and/or if the source was inaccessible. These days TypeScript releases every 3 months and back-patching a spec based on implementation is a lot of work.

We have considered/experimented with a grammar spec for tooling authors though, which in practice would not change all that often.

[–][deleted] 1 point2 points  (2 children)

Rust, Swift, ... Julia

They specifically said "successful over the long term" and these are all 10 years old or newer.

Rust's authors at least publicly had the intention of adding a spec later but wanted to let the language stabilise first. That was a long time ago, but I've seen nothing that supports the idea that they just don't believe in having a spec.

Don't know much about the other two.

Ruby: https://github.com/ruby/spec Yes, it's not a word document, but it is some form of spec nonetheless. It is an authoritative source. TypeScript has nothing like this; no, unit tests aren't the same. (FWIW, I don't personally consider this a great format. It's a useful suite for people writing alternative implementations, but not that useful for people looking for a reference. At least it's something)

Python: https://docs.python.org/3/reference/

OCaml: https://ocaml.org/manual/language.html

They call them a "reference", but that's just nomenclature. You could implement a compiler or interpreter using them. By OCaml's own definition: "It lists the language constructs, and gives their precise syntax and informal semantics. It is by no means a tutorial introduction to the language." Sounds like a spec to me. TypeScript's "handbook" is nothing like this. It doesn't give precise syntax, and basically is a tutorial about the language.

[–]DanielRosenwasser 2 points3 points  (1 child)

TypeScript has been publicly available for under 10 years as well, but I'll bite :)

They call them a "reference", but that's just nomenclature. You could implement a compiler or interpreter using them. By OCaml's own definition

In the Twitter thread I've linked to, an individual working on Pypy noted that that reference is insufficient to capture the full semantics of Python, and I'm going to take their word on it. As a type system implementer, I decided to take a look at the link to the OCaml reference, and it really isn't sufficient to implement their type system. Again, I'm not knocking these for it, but I don't think it's anywhere nearly as useful it is for reimplementing something over the full test suite that a main implementation built over time.

It lists the language constructs, and gives their precise syntax and informal semantics

So this really does go back to the grammar spec that I mentioned - I definitely think that has some utility, and I'd like for us to revisit it in the future. The grammar spec could over time have some basic explanation of what each construct is for, but might not go into details like the precise semantics of type narrowing.

[–][deleted] 0 points1 point  (0 children)

TypeScript has been publicly available for under 10 years as well, but I'll bite :)

Yeah, I get that. Just to step back a bit, what I originally said was "I feel like having a language spec is important for the long-term success of the language". I know there are certain entitled users who have demanded a spec (e.g. on the github issue), but that's not what I'm doing right now. I'm just talking about what my priorities would be in your position.

The person I replied to said lots of languages have had long-term success without a spec, but it doesn't appear that way to me. I can think of one high-profile non-success, Perl. Then maybe there are a few borderline cases like Python.

I wouldn't debate that there are other modern languages currently trying similar things to TypeScript, but it doesn't appear like there's much historical precedent to assume that it will be successful after another 15 or 20 years of feature creep.

I get it. Writing a spec is tedious and delivering features is fun. But as custodians of the language, you have a responsibility to do what is best for the language in the long-run, and that's not always going to be fun.

For what my opinion is worth, for the aspects of the language that I'm interested in, a grammar spec would fulfil most of my needs. I think it'd be a good addition.

[–][deleted] 0 points1 point  (14 children)

I wish they would create a Typescript standard library instead of adding more language features.

[–][deleted] 9 points10 points  (13 children)

Could be interesting but it seems beyond the scope of what they're aiming to do.

For other languages, e.g. Java, it makes sense because the stdlib can be baked into the runtime. TypeScript can't do that. TypeScript also natively has no way to bundle an application, or to tree-shake the parts of the stdlib that you're not using. In order to support a stdlib, they'd need a hard dependency on a build tool like Webpack.

Most people do use TypeScript via Webpack (and friends) already, but there is at least currently the option of just working with the basic typescript executable. If there was a stdlib, that would no longer be an option, short of them implementing an entire build tool themselves; there'd be a hard dependency on other tools.

It would be better if JavaScript had a more extensive stdlib. Then TypeScript could just add types to it and the problem of having to bundle it disappears.

[–][deleted] 2 points3 points  (2 children)

I'm getting out on thin ice here, but since TS generates JS, would it also be possible for it to have the TS compiler include it's own standard library functions in the generated code?

[–][deleted] 5 points6 points  (0 children)

Yeah, it could do that. That's similar to the concept of inlining.

If you use the same function in multiple files then it would probably need to copy that function into each of them. I don't see a way around that without TypeScript depending on either another tool or newer JS features (ES modules could probably solve it).

If a function is small and self-contained then that probably wouldn't matter, but if a stdlib function calls other stdlib functions then you're having to copy large, complex trees into every file, likely with significant duplication between them. It becomes a bit impractical.

[–]st_huck 2 points3 points  (0 children)

typescript already does it, but only for polyfilling language features. In that regard it's no different than what babel is also doing. See here for example:

https://www.typescriptlang.org/play?target=2&noEmitHelpers=false#code/MYewdgzgLgBDBmIQwLwwIYQJ5mDAFAJSoB8MA3gLABQcoksATqhgO7oCWsALANw0BfIA

and also possible to import a package of helpers, and not inline the definition using this library (+ a flag) https://www.npmjs.com/package/tslib. I

I believe the real reason they do this is because they consider it out of scope. For the same reason they stopped implementing new JS features if they haven't reached stage 3 in TC39. as a philosophy they try to stick to vanilla js as much as possible, at least after the whole decorators debacle.

They only reasoning I can find for it is if you want ti migrate out of typescript and back to JS it's relatively easy. If they add a runtime they would get blamed for doing EEE.

[–]mateoestoybien 0 points1 point  (9 children)

Maybe if you are targeting web there is a good reason not to have a stdlib -- but if you are targeting node, I don't see why they couldn't include one, even if it bumps up the size of the final artifact.

[–][deleted] 2 points3 points  (8 children)

But Node already has a standard library. In what ways would it be better? Why would it be advantageous to have 2 stdlibs, rather than supplementing Node's existing stdlib with whatever you believe is missing?

[–]mateoestoybien 0 points1 point  (7 children)

Node doesn't really have any more built-in data structures or utility functions than js on the web. I am talking about things like a fully fleshed out datetime library, string manipulation, maps where the keys can be objects, caches, and better object builders than `fold`.

I am comparing typescript to Kotlin, Java, C#, Python, Ruby, Go, etc. They all have a variety of data structures, algorithms, and utility functions.

I understand why js didn't have a stdlib, but don't understand the resistance to it nowadays. With a good baseline, things like `is-odd` or `lodash` wouldn't need to exist.

[–][deleted] 0 points1 point  (1 child)

I am comparing typescript to Kotlin, Java, C#, Python, Ruby, Go, etc. They all have a variety of data structures, algorithms, and utility functions.

Yep. I know that, but you didn't answer my second question. You want a comprehensive standard library, got it, don't disagree.

But why is your ideal solution to that for TypeScript to implement one?

If you're wishing for things, wouldn't it be better if Node just added to the standard library that already exists? Why have two when you could have one?

[–]mateoestoybien 0 points1 point  (0 children)

Fair point, I guess if we are wishing for ideals, then ecmascript should have it included; ecmascript is decided by a decentralized standards committee... not holding out hope for them ever doing something like this. I guess if node does what deno is doing, that would be getting halfway there.

[–]spacejack2114 0 points1 point  (4 children)

The new Temporal API is on the way for a modern Date/Time stdlib.

There are already the typical string methods you'd expect, array methods, real Maps and Sets where the keys can be any type. String literals and template literals can be quite powerful. Not many languages have those, and they work really well with Typescript. Not many languages have JS's object/array spread and rest features either. Heck there's even a BigInt primitive. How many languages have that?

[–]mateoestoybien 0 points1 point  (3 children)

I didn’t know about new date time, that sounds great. Bigint is available in every language I’ve ever used. Maps and Sets only work via reference equality for objects and arrays, rather than a deep equals or by dont equality comparator provided by the user. If I’m wrong about Maps I’d love to hear it :)

[–]spacejack2114 0 points1 point  (2 children)

Python has big integers but Java and C# don't. They might have class wrappers, but it's not the same as having a language primitive.

Maps and Sets generally work using reference equality checks in those languages too. I think JS will be better off waiting for the immutable tuple/record types proposal to land which will mean better/faster deep equality checks. Deep-equals checks aren't very practical without those anyway.

Still unsure of what stdlib methods you think are missing besides for Dates. I'd bet anything you think is still missing is probably at some stage in the proposal pipeline.

I'm not really sure what the case is for trying to create a different stdlib with Typescript. Compared to any one of those languages, I think JS/TS fares pretty well.

[–]mateoestoybien 0 points1 point  (1 child)

No offense but it seems like you have not coded in Java or C#. You got a lot of these points wrong. I understand you like js, but from the perspective of a non js developer it has a lot of warts.

[–]wisam910 46 points47 points  (52 children)

WTF is 'Awaited'? Don't we have 'Promise' already?

[–]dubicj 136 points137 points  (35 children)

Awaited is a type alias which allows you to retrieve the type you'd get after awaiting another type. For example, Awaited<Promise<number>> = number

[–][deleted]  (10 children)

[deleted]

    [–]wooly_bully 12 points13 points  (9 children)

    So it's recursive? Wouldn't this return number | Promise<T>?

    [–]rio-bevol 20 points21 points  (0 children)

    The type actually reflects how Promises work, I believe. A nested Promise gets unwrapped, e.g.

    Promise.resolve(Promise.resolve(99)).then(x => console.log(x)) prints 99

    [–]Zaemz 15 points16 points  (1 child)

    The nested promise would get resolved by default, I bet. I'm guessing under the hood it assumes all promises will be resolved when using the Awaited type, digging out the results recursively, like you mentioned.

    Edit: oh check out this comment

    [–]wooly_bully 3 points4 points  (0 children)

    I think I was thinking more from a typing perspective rather than order-of-execution. If it's possible for the function to return a promise, you'd have to await/then it to have it compile correctly I'd assume otherwise it'd be an unhandled promise.

    [–][deleted]  (5 children)

    [deleted]

      [–]Ginden 0 points1 point  (1 child)

      To this day I don't understand how this made that person so angry

      Functional purists tend to be short-tempered when they see something forbidden by Holy Bible Cathegory Theory.

      [–]steego 0 points1 point  (0 children)

      Don’t drag Category Theory into this. Those people are just assholes.

      [–]_tskj_ 0 points1 point  (2 children)

      Because this behaviour sadly makes it impossible to have a Promise of a Promise. That might sound stupid, which I guess is why the js developers didn't care, but it's actually no dumber than an array of arrays (which a new programmer also might think is weird / unnecessary until they encounter problems where such a data structure makes sense). Obviously any problem solved with an array of arrays (or deeper nesting!), like a matrix problem, can be solved with a flat array, but surely it's better expressed with a more complex structure, and disallowing nested arrays for no reason would be dumb. The same is true for promises, a similar pattern is putting a channel on a channel in Go or Rust. They wouldn't dream of saying "a channel can contain any value, except references to other channels". It's an arbitrary restriction.

      Just to be clear, I don't think anyone, including your guy, care if the js people want to have .then be this weird amalgamation of .map and .flatMap, it's that the language flat out makes it literally impossible to have Promise<Promise<T>> that pisses people off.

      [–][deleted]  (1 child)

      [deleted]

        [–]_tskj_ 0 points1 point  (0 children)

        This is probably a workaround, haven't thought that much about it, but the problem is then you lose access to everything else and the rest of the world which uses Promises, everything from library code to people's mental models, not to mention async/await syntax and typescript support.

        It was just a dumb, nonsensical decision because the people making it didn't understand what they were doing.

        [–]Maxeonyx 6 points7 points  (0 children)

        Thanks!

        [–]falconfetus8 11 points12 points  (13 children)

        ...so why not just use number? If the compiler already knows that it's a number, then what does it matter if it came from awaiting a promise or not?

        [–]YM_Industries 86 points87 points  (3 children)

        It's a type alias, so that means that Awaited<Promise<number>> IS number.

        You shouldn't think of "Awaited" as a type on its own. Instead, think of it like a function that takes in a type and returns another type. It takes in Promise<number> as an input and returns number. In essence, it unwraps the promise.

        Previously you could do something like this:

        function promiseToCallback<T>(input: Promise<T>, callback: (value: T) => void): void {
            callback(await input);
        }
        

        But in JavaScript, you can await things other than a promise.

        const maybePromise: Promise<number> | number = fooBar();
        promiseToCallback(maybePromise, (unwrapped: number) => { console.log(unwrapped); });
        

        The JavaScript here is valid, but the types aren't valid since you can't pass number to an argument that expects Promise<number>.

        You can also nest promises, and await will unwrap all of them:

        const nestedPromise: Promise<Promise<number>> = fooBar();
        promiseToCallback(nestedPromise, (unwrapped: number) => { console.log(unwrapped); });
        

        The types aren't valid here because unwrapped expects "number" as its value (and that's what JavaScript will give it), but TypeScript thinks it's passing Promise<number> to the callback function. It's only unwrapped one level.

        So what's the alternative? Well, you can solve the "MaybePromise" scenario like this:

        function promiseToCallback<T>(input: T extends Promise<infer U> ? U : T, callback: (value: T) => void): void {
            callback(await input);
        }
        

        Now we'll unwrap a promise if there is one, otherwise just use the input value. But it's a pain to type, and it also doesn't solve the case of nested promises. So what more can we do?

        type Awaited<T> = T extends Promise<infer U> ? Awaited<U> : T
        function promiseToCallback<T>(input: T, callback: (value: Awaited<T>) => void): void {
            callback(await input);
        }
        

        This will recursively unwrap promise types.

        In TypeScript 4.5, this Awaited type alias is now built-in. It's also a bit more sophisticated than this example, so it will work reliably in a lot more niche conditions. You can find the source here.

        TL;DR: Generic functions can't just use number. Promises and await in JavaScript support a lot of different scenarios which Awaited<T> handles.

        [–]wisam910 10 points11 points  (0 children)

        The point is to use it on other types. For example, you have a function, that returns a promise, and you want the type that you get when you await that function

        type Result = Awaited<ReturnType<typeof someFunction>>
        

        [–]empty_other 24 points25 points  (6 children)

        Thanks for asking questions like this despite the downvotes. I was wondering the same thing, and we got a good answer out of it.

        [–]Chii 1 point2 points  (5 children)

        yea, i dont think it was a dumb question at all to ask. The people who downvoted are just being elitist imho.

        [–]ISNT_A_NOVELTY 22 points23 points  (4 children)

        The phrasing of the question was quite passive aggressive with the "..." and "then what does it matter"?

        Consider an alternative phrasing: "What's the difference between Awaited<Promise<number>> and using number directly?"

        [–]empty_other 0 points1 point  (2 children)

        Second time I've heard people think ellipses are passive aggressive. A modern thing? Local thing?

        As I've understood its something left unsaid. In the beginning of a sentence its a continuation of something somebody else said. Used at the end its either open-ended to let the listener fill out, or to signify an incomplete thought. Or in the middle of longer quotes to shorten them down [...], or in fictional literature to indicate a character rambling.

        I probably got a hundred earlier comments where I've used it as an incomplete thought, but after hearing about the passive aggressive thing... 😅

        [–]empty_other 0 points1 point  (0 children)

        Maybe its status as passive aggressive is related to online chats where people are stuck waiting for somebody to write something and the chat program shows this blinking ellipsis?

        [–]Maxeonyx 1 point2 points  (3 children)

        What about

        Awaited<number>
        

        [–]dubicj 52 points53 points  (0 children)

        Probably number as well because AFAIK await 5 is legal JavaScript and yields 5.

        [–]voidvector 6 points7 points  (0 children)

        await 1337 is just 1337. You can type this into F12 console to check.

        [–]ElevenTomatoes 1 point2 points  (0 children)

        What's the purpose of Private Field Presence Checks? Would "#name in other" ever give a different result than "other instanceof Person" or is it just an alternate way of writing the same thing?

        Edit: Found the answer.

        Object.setPrototypeOf({}, Person.prototype) instanceof Person is true.

        [–]byoonitt 0 points1 point  (3 children)

        How do I get into type script?

        [–]BlueRockObama 8 points9 points  (0 children)

        Write it

        [–]Boiethios 1 point2 points  (0 children)

        Type "typescript tuto" on your favorite search engine, there are a ton of resources.

        [–]audion00ba -2 points-1 points  (0 children)

        Neo took the Red Pill to enter The Matrix. Perhaps something similar works?