5 Things They’ll NEVER Add To TypeScript

Поділитися
Вставка
  • Опубліковано 1 лис 2024

КОМЕНТАРІ • 74

  • @feldinho
    @feldinho Місяць тому +36

    I followed the whole throws conversation on github and I'm pretty disappointed with the arguments for not implementing it:
    1) Java has it and people use it wrong - Java doesn't have inference, leading to many functions mindlessly having `throws Exception` by default. That wouldn't happen in TS.
    2) You can always get exceptions which are not declared - Irrelevant. Those are always a possibility. Having domain errors in the API would be useful even with no guarantees.
    3) Nobody uses the @throws in jsdoc - No popular tool make use of it when you use it. If using it provides no value, people won't use it. That's a flawed metric.
    4) You can always return domain errors as values - Not many libraries do it and those that do are not very ergonomic. Go does it, and error handling is all over the code; there's no concept of "happy path". Rust does it great, but it relies on a Result type supported at the language level, with the ? short-circuiting a function like a throw does in JS. It also relies on the Result type itself and the ability to pattern-match against it. JS has nothing close to that.
    JS throws. Node, deno, bun, browsers, popular libraries and frameworks… they all throw. Not having a way to encode that is such a weird omission!
    (forgive me if I'm misremembering something)

  • @grzegorz_derdak
    @grzegorz_derdak Місяць тому +80

    Something indicating that a function can throw an error would be nice, so you'd know to catch it

    • @ozzitodadoritto
      @ozzitodadoritto Місяць тому +10

      I feel like this is already supported by putting @throws in the comment. But a linting warning if you don't handle a function with that in the comment would be pretty cool

    • @Danielo515
      @Danielo515 Місяць тому +1

      That is doable in user land. Fp-ts, effect and several other libraries does

    • @TheBswan
      @TheBswan Місяць тому +8

      Errors as values is the way

    • @grzegorz_derdak
      @grzegorz_derdak Місяць тому

      ​@@TheBswan that's what I came with eventually, to avoid throwing errors and just return an object with error code

    • @vitalysuper3193
      @vitalysuper3193 Місяць тому

      This is literally the only update I want from typescript team

  • @_nikeee
    @_nikeee Місяць тому +5

    I've proposed the interval types. The reason why I think negated types aren't a thing is because that would make some cases of resolving types extremely hard. Consider the cases where you'd union or intersect a negated type. It can result in a large type pretty quickly.

  • @mahadevovnl
    @mahadevovnl Місяць тому +41

    I wish they would just allow for `Omit` and that's it. That reads intuitively: it can be any string but not 'click'.

    • @nisabmohd
      @nisabmohd Місяць тому +11

      Omit is for object types 👍

    • @fardolieri
      @fardolieri Місяць тому +30

      `Exclude` would make more sense than Omit. It already works as you expect on unions: `Exclude // => 'touch'`

    • @mahadevovnl
      @mahadevovnl Місяць тому +1

      @@nisabmohd It's just a primitive type with its own built-in properties and methods. It's not that odd to think TS could include these things to even manipulate the prototype of these objects when you pass them along.
      So my code would be changed a tiny bit to `Omit` - capitalised the String part.
      The reason I'm saying this is that it would be intuitive to work with and not so arcane as many other parts of TS already are.

    • @ilonachan
      @ilonachan Місяць тому +2

      ​@@mahadevovnlYeah but TS doesn't really deal with manipulating the properties of builtins, for one. For two, your proposal ABSOLUTELY would not do what you think: it'd "remove" the "click" method from all strings of this type, if it existed, which it doesn't.
      `Exclude` makes sense with the way strings are treated too, because "string" isn't really an object prototype, it's more of "a union of all possible builtin strings".

    • @mahadevovnl
      @mahadevovnl Місяць тому

      @@ilonachan Fair points :) I guess it just makes a good case for validators like Zod, which is probably where this kind of thing belongs anyway, not in TS.

  • @avramlevitter6150
    @avramlevitter6150 Місяць тому

    The Opaque type is something I wish was easier to do in C++. The relative vs absolute path is a great example, but I've also wanted things like "both position and velocity are vectors, but their arithmetic is constrained". Very educational to see how this problem is addressed in Typescript, thank you!

  • @Bluesourboy
    @Bluesourboy Місяць тому

    5:55 is such a great pattern for when you need extra protection around a set of exported functions. Form validation & sanitization comes to mind.
    All of the external functions accept only the branded type and a provided gatekeeper/sanitize function returns the input in the correct branded type.

  • @munzamt
    @munzamt Місяць тому +9

    Brand types can be applied without casting, if brand property is optional

    • @BoonBoonitube
      @BoonBoonitube Місяць тому

      Nice!

    • @specy_
      @specy_ Місяць тому

      Wouldn't that allow you to pass in any primitive except other nominal types?
      As in, if u have:
      type A = string & {[_brand]?: "A" }
      And then you have a function that accepts A, wouldn't you be able to pass in any string, except other nominal types that aren't A

    • @munzamt
      @munzamt 10 днів тому

      @@specy_ You're right, this allows for that kind of type conversion. However, I think it's less destructive than using as-casting, as it doesn't disable any type checks.
      I usually use brand types as ID types, as sometimes the ID can represent different types of entities in different contexts. Therefore, it's convenient to have the ability to cast between a brand type and a string, and vice versa, without the need for as-casting.
      However, this scenario is relatively rare. More often, I pass many IDs into a function, and this ensures that I don't accidentally pass the wrong ID.

    • @specy_
      @specy_ 10 днів тому

      @@munzamt I mean it's better than nothing I guess

  • @ex-xg5hh
    @ex-xg5hh Місяць тому

    Typescript does have nominal types for classes with private members, you can also use those for branding

  • @hut_fire1490
    @hut_fire1490 Місяць тому

    I think I can hear the rain at the end of the video, anyway thanks for the awesome vid Matt !

  • @rickvandenbroek6869
    @rickvandenbroek6869 Місяць тому +6

    You forgot the most important one, why they do not type Object.keys for narrowed types and just make them string. 😉

    • @mattpocockuk
      @mattpocockuk  Місяць тому +4

      Yeah, this is a result of not having exact object types by default.

  • @TheAlexLichter
    @TheAlexLichter Місяць тому +9

    I thought you'd at least list some of the things you fixed with ts-reset :P

    • @mattpocockuk
      @mattpocockuk  Місяць тому +3

      Those are small-fry! Opaque types would be so sweet

    • @MirrorsEdgeGamer01
      @MirrorsEdgeGamer01 Місяць тому

      Coming from Scala 3, I really like opaque types.

  • @neolight1010
    @neolight1010 Місяць тому +2

    Nominal types are also referred to as "newtypes" in other languages. The library "gcanti/newtype-ts" provides a cool implementation that you can use :D

  • @markzuckerbread1865
    @markzuckerbread1865 Місяць тому

    I love the structural type system of TS, but I've hoping for nominal types since the day I found out about them

  • @robertomolinasilvera4863
    @robertomolinasilvera4863 Місяць тому

    matt hi, this one of best channels about advanced ts, great content. Can you make a video about project references? how to use them? implications? how to work with them when we are using bundlers ? pros vs contras. I'm moving one large monorepo to ts project references, but i found i have to make a build each time i wanna typecheck code and that is generating slow performance, even if its just declaration files, but is taking so much

    • @robertomolinasilvera4863
      @robertomolinasilvera4863 12 днів тому

      hi matt, i ve been doing this for a month. and I faced some problems migrating to turborepo. do you have any example/video working with turborepo and lefthook (or lintstage). I want to execute tasks in precommit just for workspaces that were affected and are staged. but is kind of impossible using turborepo by itself. The unique way that ive seen is using --filter=[HEAD] but im even matching not staged files (workspaces). Thanks

  • @SergiySev
    @SergiySev Місяць тому +1

    Not only do I have to program JS in TS every day, but now I also have to program the Typings for TS. Stop the torture :(

  • @kirillzlobin7135
    @kirillzlobin7135 Місяць тому

    IMPORTANT FOR MATT
    Hi Matt. You, as a TS ninja, whose opinion on TS is important worldwide, can affect TS team on the following issue :) Or maybe you can explain why stuff that I will describe below is as it is:
    For example I declared type:
    type MyType = {
    name: string
    values: number[]
    }
    const myObject: MyType = {
    name: "Batman"
    values: [3, 5, 8]
    }of
    Why TS allows to doubt here:
    myObject.values?.filter(num => num > 10)
    Why it allows to use this question mark? We definitely know that values is mandatory, and they are represented by array. It should say something like - hey man, this question mark is redundant.
    Please, help to understand why it works like this
    Kind regards

    • @mattpocockuk
      @mattpocockuk  Місяць тому +3

      TS lets you do unnecessary checks. ESLint can help prevent that.
      Also, I have zero influence over the TS team

    • @kirillzlobin7135
      @kirillzlobin7135 Місяць тому

      @@mattpocockuk I know, but I am sure they respect you. Thank you very much

  • @detaaditya6237
    @detaaditya6237 Місяць тому

    The TypeScript God is trying to tell you something Matt

  • @nomadshiba
    @nomadshiba Місяць тому

    2:44 type Spread = T2 & BetterOmit;

  • @uhhmir
    @uhhmir Місяць тому

    awesome video!

  • @doubledouble3579
    @doubledouble3579 Місяць тому

    5 features the enzo checker already has

  • @noriller
    @noriller Місяць тому +2

    What about Symbols? Symbols are something unique, but it seems typescript doesn't really know that.

    • @ilonachan
      @ilonachan Місяць тому +1

      Which also makes symbols prime candidates for the hacky nominal typing workaround

  • @jimmy.im-kp
    @jimmy.im-kp Місяць тому

    brand type assertion awesome !

  • @DefekacjaOdbyta
    @DefekacjaOdbyta Місяць тому

    Another thing that is unlikely to be implemented is a multi-threaded tsc. Yes, I know it's not a language feature, but still.

    • @mattpocockuk
      @mattpocockuk  Місяць тому

      Yes, TS currently needs to read every file to detect global augmentations, and will do for as long as global augmentation is allowed in TS. So multi-threadedness has limited benefits.

  • @edgeeffect
    @edgeeffect Місяць тому

    I was hoping your book would be an actual paper book that I could buy.

    • @mattpocockuk
      @mattpocockuk  Місяць тому +3

      It will be! Published by No Starch

  • @LePetitMondedePurexoYT
    @LePetitMondedePurexoYT Місяць тому

    `Exclude` should work no ?

  • @user-abc855
    @user-abc855 Місяць тому

    I've seen examples of branded types from your LinkedIn as well, and I don't understand what's the point of this? If we are talking about the example with relative and absolute paths, I find it completely useless it still does not check a string whether it is a relative path or an absolute one, and it doesn't check it in compile time nor runtime. If I would need something similar, I would just go with wrapping a string into a class with putting validation to the constructor

  • @catbb
    @catbb Місяць тому

    thaamks peacock 🦚

  • @EricSundquistKC
    @EricSundquistKC Місяць тому

    Shout out to the TypeScript Tuesdays emails! If you're not subscribed, you should be!

  • @culi7068
    @culi7068 Місяць тому

    It saddens me that we can't just
    type NewSpeak = Exclude;

  • @QwDragon
    @QwDragon Місяць тому +2

    5:35 There is another way of doing type branding without using `as`:
    declare const Brand: unique symbol
    declare const AbsolutePathBrand: unique symbol
    declare const RelativePathBrand: unique symbol
    type AbsolutePath = string & { [Brand]?: typeof AbsolutePathBrand }
    type RelativePath = string & { [Brand]?: typeof RelativePathBrand }
    const absolutePath: AbsolutePath = "/path/to/file"
    const relativePath: RelativePath = "../relative/path"
    const stringPath = "string"
    declare function handleAbsolutePath(path: AbsolutePath): void
    handleAbsolutePath(absolutePath) // ok
    handleAbsolutePath(relativePath) // error
    handleAbsolutePath(stringPath) // ok
    handleAbsolutePath("hi") // ok
    declare function handleAbsolutePath2(path: typeof Brand extends keyof P ? P : never): void
    handleAbsolutePath2(absolutePath) // ok
    handleAbsolutePath2(relativePath) // error
    handleAbsolutePath2(stringPath) // error
    handleAbsolutePath2("hi") // error

    • @mattpocockuk
      @mattpocockuk  Місяць тому +3

      This will accept strings

    • @QwDragon
      @QwDragon Місяць тому

      @@mattpocockuk nongeneric version only. I've checked in playground. Generic version (handleAbsolutePath2) rejects strings.

  • @TheUnknownFactor
    @TheUnknownFactor Місяць тому +1

    For the branded types you could use template literal types

    • @mihaimanole2643
      @mihaimanole2643 Місяць тому

      In this example, the branded type is the string type. What about other types? Can the template literals brand those types?

    • @nisabmohd
      @nisabmohd Місяць тому

      Think of an instance from class what about them

  • @juanlambrotta1781
    @juanlambrotta1781 15 днів тому

    I've been checking out your courses, I did all the free ones because I liked them so much and when I go to the checkout to buy one. $200?! $500?! is too much dude, I'm not saying your content isn't worth it but the market is full of typescript or javascript courses at lower prices.
    Believe me, lower the price to $25 - $80 and you'll have more sales (including me)

  • @rafadydkiemmacha7543
    @rafadydkiemmacha7543 Місяць тому

    Why would I expect Omit to throw errors when the keys don't overlap? It wouldn't make any sense.

    • @ThisAintMyGithub
      @ThisAintMyGithub Місяць тому

      Because why wouldn't you want to know if the key isn't omitting anything (other than the exception given in the video)? The vast majority of Omit's use cases are "omit this field from this object"

    • @rafadydkiemmacha7543
      @rafadydkiemmacha7543 Місяць тому

      @@ThisAintMyGithub not at all my experience. I very often omit via unions.

  • @VeitLehmann
    @VeitLehmann Місяць тому +1

    Can't AbsolutePath/RelativePath be defined like type AbsolutePath = `/${string}` and type RelativePath = `.${string}`?

    • @mattpocockuk
      @mattpocockuk  Місяць тому +1

      Yes, but they wouldn't be cross-platform, and it also doesn't work well with external libraries. In my repo I end up casting the results of third-party libs like glob.

    • @auscompgeek
      @auscompgeek Місяць тому

      Relative paths don't necessarily start with `.` either.

    • @VeitLehmann
      @VeitLehmann Місяць тому

      @@auscompgeek True, this would be a kind of crude solution that might work in certain cases, not in all.

  • @MattChinander
    @MattChinander Місяць тому

    5:55 very gross indeed

  • @bigmistqke
    @bigmistqke Місяць тому

    If u wanna do absolute/relative path without branding: T extends `../${string}` ? never : T

    • @mattpocockuk
      @mattpocockuk  Місяць тому

      This doesn't make sense when most of the types are 'string'