[HN Gopher] Extreme explorations of TypeScript's type system
       ___________________________________________________________________
        
       Extreme explorations of TypeScript's type system
        
       Author : joshuakgoldberg
       Score  : 80 points
       Date   : 2022-06-27 18:13 UTC (4 hours ago)
        
 (HTM) web link (www.learningtypescript.com)
 (TXT) w3m dump (www.learningtypescript.com)
        
       | evolveyourmind wrote:
       | Some other type-only TS projects:
       | 
       | - RegExp matching through types: https://github.com/desi-
       | ivanov/ts-regexp
       | 
       | - Lambda calculus through types: https://github.com/desi-
       | ivanov/ts-lambda-calc
       | 
       | - Brainfuck through types: https://github.com/susisu/typefuck
        
         | joshuakgoldberg wrote:
         | These are wonderful, thank you!
        
       | joshuakgoldberg wrote:
       | TypeScript's type system is Turing Complete: meaning it has
       | conditional branching (conditional types) and works with an
       | arbitrary huge amount of memory. As a result, you can use the
       | type system as its own programming language complete with
       | variables, functions, and recursion. This blog post is a starting
       | list of a bunch of the shenanigans TypeScript developers have
       | pushed the type system to be able to do.
        
         | tadfisher wrote:
         | It also makes static checking undecidable in pathological
         | cases. I know Idris has this problem as well; they work around
         | it by skipping partial functions in types, so you end up with
         | "total" and "partial" programs where only the former are
         | completely typechecked proofs.
        
         | Gehinnn wrote:
         | That's not the meaning of turing completeness, just an
         | implication of the space hierarchy theorems. There are systems
         | that also have branching and unbounded memory, but are not
         | turing complete. Context free grammars for example.
         | 
         | Turing completeness means for TS that for every computable
         | function, there is a TS type that can compute that function (if
         | TS wouldn't limit the recursion depth).
        
       | tobr wrote:
       | > You have to wonder whether you could implement TypeScript
       | itself in that language...
       | 
       | I also wonder if you could compile TypeScript to TypeScript
       | types? After all, you want your type manipulation code to be
       | typesafe.
        
       | adamddev1 wrote:
       | Are there (m)any other languages with type systems as flexible
       | and powerful as Typescript's?
        
         | cjbgkagh wrote:
         | I would guess that all languages with Turing complete
         | typesystems would be technically equally powerful. I think
         | Typescript supports more useful behaviors out of the box.
        
           | dllthomas wrote:
           | Equally computationally powerful, but not necessarily equally
           | powerful as type systems. "X is Turing complete" means that
           | it can compute the same set of computations as any other
           | Turing complete system, but it does not mean that it can
           | accept inputs and produce results in an encoding relevant to
           | what you're trying to do.
        
         | [deleted]
        
         | yulaow wrote:
         | I believe that typescript type system is so flexible, powerful
         | and complex just because it had to be adapted and built around
         | the shortcomings and limitation of javascript. It makes no
         | sense to have something like it if you build a language from
         | the ground up (or if you could just scrap backward
         | compatibility in a bad designed one)
        
         | valenterry wrote:
         | Yes there are. For instance Idris (https://www.idris-lang.org/)
         | has a way more powerful typesystem than Typescript.
         | 
         | If you are looking for more practical and less academic
         | languages, then Scala would be one of the languages that
         | technically has a more powerful/generalized typesystem but at
         | the same time is harder to use compared to Typescript's and
         | cannot do some things that Typescript can do.
        
       | singaporecode wrote:
       | This was a good guide on TypeScript that I found online:
       | https://ashok-khanna.medium.com/introduction-to-typescript-c...
        
       | dllthomas wrote:
       | > If you do find a need to use type operations, please--for the
       | sake of any developer who has to read your code, including a
       | future you--try to keep them to a minimum if possible. Use
       | readable names that help readers understand the code as they read
       | it. Leave descriptive comments for anything you think future
       | readers might struggle with.
       | 
       | Also, as you start getting complicated logic in your types, you
       | need to test your types; make sure they admit things they should
       | admit and reject things that they should reject. Ideally these
       | tests can also serve some role as examples for your
       | documentation.
        
         | acemarke wrote:
         | We do a _lot_ of this in the Redux library repos (examples: [0]
         | [1] [2] ). We have some incredibly complicated types in our
         | libraries, and we have a bunch of type tests to confirm
         | expected behavior.
         | 
         | Generally, these can just be some TS files that get compiled
         | with `tsc`, but it helps to have a bunch of type-level
         | assertions about expected types.
         | 
         | I actually recently gave a talk on "Lessons Learned Maintaining
         | TS Libraries" [3], and had a couple slides covering the value
         | of type tests and some techniques.
         | 
         | [0] Redux Toolkit's `createSlice`:
         | https://github.com/reduxjs/redux-toolkit/blob/9e24958e6146cd...
         | 
         | [1] Reselect's `createSelector`:
         | https://github.com/reduxjs/reselect/blob/f53eb41d76da0ea5897...
         | 
         | [2] React-Redux's `connect`: https://github.com/reduxjs/react-
         | redux/blob/720f0ba79236cdc3...
         | 
         | [3] https://blog.isquaredsoftware.com/2022/05/presentations-
         | ts-l...
        
         | brundolf wrote:
         | @ts-expect-error is useful for this
        
         | yulaow wrote:
         | I lost it when at my previous job I found a 20 multiline super
         | complex type defined by another dev, asked him to describe it
         | because I was in a tight deadline and had not time to parse
         | whatever he was defining. He starts with "it's pretty simple"
         | and then used like 10 minutes to describe me what he meant
         | while writing on paper the various pieces getting confused two
         | times. At the end of the day it could be simplified in a one
         | line union type of a few strings which would cover 99% of the
         | usecases and the other 1% was something we had never used and
         | would never use.
         | 
         | I really wish people would focus more on keeping types as
         | simple as possible instead of using that complexity just
         | because the language allowed it.
        
       | fishtoaster wrote:
       | You can do some truly silly things with sufficiently ridiculous
       | uses of typescript. I built a typecheck-time spell checker[0] in
       | it such that:                 import { ValidWords } from
       | "./spellcheck";              // Typechecks cleanly:       const
       | result: ValidWords<"the quick brown fox."> = "valid";
       | // Throws a type error       const result: ValidWords<"the qxick
       | brown fox."> = "valid";
       | 
       | [0] https://github.com/kkuchta/TSpell
        
         | idontwantthis wrote:
         | So does spellcheck contain a gigantic array of the English
         | language?
        
           | jjtheblunt wrote:
           | the source code to answer your question is directly above
           | your question
        
           | xdennis wrote:
           | Yes. It looks like this:                   export type
           | ValidWords<T extends string> = T extends ""           ?
           | "valid"           : T extends `the${infer Rest}` | `of${infer
           | Rest}` | `and${infer Rest}` | ...
        
         | joshuakgoldberg wrote:
         | This is great!
        
       | theogravity wrote:
       | This is a great list. I feel I'm only scratching the surface when
       | it comes to Typescript, and it would be awesome to have a place
       | where we can see advanced examples of Typescript usage like this.
       | 
       | I've seen many projects where the typing is done so well that it
       | can infer and include all the data I've fed into the TS-defined
       | functions / classes, which is great for IDE autocompletion.
        
         | HyperSane wrote:
         | What are some of the projects have done typing that well?
        
           | theogravity wrote:
           | If you're a GraphQL developer, Pothos is the best example -
           | all your user-defined types just fits in it like a glove 99%
           | of the time. It definitely makes the most use of TS generics.
           | 
           | https://pothos-graphql.dev/
           | 
           | (I'm a bit sleepy, so this is the main one I can think of at
           | the moment that I really enjoy using.)
        
           | sir_pepe wrote:
           | Not really a "project", but TypeScript's own type definitions
           | for the DOM are a great example. Eg. document.createElement
           | returns different subtypes of HTMLElement depending on the
           | string argument (HTML tag name) it gets called with.
        
           | girvo wrote:
           | purify-ts is my favourite currently.
        
       | kevingadd wrote:
       | When doing fancy things with typescript types, be really careful
       | - it's possible to accidentally construct typescript types that
       | will increase your tsc compile times by _multiple seconds_ and
       | the tooling for troubleshooting this is nonexistent. A tiny
       | change to one codebase I work on made compile times go from 300ms
       | to something like 7 seconds and it took me something like 14
       | hours of grepping and manually bisecting source code to find the
       | cause - tsc was O(N * N * N) trying all possible types for a
       | string literal to determine whether any of them were valid
       | matches, and someone had defined a _very_ fancy string literal
       | type.
       | 
       | When this happens, typescript language integration (like in vs
       | code or sublime text) will suddenly fall over and stop working
       | correctly, and it'll be near impossible to figure that out too.
       | 
       | Our build uses rollup to invoke tsc and as it happens their
       | profiling system doesn't actually measure how long tsc takes to
       | run - the time is unaccounted :) So in general, be aware that
       | 'typescript is taking a long time to compile' is a blind spot for
       | this whole ecosystem and if you hit it you're going to have to
       | work hard to fix it.
        
       | sir_pepe wrote:
       | To try and limit one's use of operations on types, as suggested
       | in the article, is not really great advice in my opinion. Sure,
       | you would not want to actually implement and use a VM in types,
       | but distilling rules about a program into types and then deriving
       | the actual interfaces and signatures from those rules with
       | operations on types? That's quite powerful.
       | 
       | TypeScript's type annotations are really a DSL embedded into
       | JavaScript. And they can, and, depending on the problem at hand,
       | _should_ be treated as such.
        
         | vore wrote:
         | I think it depends how far you go: if you start encoding rules
         | into the type system that are undecidable then you can quickly
         | run into trouble.
        
           | tevon wrote:
           | To me its about readability. I agree that operations on types
           | generally are a good thing because I find them more readable.
           | Not using operations I've found leads to many very specific
           | types which are hard to read and understand from the devs
           | perspective.
        
         | lf-non wrote:
         | Yes, while that is true, TS errors can sometimes be really
         | clunky. And sans a debugger, it is not uncommon for me to be
         | spending stretches of 20-30 mins almost every week trying to
         | unravel complex type errors that span multiple pages. I
         | recently traced a very weird error to TS changing what keyof
         | never evaluates to in a minor version.
         | 
         | So yeah, using discriminated unions, branded types, mapped
         | types etc. in moderation can substantially reduce the surface
         | area of errors - more so than other mainstream nominally typed
         | languages. However, trying to model and prevent every invalid
         | state at type level can lead to a serious drain in
         | productivity. And, I am not really sure how to draw a line
         | between.
        
         | jasonkillian wrote:
         | > TypeScript's type annotations are really a DSL embedded into
         | JavaScript. And they can, and, depending on the problem at
         | hand, should be treated as such.
         | 
         | I think this is the key. If treated as you describe, meaning
         | the advanced types are well-written, well-documented, and well
         | unit-tested as if they are "true" code, then using them
         | shouldn't be too much of an issue.
         | 
         | However, I think people often just assume that the types aren't
         | "real" code and thus the normal concepts of good software
         | engineering don't apply and type monstrosities which nobody can
         | understand result.
         | 
         | Imagine if this code[0] wasn't well-documented, fairly clearly
         | written, and also tested. It would definitely be a liability in
         | a codebase.
         | 
         | In addition, the rules of how advanced TypeScript concepts work
         | can be quite nuanced and not always extremely well defined, so
         | you can end up in situations where nobody even _really_
         | understands why some crazy type works.
         | 
         | [0]: https://github.com/sindresorhus/type-
         | fest/blob/2f418dbbb6182...
        
       ___________________________________________________________________
       (page generated 2022-06-27 23:00 UTC)