[HN Gopher] Learn how to unleash the full potential of the type ... ___________________________________________________________________ Learn how to unleash the full potential of the type system of TypeScript Author : NeekGerd Score : 296 points Date : 2022-09-20 15:31 UTC (7 hours ago) (HTM) web link (type-level-typescript.com) (TXT) w3m dump (type-level-typescript.com) | bwestergard wrote: | This is fantastic! I've often had to advise coworkers to read | documentation for OCaml or Rust to learn idiomatic, functional, | statically typed programming. It's great to see a Typescript | specific resource with exercises. | mikessoft_gmail wrote: | downvoteme77221 wrote: | my HTML blog only has 30 lines of JS. i didn't need a framework | and i certainly don't need types. grumble grumble web should just | be simple html javascript grumble serverside render in my pet | language grumble /s | cutler wrote: | Add another 1 to your army. We all know how Typelevel Scala | ended. | jaredcwhite wrote: | I feel seen. | Rapzid wrote: | I wish this issue would get addressed: | https://github.com/microsoft/vscode/issues/94679 | | Showing fully resolved types in Intellisense would be the single | largest usability enhancement they could make for me right now.. | uup wrote: | The Id<T> type defined in the first comment seems like a pretty | good, albeit ideally unnecessary, workaround. | Rapzid wrote: | Those hacks work but in practice I wouldn't classify them as | "good". You end up having to look at a ton of types including | in library code like React and etc that can be quite complex. | Having to stop and try to wrap those on the fly is terrible | ergonomics. | lf-non wrote: | Yeah, this is a recurring pain. | | I know the universe at large has moved away from eclipse, but I | loved their rich tooltips where you had nice structured | representation (not just a blob of text from lsp) and could | click through and navigate the type hierarchy. | 9dev wrote: | Try any IntelliJ IDE, they've got that. I can't understand | why anyone would want to use VS Code with a Typescript | project voluntarily... | Myrmornis wrote: | This looks fantastic, not just for people learning Typescript, | but I'd think it would be useful (when completed) as an | introduction to generics and type-level thinking etc for lots of | newcomers to those areas. | theteapot wrote: | Didn't know `Expect` existed. Can't find it docs? | gvergnaud wrote: | It's actually _not_ in the standard library, but you can write | it yourself: | | type Expect<T extends true> = T; | | `T extends true` puts a type constraint on the parameter, which | then needs to be assignable to the literal type `true` to type- | check. | theteapot wrote: | OK cool, now what about `Equal` :). | triyambakam wrote: | type Equal<A, B> = A extends B ? (B extends A ? true : | false) : false; | guggleet wrote: | Seems like a good start, but there are a lot of interesting | offerings in the introduction that really don't exist in the | content so far. | | Maybe finishing one of the more advanced chapters would be enough | to lure people who are more experienced to check back on progress | / pay / whatever you want traffic for. | AkshatJ27 wrote: | This reminded me of the Typescript type-level Quine. | https://youtu.be/UzE6d1ueT_E | theteapot wrote: | Course is probably great, but I find it really weird and | unnecessary to describe Typescript type system as Turing | complete. Who cares? | lolinder wrote: | People who are trying to statically describe the behavior of | highly dynamic JavaScript code. | theteapot wrote: | Yeah but how is Turing completeness directly relevant to | that? Article doesn't seem to explain, just says "Turing | Complete" in the title. Again, so what? I suppose it's | somewhat indirectly vaguely reassuring. | | P.S. You might like this http://beza1e1.tuxen.de/articles/acc | identally_turing_complet... | simlevesque wrote: | Wow I think I know a lot of Typescript but I'll have to go | through it because I'm always asked for ressources to get started | and this one seem great. | | I also recommend type-challenges: https://github.com/type- | challenges/type-challenges | | It works great with the VSCode extension. | kbr- wrote: | Is there any place I could report issues or ask questions about | the course other than Twitter? | | If the author is reading this, the proposed solution to the | `merge` challenge is: function merge<A, B>(a: A, | b: B): A & B { return { ...a, ...b }; } | | That's the "obvious" solution, but it means that the following | type-checks: const a: number = 1; const b: | number = 2; const c: number = merge(a, b); | | That's not good. It shouldn't type check because the following: | const d: number = { ...a, ...b }; | | does not type check. | | And I don't know how to express the correct solution (i.e. where | we actually assert that A and B are object types). | | Also, looking forward to further chapters. | KrishnaShripad wrote: | > And I don't know how to express the correct solution (i.e. | where we actually assert that A and B are object types). | | You can do this: function merge< A | extends Record<string, unknown>, B extends | Record<string, unknown> >(a: A, b: B): A & B { | return { ...a, ...b } } const result = merge({ | a: 1 }, { b: 2 }) | ttymck wrote: | Why Record and not object? | lediur wrote: | Linters for TypeScript recommend using `Record<string, | any>` instead of `object`, since using the `object` type is | misleading and can make it harder to use as intended. | | See: | | - https://typescript-eslint.io/rules/ban-types/ | | - https://github.com/typescript-eslint/typescript- | eslint/issue... | | - https://github.com/microsoft/TypeScript/issues/21732 | | - https://github.com/microsoft/TypeScript/pull/50666 | davidatbu wrote: | Avoid the Object and {} types, as they mean 'any non- | nullish value'. This is a point of confusion for | many developers, who think it means 'any object type'. | | https://github.com/typescript-eslint/typescript- | eslint/blob/... | KrishnaShripad wrote: | Because you only want to merge two objects that have keys | with string type. "object" is represented as Record<any, | any>. That would mean, you can use any type as key. Here is | an example: function merge< A | extends object, B extends object >(a: A, b: | B): A & B { return { ...a, ...b } } | const result = merge(() => {}, () => {}) // should fail! | const anotherResult = merge([1, 2], [3, 4]) // should fail! | | Which is obviously not what you want. | | This table here gives you a good overview of differences | between object and Record<string, unknown>: https://www.red | dit.com/r/typescript/comments/tq3m4f/the_diff... | brundolf wrote: | Yeah, TypeScript gets funky around the boundary of "things that | can only be objects", because JavaScript itself gets funky | around "what is an object" | | _Technically_ TypeScript "object types" only describe | _properties_ of some value. And in JavaScript... arrays have | properties, and primitives have properties. Arrays even have a | prototype object and can be indexed with string keys. So... {} | doesn 't actually mean "any object", it means "any value" | | At its boundaries, TypeScript has blind-spots that can't | realistically be made totally sound. So the best way to think | of it is as a 90% solution to type-safety (which is still very | helpful!) | agentultra wrote: | Nice course, and nice site. | | Although, as a Haskell developer, I am curious what type system | TS is using (System F? Intuitionist? etc) and what limitations | one can expect. Aside from the syntax of TS being what it is, | what are the trade-offs and limitations? | | I was under the impression, and this was years ago -- things are | probably different now?, that TS's type system wasn't sound (in | the mathematical logic sense). | 323 wrote: | Non-soundness is sort of a feature, it lets you force your way | through and just say "trust me, this is a Thing" when it's just | hard (or impossible) to make TypeScript see that. In practice, | you can write large code bases where you only need to do this | every 1000 lines or so. Not ideal, but better than no typing. | agentultra wrote: | Is it fair to say that the limitation is that the type | checker can admit programs that are not type correct? | 323 wrote: | Yes. For example it has the "any" type which will bypass | any sort of type check. | | But I think it's more nuanced. Depends of what you mean by | type correct. Even in Haskell you can override the compiler | and say "trust me on this". | jakear wrote: | Any isn't required. The go-to example of unsoundness is | the Cat[] ref that you alias as an Animal[], append a Dog | to, then map over the original ref calling 'meow()' on | each entry. | spion wrote: | You can make "believe_me" assertions, which is incredibly | useful when writing advanced (metaprogramming heavy) | library code. The idea is to try and contain / and heavily | test the small "unsafe" library part and isolate it from | the rest of the code, then enjoy the advanced type | transformations and checks in the "normal" application | code. | | For example, an SQL query builder library may internally do | unchecked assertions about the type of the result row that | a query transformation would produce (e.g. group_by), | however assuming that part is correct, all application code | using the query builder's group_by method would benefit | from the correct row types being produced by `group_by` | which can then be matched against the rest of the | application code. | diroussel wrote: | Yea, because typescript is trying to model what can be done | in JavaScript, which is a very permisssive runtime. | Tade0 wrote: | That's by design, considering that its original purpose was | to introduce static typing in JS codebases. | AaronFriel wrote: | I don't believe it is using any type system described outside | of the TypeScript compiler. The goal of the system is to | accurately describe real-world JavaScript programs and | semantics, not to introduce a formalization that existed | elsewhere and impose it on JS. | | As a consequence, it has aspects of structural types, dependent | types, type narrowing, and myriad other features that exist | solely to model real-world JavaScript. | | As far as soundness: it's not a goal of the type system. | https://www.typescriptlang.org/docs/handbook/type-compatibil... | Ayc0 wrote: | I love the content!! | d4mi3n wrote: | I'm always impressed at how much the type system in TS is capable | of. The provided examples remind me of what I'd expect in | something like Rust; it brings me joy that we can do this sort of | stuff in our frontend code and tooling these days. | | We have come far. | danellis wrote: | I'm currently working on a project that uses both Typescript | and Scala. The overlap in concepts is rather useful when | switching contexts. | kaladin_1 wrote: | Just came here to say that this is really nice. Fantastic way to | play with Typescript Types even for people with decent knowledge | of Typescript as I would consider myself. | | Particularly enjoy the confetti :) | nonethewiser wrote: | This is a really nice website. | willthefirst wrote: | Great content. Give it me to me as a daily-dose newsletter :) | HellsMaddy wrote: | This is great. Can you please create an email list where we can | sign up to be notified when new chapters are available? If I | follow you on Twitter, I will invariably miss any announcements. | amadeuspagel wrote: | Looks cool. I like how it gives immidiate feedback, but doesn't | feel constraining or arbitrary. Is there are more basic tutorial | in a similar style? | cercatrova wrote: | Speaking of types, what are your thoughts on fp-ts if you've used | it? It brings functional programming concepts like in Haskell | such as monads into TypeScript. | SpikeMeister wrote: | If you stick to a few useful types like `Option` and | `Either`/`TaskEither` you can get a lot of value out of it when | writing server side code, particularly when combined with `io- | ts` for safely parsing data. | | If you go all in and use every utility it provides to write | super succint FP code, it can get pretty unreadable. | NTARelix wrote: | I haven't used fp-ts directly, but I use an adjacent package | that declares fp-ts as a peer dependency: io-ts. I've almost | exclusively for easier type management during deserialization. | In vanilla TypeScript I would have defined an interface and a | user-defined type guard to handle deserialization: | | interface ClientMessage { content: string } | | function isClientMessage(thing: unknown): thing is | ClientMessage { return thing !== null && typeof thing === | 'object' && typeof thing.content === 'string' } | | expect(isClientMessage('nope')).toBeFalse() | | expect(isClientMessage({ content: 'yup' })).toBeTrue() | | but user-defined type guards basically duplicate the interface, | are prone to error, and can be very verbose. io-ts solves this | by creating a run-time schema from which build-time types can | be inferred, giving you both an interface and an automatically | generated type guard: | | import { string, type } from 'io-ts' | | const ClientMessage = type({ content: string }) | | expect(ClientMessage.is('nope')).toBeFalse() | | expect(ClientMessage.is({ content: 'yup' })).toBeTrue() | | Very nifty for my client/server monorepo using Yarn workspaces | where the client and server message types are basically just a | union of interfaces (of various complexity) defined in io-ts. | Then I can just: | | ws.on('message', msg => { if | (ClientMessage.is(msg)) { // fullfill client's | request } else { // handle invalid | request } | | }) | | Only thing missing is additional validation, which I think can | be achieved with more complicated codec definitions in io-ts. | seer wrote: | haven't used it myself but other teams at the company I work | for have tried with mixed results. | | It's very opinionated about the way you structure your code and | basically makes anything thats not fully fp-ts hard to | integrate, and also is quite hard for general JS people to wrap | their head around. | | It's been designed by FP people for FP people and if there are | some on your team who are not fully on board or are just | starting to learn FP - expect lots of friction. | | At my company it was mostly scala coders and "cats" lovers | (category theory stuff lib for scala) mixed in with regular | nodejs devs and I could sense a lot of animosity around fp-ts | and its use. | | But on a more practical note, the more they converted their | codebase to fp-ts the more they reported massive compile time | slowness. Like it would start to take minutes to compile their | relatively isolated and straight forward services. | | From what I gathered, if you want to go fp-ts its just too much | friction and you're much better off picking up a language | designed from the bottom up for that - scala / ocaml / elixr / | etc. | | To be honest once I've been comfortable enough with the more | advanced TS features, you can write plain old javascript in a | very functional style, and thats actually pretty great, | especially if you throw date-fns, lodash/fp or ramda into the | mix, and it remains largely approachable to people outside of | FP and you can easily integrate external libs. | cercatrova wrote: | That sounds about what I've expected. Frankly in a TS | codebase with many other devs that are not versed in FP, I | wouldn't want to bring in a pure FP library because it, like | you said, needs everyone to understand the "meta-language" of | FP so to speak, such as how monads work, not having raw side | effects, mutation etc. | | Ramda et al seem like a good compromise. Looking through its | docs though, doesn't JS have a lot of this stuff covered? ie | filter, map, reduce etc. What new stuff is it bringing in | that covers say the 90% of most use cases? | rockyj wrote: | Totally agree. I wrote my thoughts and experiences on FP-TS | and maybe those csn help -https://rockyj.in/2022/03/24/fun- | with-composition-2 | | IMHO, functional TS is great with ramda, currying etc. and | solves a lot of problems nicely. See also https://mostly- | adequate.gitbook.io/mostly-adequate-guide/ch0... | gherkinnn wrote: | fp-ts [0] and the accompanying io-ts [1] are very well designed | and neatly implemented. I'd consider them the reference for all | things FP in Typescript. | | In practice though I find that they don't mesh well with the | language and ecosystem at large. Using them in a | React/Vue/Whatever app will catch you at every step, as neither | the language nor the frameworks have these principles at their | core. It takes a lot of effort to escape from their | gravitational pull. Using Zod [2] for your parsing needs and | strict TS settings for the rest feel more natural. | | It could work in a framework-agnostic backend or logic-heavy | codebase where the majority of the devs are in to FP and really | want / have to use Typescript. | | 0 - https://gcanti.github.io/fp-ts/ | | 1 - https://gcanti.github.io/io-ts/ | | 2 - https://zod.dev | gitowiec wrote: | Hi n I have a question. I will go as simple and short as | possible. I joined a small team working on the internal invoicing | tool. Backend is Spring. Front-end is ExtJS used for me in very | peculiar way. It emulates Java classes, there are Ext.define | declarations with FQN names eg: | "com.projectName.ds.Board.ui.extjs" (as string, casing important) | Then in the code this class is instantiated by its FQN but used | as identifier eg: var Board = new | com.projectName.ds.Board.ui.extjs(); There are also a lot of FQNs | with short namespaces, different are associated with business | short names and other like Dc, Ds, Frame belong to code | architecture domain (data controller, data store, a frame on the | screen). How I could use typescript to improve developer | experience here? I'm from the react world, I programmed 4 years | only in typescript, react, node and mongo. Thanks! | classified wrote: | Is TypeScript's type system Turing-complete? | 9dev wrote: | Why yes it is: | https://github.com/microsoft/TypeScript/issues/14833 | cogman10 wrote: | A great idea. Now, everyone that learns this stuff, show some | restraint! | | The drawback of a powerful type system is you can very easily get | yourself into a type complexity mudhole. Nothing worse than | trying to call a method where a simple `Foo` object would do but | instead you've defined 60 character definition of `Foo` | capabilities in the type system in the method signature. | | Less is more. | tobyhinloopen wrote: | True that. | | Once types get so complex, I've no idea what's going wrong. | | Today I had code running fine but throwing errors all over the | place because some deeply nested type mismatch between two | libraries. | | I just any'd it... i aint got no time for that shit | marcosdumay wrote: | The types in your code are just as designed just like any other | aspect of it. It's not a matter of restraint, it's a matter of | doing things on the correct way. | dllthomas wrote: | So less can be more but more can also be more, more or less? | primitivesuave wrote: | Second this. By the end of a progressive multi-year TS | migration at my last company, we were refactoring | `HTTPRequest<GetRequestBody<IncrementUpdate>>` back into its JS | ancestor `HTTPGetRequest`. | johnfn wrote: | This is so true. I've been thinking recently that in the same | way that "use boring technology" is a pretty well-known | concept, so should "use boring types" enter the collective | conscious. Type operators are exciting and flashy, but I found | that using them too much leads to brittle and confusing types. | Saving them for a last resort tends to be the right strategy. | Often there's an extremely dumb way to write your types that | works just as well - maybe even better :) | arberx wrote: | There are only 3 chapters so far... | tunesmith wrote: | Might as well ask here. On our teams, we have the occasional | developer that is insistent on using Typescript in an OO fashion. | This has always struck me as square peg round hole. Even though I | come from an OO background, Typescript strict settings really | seem to push me in a direction of using interfaces and types for | type signatures, and almost never classes, subclasses, | instantiated objects. I don't have a very good answer for "yeah, | but what about dependency injection"? though. Any thoughts from | anyone? | WHATDOESIT wrote: | An ES module is encapsulated enough. You can dependency inject | with them as you wish. It's like having a class, no need for a | class inside a class. | | The biggest argument is that my functional-ish code is always | 3x shorter with the same features, though. | bilalq wrote: | This. Creating classes to wrap dependencies is a pattern only | needed because of language limitations. With JS/TS, you can | mock at the import statement level, so no need to twist your | code to abstract away importing. | | Also, even if you didn't want to mock that way, you can get | dependency injection with functions just by taking a | parameter for a dependency. If dependency injection is the | only reason you have to use a class, you probably shouldn't | use a class. | spion wrote: | Request-scoped DI (as seen in ASP.NET MVC) is great on the | backend for servicing requests. You can ask for e.g. a | class representing the current user information to be | injected anywhere, or to keep track of request-associated | state like opentelemetry spans, or a transaction, etc. The | alternative is to pass the user information class or | transaction to all other services, which can be annoying | | Its rarely seen in the ecosystem as a solution, | unfortunately (everyone is passing all arguments all the | time), but its one of the rare places where this is still | useful. I've had bad experience with the alternative | (continuation local storage) and its not nearly as elegant. | depaulagu wrote: | How do you dependency inject a ES module? | diroussel wrote: | I get this question sometimes from a developer new to my team | asking if it's ok to add OOP code since most of the existing | code is just functions. | | My view on that is that it's ok to use OOP and define classes | if you are really defining an OOP style object. Back in the 90s | is was taught that an object has identify, state and behaviour. | So you you don't have all three, it's not really an object in | the OOP style. | | Looking at it through this lens helps make it clearer when you | should add classes or just stick to function and closures. | spion wrote: | I'll give a practical, non-philosophical answer. | | Indeed, if you want to use emitDecoratorMetadata for automatic | dependency injection, you should use classes. If the library | itself takes advantage (again likely due to decorators) of | classes e.g. https://typegraphql.com/docs/getting-started.html | then yes, classes are again a fine choice. | | The general answer is that they're useful when the type also | needs to have a run-time representation (and metadata). | Otherwise, not really. | YoannMoinet wrote: | I particularly love the interactivity of the training. So | polished. | | Can't get enough of the fireworks! | 323 wrote: | One think I struggled a lot with until I got it is that the | TypeScript types and the JavaScript code live in totally separate | universes, and you cannot cross from the type world to JavaScript | values because the types are erased when transpilling - meaning | they can't leave any trace. | | This means that it's impossible to write this function: | function isStringType<T>(): boolean { return ... } | const IS_STRING: boolean = isStringType<string>(); | | At best you can do something like this, which is inconvenient for | more complex cases: function isStringType<T, | IsString extends boolean = T extends string ? true : | false>(isString: IsString): boolean { return isString } | const IS_STRING_1: boolean = isStringType<string>(true); // | compiles const IS_STRING_2: boolean = | isStringType<string>(false); // type error | | You basically need to pass the actual result that you want in and | just get a type error if you pass in the wrong one. Still better | than nothing. | | Link if you want to play with it online: | https://www.typescriptlang.org/play?#code/GYVwdgxgLglg9mABDA... | | Put another way, you can't do reflection with TypeScript. | | You can write that function in C++ templates, and I naively | assumed that it's possible in TypeScript too, since from my | observations TypeScript allows complex typing to be expressed | easier in general than C++. | rideg wrote: | You can use type guards for something similar: | https://www.typescriptlang.org/docs/handbook/advanced-types.... | 323 wrote: | Keyword similar. I use type guards for other things (like | typing JSON responses), but they can't solve this particular | problem. | brundolf wrote: | What they were getting at is that you can't observe T itself, | only values passed in as type T | [deleted] | iainmerrick wrote: | I think that's actually a positive feature of TypeScript -- a | useful limitation. Reflection is generally a bad idea and it's | good to be forced to do without it. | | It is a bit annoying sometimes that you can't have overloaded | functions with different types, but in that case you can | usually just give the overloads different names, and usually | that's better for readability anyway. (Or if you really want | to, write one function and use JS reflection to do the | overloading manually) (but you really don't!) | | Here's an interesting discussion of the overloading question in | Swift: https://belkadan.com/blog/2021/08/Swift-Regret-Type- | based-Ov... | brundolf wrote: | Wanted to note that you can do function signature overloading | in typescript- you just have to have a single function at the | bottom that encapsulates all the different signatures and | then branches its logic dynamically based on the values it's | given: | https://stackoverflow.com/questions/13212625/typescript- | func... | | I actually think this is a super cool and elegant way to do | overloading | melony wrote: | Does TypeScript support dynamic dispatch? JS by nature is | dynamically typed you should be able to introspect at get the | type at runtime. | davidatbu wrote: | You might be interested in DeepKit[0]. In short, it enables | introspection/reflection of typescript types at runtime, and | builds off of that to do super interesting things like an ORM, | an API framework, ... etc. | | [0] https://deepkit.io/ | 323 wrote: | Thanks, their @deepkit/type is exactly what I would need, but | it seems they do that by a TypeScript plugin, and I'm in an | esbuild setup which completely bypasses TypeScript. | | But I will check if maybe I can use DeepKit to auto-generate | files with the reflection info I need as a separate build | step. | likeclockwork wrote: | That boundary is why I have a hard time taking TypeScript | seriously. A type system that doesn't participate in code | generation is what.. just for linting and documentation | basically? Is that what we are become? Is that all people think | a type system is good for? | | Worse there is one value that is both a user-definable | TypeScript type and a JS value. ___________________________________________________________________ (page generated 2022-09-20 23:00 UTC)