[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)