Solving the 'any' problem is harder than you think

แชร์
ฝัง
  • เผยแพร่เมื่อ 10 ธ.ค. 2024

ความคิดเห็น • 87

  • @mattpocockuk
    @mattpocockuk  ปีที่แล้ว +15

    Sorry for the previous failed upload! This one's much better, trust me.

  • @aprilmintacpineda2713
    @aprilmintacpineda2713 ปีที่แล้ว +17

    This reminds me of my first experience with TypeScript, we were a team of JS devs and decided to migrate to TS for the reason that it makes switching between multiple projects better, that was 2020, and damn we had tons of ANY in our code base, we even has as unknown as any in there if I remember correctly!

  • @gosnooky
    @gosnooky ปีที่แล้ว +19

    Best TypeScript guy on the interwebs. I started using it in 2016 in a React project and it's been really fun to watch the language grow up, especially with its inference capabilities.

  • @faisalantu
    @faisalantu ปีที่แล้ว +2

    You are the only TS GURU I need!!, Thanks.

  • @MiceInDownpour
    @MiceInDownpour ปีที่แล้ว +23

    Where does the PropertyKey-Type in the generic example come from? Actually while I like this video I would've preferred and explanation for the generic solution as well. Just for the sake of completeness.

    • @TheBswan
      @TheBswan ปีที่แล้ว +10

      PropertyKey is built in, it's just a union of string | symbol | number, i.e. any valid type for accessing properties on an object

    • @MiceInDownpour
      @MiceInDownpour ปีที่แล้ว

      @@TheBswan Ah, thank you - I was actually referencing the typescript docs, because I suspected it to be a builtin type. But I must have taken a wrong turn, because I didn't find it :D

  • @DuongLe-em4dg
    @DuongLe-em4dg ปีที่แล้ว +6

    This is exactly what I faced when I was in my internship. Gladly I ended up using generic thank to your video about object keys lol

    • @MyPhuckDub
      @MyPhuckDub ปีที่แล้ว

      Dam you're pretty good for an intern

  • @arturamorfati
    @arturamorfati ปีที่แล้ว +1

    glad to see your channel growing!❣

  • @elraito
    @elraito ปีที่แล้ว +6

    As a beginner the hardest part for me has been to type things that can error or fail. Say a fetch that can return a general error, correct result or one of several custom errors where you can have one or many error messages that are not standard. So far ive been using type unions as a substitute for anys as return types and it works-ish but id be interested to see how the more pros handle things.

    • @mattpocockuk
      @mattpocockuk  ปีที่แล้ว +4

      Yes - this is complex because technically, in a try/catch, any error can be thrown. You can't know the errors ahead of time.
      So returning a union type that may contain an error is very legit, and used often. I think it's actually the default behaviour in langs like Rust. I think it's called an Either type? Though likely wrong there as I don't use Rust.

    • @johtso1
      @johtso1 ปีที่แล้ว

      @@mattpocockuk this is something I've been wondering about. From a typing perspective is it better to return rather than throw errors? Better to use safeParse rather than parse? Maybe the distinction is, don't throw errors for things that are expected to happen often in the "normal" operation of the code?

    • @mattpocockuk
      @mattpocockuk  ปีที่แล้ว +2

      @@johtso1 It's a tricky one, because whenever you throw anything, you're throwing it into a hole that can't be typed. I.e. unknown is truly the only correct typing for an error.

    • @johtso1
      @johtso1 ปีที่แล้ว

      @@mattpocockuk I guess you can think of it as, errors allow the caller to choose not to handle certain results (just don't catch the error). In contrast, properly typed return values force the caller to deal with all possible outcomes. I like the idea of Either and how honest it keeps you. Exceptions seem like kind of an escape hatch

    • @bigpopakap
      @bigpopakap ปีที่แล้ว +1

      I'd say if you want to type your errors, a good first option is to return a tagged union, so you can check whether it was success or error, what the specific error was, and properties about that error, all while remaining type safe.
      Another good solution would be to use fp-ts or @effect, which has a bunch of primitives for the Either type (and many others) and tons of stuff to be fully type safe about errors (and concurrency, and composability, and dependency injection and so much more).
      Of course, this is JavaScript and nothing will stop you from a row developer just throwing any old error that you don't know about

  • @wlockuz4467
    @wlockuz4467 ปีที่แล้ว +2

    I love the analogy of a single "any" leaking more "anys" 🤣

  • @edgeeffect
    @edgeeffect ปีที่แล้ว +1

    I'm an old hand at programming but an absolute newbie at TypeScript. When I was "having a go at this TypeScript thing" I instinctively smelt something bad about `any`. Fortunately I knew there must be some generics in there somewhere and was rescued quite quickly. I hated the arrival of weak typing back in the 90s... I'm highly amused by the arrival of TypeScript, type-hinting in PHP and Python, and so on. I feel vindicated for spending the last 30 years with a clothes-peg on my nose now.

  • @Peshyy
    @Peshyy ปีที่แล้ว +2

    While a great video, I'd definitely take a different approach.
    Directly return `arr.reduce()` instead of using `forEach` and defining a variable inside the function. This will also help with typing the values and inferring types. And to improve code readability, I'd define custom types/interfaces and use those instead of inline typing everything.

  • @victorlongon
    @victorlongon ปีที่แล้ว +2

    Nice one, Matt! What's the PropertyKey type in the example?

    • @omarlopez4340
      @omarlopez4340 ปีที่แล้ว +3

      This type is built-in in the language and basically is a union type of `string | number | symbol`

    • @victorlongon
      @victorlongon ปีที่แล้ว +2

      @@omarlopez4340 really?! I didnt know that! Thanks!

    • @anush8
      @anush8 ปีที่แล้ว

      ​@@omarlopez4340Thanks

  • @culi7068
    @culi7068 ปีที่แล้ว

    Here's how I did a very similar function just the other day. In just 11 lines of code!
    The function is to take an array of objects that have, e.g., an `"id"` key, and return a record of those objects indexed by their key. Same TypeScript principles apply
    ```
    /**
    * Create a lookup table from an array by an arbitrary key
    */
    export const createRecordFromKey = <
    K extends string,
    T extends Record
    >(
    array: T[],
    key: K
    ): Record =>
    array.reduce(
    (acc, item) => ({ ...acc, [item[key]]: item }),
    {} as Record
    );
    ```
    the limitation here is `K` is typed as a `string`. It could be a `symbol or keyof T` instead, but tbh I haven't come across a usecase for that expansion

    • @culi7068
      @culi7068 ปีที่แล้ว

      The other benefit is, if you don't have the eslint rule that forces you to annotate the return value on exported functions, you can omit the return value entirely and rely on TS' type inference

    • @culi7068
      @culi7068 ปีที่แล้ว

      Also props to you Matt for always using realistic examples. I almost always am able to see how the example relates to my own code

  • @TheBooker66
    @TheBooker66 ปีที่แล้ว +1

    I think the place "any"s are most common isn't utility functions, but rather returning API calls. I just never know what the API returns and that can be problematic for me.

  • @ShotgunAFlyboy
    @ShotgunAFlyboy ปีที่แล้ว

    I basically view Unknown as "I'm deliberately making this section of code painful because you didn't type it and you should".

  • @FakeNeo
    @FakeNeo ปีที่แล้ว

    If your function could be called from pure JS, using unknown with the explicit validation of arguments is safest, as this will guard against undefined behavior in production. In most other situations, I would avoid using unknown inputs until I have a specific reason to use it.
    One special exception is that I prefer unknown over void for the return value of function arguments, because TS will allow non-void return types to be cast implicitly as void through a nasty edge case:
    ```ts
    const returnsVoid = (fn: () => void): void => fn();
    const doesNotReturnVoid = () => 'example';
    const expr: void = returnsVoid(doesNotReturnVoid);
    console.log(expr);
    ```
    Typescript is trying to be helpful by allowing you to take a ()=>string in a location where a ()=>void is expected. On the surface this is convenient, but in this example the erasure of return type information causes the TS compiler to erroneously allow returnsVoid to implicit cast values to void.

  • @neociber24
    @neociber24 ปีที่แล้ว +2

    I really like generics, is like a cool math problem, but oh boy, I use a lot of time doing that.

  • @nomadshiba
    @nomadshiba ปีที่แล้ว

    the problem is mostly with how typescript works (in some case)
    not just any(s)
    in some edge cases logic of typescript is just doesn't make sense, like it shouldn't do that, it should be a bug, but does it, and it wont be fixed because it would be a big breaking change.
    sometimes i look at typescript and say, "why, why handle this like that"
    but its what we have now, still better than just javascript
    i love the idea that types are more than just types with typescript
    i keep thinking what if we made a new more strictly typed language but also inspired by the meta programming of the typescript but even better
    i think rust is doing some cool stuff with "types" and "macros"

  • @edwardkeselman5118
    @edwardkeselman5118 11 หลายเดือนก่อน

    What if my function is something like:
    const fetch = (url:string, options: Record ) => {
    //...some code
    const someVal = options.entry ? someArrFromAbove[options.entry as string] : 0; // Use as string here to fix unknown cannot be used is index error.
    }
    It's a utility function and it can be used in different cases and projects so I don't really know which properties options will have.
    Is using unknown in this case is a valid option or should I try to work around with with generics as well

    • @mattpocockuk
      @mattpocockuk  11 หลายเดือนก่อน

      Generics for sure.

  • @mikechurvis9995
    @mikechurvis9995 ปีที่แล้ว +7

    This is a good video, Matt. My only concern with the Generics approach is that it gives a false sense of security, because Generics in TypeScript, while good for tooling, make no guarantee of *runtime* type validity and are thus insufficient for validation.
    In the example that uses `unknown`, you are forced to implement a validator that demonstrates that the thing you *think* you're about to receive is in fact the correct type. This is necessary if you are writing a public-facing API or a function that converts one object type to another, i.e. from JSON.parse()'s `any` to a typed object. TypeScript makes this easy in the form of Type Predicate functions { `(thing: unknown) => thing is MyType` }, as they let your validator perform type narrowing. As boolean functions, Type Predicates also force you to consider the code path where the unknown is *not* the type you think it is.
    In short, I like `unknown` for the reason you seem to dislike it: it demands defensive diligence. It requires the developer to programmatically guarantee the validity of a type *at runtime*. To my knowledge, Generics can not do this in TypeScript. Please correct me if I'm wrong!
    What are your thoughts?

    • @peterjwest3
      @peterjwest3 ปีที่แล้ว

      I think generics are the solution I’m this case, but obviously unknown is perfect in other situations, such as user input. I’m sure Matt is aware of this as his library changes the output of JSON.parse to unknown

    • @mattpocockuk
      @mattpocockuk  ปีที่แล้ว +16

      I think you should be validating at your app's I/O boundaries, but not validating elsewhere.
      Defensive diligence is important for I/O, far less important otherwise.

    • @mikechurvis9995
      @mikechurvis9995 ปีที่แล้ว +1

      @@mattpocockuk Agreed, for the most part. I tend to mentally focus on I/O boundaries just because that's where most of the bugs come from, in my experience. Thanks for sharing!

    • @mk72v2oq
      @mk72v2oq ปีที่แล้ว +2

      Very strange concern. It is obvious because TS does not provide runtime guarantees like at all. Generic or not - does not matter.
      With your logic we should use unknown everywhere instead of actual types. Just because in runtime it is technically possible to e.g. call a function with wrong arguments.

    • @rutabega306
      @rutabega306 ปีที่แล้ว +2

      The runtime vs compile time concern is not a problem of Generics, it's a "problem" of TypeScript

  • @nghiaminh7704
    @nghiaminh7704 ปีที่แล้ว

    in the example, item["age"] is a number. why can you cast it to string?

  • @johtso1
    @johtso1 ปีที่แล้ว +1

    another banger!

  • @leo11877
    @leo11877 ปีที่แล้ว

    any, are you okay? So, any, are you okay? Are you okay, any? any, are you okay?

  • @Dev-Siri
    @Dev-Siri ปีที่แล้ว

    infer > > unknown > any;

  •  ปีที่แล้ว +2

    Very interesting subject. I still find the video very hard to track and things and explanations fly by very fast for me.
    But i guess that is my problem.

    • @MyPhuckDub
      @MyPhuckDub ปีที่แล้ว

      I agree. I had to practice so much just to understand the basics, I'm using TS for like 7 months and sometimes it's still hard to grasp. I'm not really intellectual LOL!

  • @olduniverse9270
    @olduniverse9270 ปีที่แล้ว

    3:49 that's good

  • @AlexAegisOfficial
    @AlexAegisOfficial ปีที่แล้ว

    TLDR realize when you want to define something and when you just want to constraint something. Use generics with extends for the latter.

  • @ColinRichardson
    @ColinRichardson ปีที่แล้ว +3

    Not private this time then?

    • @mattpocockuk
      @mattpocockuk  ปีที่แล้ว +2

      Screwed up the previous edit! This one's much cleaner

    • @ColinRichardson
      @ColinRichardson ปีที่แล้ว

      @@mattpocockuk It's all good... The more times you try it, the better it will end up

  • @TheKayser1
    @TheKayser1 ปีที่แล้ว

    great video as usual!! It wouldn't be better to only allow to groupBy Records which values are string | number | symbol and avoid casting?
    function groupBy(arr:T[], key:K){
    let result ={} as Record
    arr.forEach(item=>{
    const resultKey = item[key]
    if(result[resultKey]){
    result[resultKey].push(item)
    }else{
    result[resultKey] = [item]
    }
    })
    return result
    }

    • @mattpocockuk
      @mattpocockuk  ปีที่แล้ว

      No, because then you can't pass in things that aren't PropertyKeys in the values, which seems overly restrictive given that we only care about one value.

  • @aram5642
    @aram5642 ปีที่แล้ว

    Assume you publish a lib that deals with errors and allows to pass a cb to handle an error. Would you type the error param as any or unknown?

    • @mattpocockuk
      @mattpocockuk  ปีที่แล้ว +1

      Definitely unknown! Same as the way catches are handled in TS

  • @aleksander5298
    @aleksander5298 ปีที่แล้ว

    But there is no protection here against reading a key that does not exist, isn't there some way to return exact values in such a function?

  • @SergeiGrishchenko-o5m
    @SergeiGrishchenko-o5m ปีที่แล้ว

    I saw that you used something like `T extends (...args: any[]) => any` as generic bound in some your previous video. And you said something like "Sometimes you have to use `any` in your code". I believe that technically it is not necessary. You can use something like `T extends (...args: never[]) => unknown` for it. I really don't understand why people don't use this pattern to avoid `any` in their code. It would be great if you make a video about it.
    Thank you though.

    • @mattpocockuk
      @mattpocockuk  ปีที่แล้ว

      Any's in generic constraints are absolutely fine - they don't actually reach the rest of your code. So, T extends (...args: any[]) => any is fine.

    • @SergeiGrishchenko-o5m
      @SergeiGrishchenko-o5m ปีที่แล้ว

      @@mattpocockuk Yea, I know, but it is more about code style. There are eslint rules, that check that you don't use `any` type in your code, and you don't need to make exceptions for such rules even in case of generic bounds.

  • @AndersGustafsson87
    @AndersGustafsson87 ปีที่แล้ว

    4:20 "the result is typed properly"
    is it really though? :p

    • @mattpocockuk
      @mattpocockuk  ปีที่แล้ว

      Yes!

    • @AndersGustafsson87
      @AndersGustafsson87 ปีที่แล้ว

      @@mattpocockuk Was referring to keys being number. For example Object.keys(result) will not actually give you number[]

    • @mattpocockuk
      @mattpocockuk  ปีที่แล้ว

      @@AndersGustafsson87 Keys in JS are automatically coerced to strings, so all keys are essentially strings.

    • @AndersGustafsson87
      @AndersGustafsson87 ปีที่แล้ว

      @@mattpocockuk yes

  • @JoepKockelkorn
    @JoepKockelkorn ปีที่แล้ว +1

    Any are you okay, would you tell us, that you’re okay?

  • @prionkor
    @prionkor ปีที่แล้ว

    I am still so confused with generics :(

    • @mattpocockuk
      @mattpocockuk  ปีที่แล้ว

      Does this help?
      th-cam.com/video/dLPgQRbVquo/w-d-xo.html

  • @CoericK
    @CoericK ปีที่แล้ว

    Daf*ck the solution doesn’t show the implementation of the generics, I prefer the any then 😂

  • @allan_archie
    @allan_archie ปีที่แล้ว

    What of Runtime costs?

  • @yoyo26-34
    @yoyo26-34 ปีที่แล้ว

    result : make sure to know generics when using typescript 😀😁

  • @wobsoriano
    @wobsoriano ปีที่แล้ว

    Titanic....

  • @thechaoslp2047
    @thechaoslp2047 ปีที่แล้ว +1

    If you dont know generics, take the 10 minutes to learn genereics

    • @ChillAutos
      @ChillAutos ปีที่แล้ว

      TBF whilst you can learn the basics of generics in 10 minutes the end solution is still going to cause issues for a lot of developers. Its a lot more complicated than just learn generics and then we end up in a situation where only 1-2 people can understand that code. You an say just get good but you could say that about anything really.

    • @culi7068
      @culi7068 ปีที่แล้ว +1

      Uh I don't know if I agree. It's much harder to refactor our a deeply embedded `any` than it is to fix a generic. Generics with the `extend` keyword are very safe and extremely useful
      speaking as someone who just refactored some really painful typescript

  • @jamesgl
    @jamesgl ปีที่แล้ว

    I was so disappointed until you mentioned at the very end you have a generics video explaining them.
    Imo, I would’ve liked that info right after you said you wouldn’t explain it bc here I was like “this guy is gonna NAME how to fix it but not SHOW how to fix it, cmon” 😅

  • @caoimhe801
    @caoimhe801 ปีที่แล้ว +1

    first : )

    • @ColinRichardson
      @ColinRichardson ปีที่แล้ว

      OOOOhh.. sorry to inform you.....

    • @mattpocockuk
      @mattpocockuk  ปีที่แล้ว

      Yep, Colin got it by 3 seconds!

    • @caoimhe801
      @caoimhe801 ปีที่แล้ว

      @@mattpocockuk damn... maybe next time!

  • @UocLv
    @UocLv ปีที่แล้ว

    TS is garbadge, this makes me so mad. I better add aditional tests, then learn this boilerplate.

  • @tmahad5447
    @tmahad5447 ปีที่แล้ว

    that's why you should not use ts