Be Careful With Return Types In TypeScript

  Рет қаралды 60,573

Theo - t3․gg

Theo - t3․gg

Жыл бұрын

I'm sorry Prime ❤️
#typescript
THANK YOU TO ALL THE AWESOME GUESTS
- Malte / cramforce
- Trash @trash_dev
- Ben Holmes / bholmesdev
- Josh Goldberg / joshuakgoldberg
- Dax Raad / thdxr
- Maple / heyimmapleleaf
- Alex / alexdotjs
- Tanner / tannerlinsley
- Kent C Dodds / kentcdodds
- Matt Pocock @mattpocockuk
ALL MY VIDEOS ARE POSTED EARLY ON PATREON / t3dotgg
Everything else (Twitch, Twitter, Discord & my blog): t3.gg/links

Пікірлер: 430
@t3dotgg
@t3dotgg Жыл бұрын
Goal of this video is to end the convo. Please don't be obnoxious to me, Prime, or anyone else so we can move on ❤
@MrJester831
@MrJester831 Жыл бұрын
Doesn't this just open up a whole new convo about the shortcomings of TS type safety?
@gideonmaxmerling204
@gideonmaxmerling204 Жыл бұрын
I don't think that the prime example is a good example, as not specifying a return type did not actually solve the problem with the code, you're still returning a user with the wrong permissions, just because you changed the type definition to accommodate the error doesn't mean that the error is gone
@noctali8049
@noctali8049 Жыл бұрын
This debate was super engaging and I learned a lot! You guys should disagree publicly more often 😉
@magne6049
@magne6049 Жыл бұрын
@@MrJester831 **cough** ReScript **cough** superior/superb **cough** Hindley-Milner type inference **cough**
@daedalus5070
@daedalus5070 Жыл бұрын
I just want Mummy and Daddy to stop fighting 😭
@samuelgunter
@samuelgunter Жыл бұрын
just explicitly return `any`, then you'll never have to deal with losing truth
@stephanjacob17
@stephanjacob17 Жыл бұрын
type everything with any and you'll never have any (pun not intended) problems!
@noccer
@noccer Жыл бұрын
Yes absolutely, and don't forget to use password123 while you're at it ☘️
@neociber24
@neociber24 Жыл бұрын
Well Theo likes it's diagrams, and any for sure is the universe
@austincodes
@austincodes Жыл бұрын
😂
@froxx93
@froxx93 Жыл бұрын
Thanks, I hate it
@TypeScriptTV
@TypeScriptTV 10 ай бұрын
Explicitly annotating return types is necessary when dealing with recursive functions: kzbin.info/www/bejne/pIfHaZ2sn7Bnj5I
@DylanRJohnston
@DylanRJohnston Жыл бұрын
You keep calling it “lying” because you’re mistaking Typescript’s structural types with nominal types from other languages. In your User type example, you need to learn to read typescript types as contracts about minimum functionality not a maximum bound. It means this type has at least a username and an email, but it could have more. It allows typescript code to be much more composable if you embrace the structural typing instead of borrowing nominal typing ideas from other languages.
@jorgitoaral
@jorgitoaral Жыл бұрын
I completely agree with you! To me, it's kind of frustrating to try to apply the same concepts of typed languages in typescript, where typing is just a specification of the minimal contract to be meet... The only case I'm worried about though (I should better study the theory behind it) is the case of the overridden function definition.
@Kopraaaa
@Kopraaaa Жыл бұрын
Finally, someone explained it properly.
@DubiousNachos
@DubiousNachos Жыл бұрын
I agree that structural typing is powerful, but in some cases, having additional properties and values hidden beneath the TS poses security risks. Like with an API response. If TS is telling you that you're sending an object with two properties, but you're really sending something with 15 properties with some sensitive values, it's a problem if Typescript isn't able to catch that. You can make sure to extract out the properties you intend to ship off, just to be safe, but from Typescript's perspective, that isn't going to be necessary You can do things like make sure that you're extracting out the exact properties you
@damymetzke514
@damymetzke514 Жыл бұрын
@@DubiousNachos I'd argue that relying on typescript for security is a mistake in the first place. Typescript doesn't make any guarantees at compile time, so you're at risk anyways. Also TS is not telling you that you're sending an object with two properties, it's saying that you're sending an object with at least 2 properties. This is the exact point of the initial comment.
@Kopraaaa
@Kopraaaa Жыл бұрын
@@Fixeish That would be really good, but that was not point of initial comment. That's why strongly typed languages will always be better choice for me.
@felipenascimento6118
@felipenascimento6118 Жыл бұрын
The second example is not a lie, it is correct representation of what's happening. When you declare a object type with two properties, it doesn't mean "these properties and nothing else", it means "at least these properties". The error is saying that you can't use password, not because it doesn't exist, but because it cannot trust it will always exists. This is correct behavior, since is this what allows interface overriding. I think typescript should give unknown, not a error, but that's how it works. I also miss the ability to declare a strict type, with only those properties and nothing else. Typescript has a really bad typing system, in a sense that is pretty easy to lie to it. I kinda understand it. If it weren't, it would be pretty hard to do all of those dynamic transformations of value that it allows
@JeyPeyy
@JeyPeyy Жыл бұрын
By simply looking at that code we can trust that password will always exist. You could say that explicit typing isn't a lie, but rather an omission of information, sure. But I still see it as a lie. But yes, a strict type where nothing else can be included would be a very good addition to typescript.
@ShaharHarshuv
@ShaharHarshuv Жыл бұрын
​@@JeyPeyy Omission of information is not a problem if you're not expecting the consumer to use this property. This can frequently happen on the other side with function arguments to be more flexible for the consumer. (it's considered a bad practice for a function to ask for properties it doesn't need, because than you are more strict on the consumer unnecessarily)
@kabal911
@kabal911 Жыл бұрын
It is interesting, because if we use “interface” instead of “type”, then the result of having no password property is 100% expected, and how basically every programming language works.
@ShaharHarshuv
@ShaharHarshuv Жыл бұрын
@@kabal911 There is almost no difference between interface and type in typescript, which is one of the more confusing aspects of the language
@kabal911
@kabal911 Жыл бұрын
@@ShaharHarshuv sure, and interfaces have other issues too, but that’s not really the point. My comment is about the framing of the issue. No one would say it was “a lie” if we are taking about interfaces. Everyone understands that if I have an interface called “Named” with a single property called “name”, a function that returns a “Named”, and inside that function I return an object that has many properties including “name”, then the code that calls that function only “sees” the property “name”.
@theonethatprotectsyoufromt9271
@theonethatprotectsyoufromt9271 Жыл бұрын
What I like about having a return type is that it prevents me from accidentally returning a wrong value. Most of the problems with return type can be solved if a return type is narrow. I'd still use them depending on a situation.
@TheProcessor
@TheProcessor Жыл бұрын
This has saved me many times from a small bug.
@FlorianWendelborn
@FlorianWendelborn Жыл бұрын
I can't think of many cases where you wouldn't catch those once you use the function
@TheProcessor
@TheProcessor Жыл бұрын
@@FlorianWendelborn The TS catches it faster than running the function.
@FlorianWendelborn
@FlorianWendelborn Жыл бұрын
@@TheProcessor I didn’t mean executing, I meant using the function in your code.
@SamualN
@SamualN Жыл бұрын
you almost catch this when you use the function and you notice it isn't returning the type you expect if the function is part of an api however return types make a lot of sense (and using them can prevent accidentally introducing breaking changes)
@paulomattos6753
@paulomattos6753 Жыл бұрын
4:46 the point is, the user type is TELLING THE DEVELOPER THAT "PASSWORD" IS NOT A VALUE THAT THIS FUNCTION SHOULD RETURN
@3ventic
@3ventic Жыл бұрын
I'm not a fan of the password example, because you need a test or something like Zod if you want to guarantee information doesn't get leaked in that manner; relying on seeing it with intellisense just isn't nearly good enough. The safety of accessing properties you intend to be there is still present so typescript is doing its job. The overloading is something I didn't even know typescript supported because it just seems like a terrible idea: if you need to get a specific type from a union, the answer is type-guards. The main reason I like to use explicit return types in a lot of cases is to create a contract about the function: you and future maintainers know what it's supposed to return and you can't accidentally change it, and it gives a full call signature you can share between contexts. This is important if the callstack (including network calls and other execution context changes) isn't fully type-safe, which is the case for much of the typescript code I currently write (though I'm trying to get towards full end-to-end type-safety).
@krzysztofzabawa2212
@krzysztofzabawa2212 Жыл бұрын
Exactly! I think only Matt got that right. That it depends where you use (or not) inference. Within a scope of single file or just an application code - it's totally fine. But for library code it's totally unacceptable. Password (or literally anyting else in User object) might be just an implementation detail which might be changed and we should not leak it to publicly exposed contract. Hence defining User as return type is not a lie, it's actually specifying the contract and giving a freedom of changing library function implementation details without potentially breaking implementations using it. Which translates to scalability and maintainability.
@Nicholas-qy5bu
@Nicholas-qy5bu Жыл бұрын
I m with prime on this one, sorry theo x) Strict typing is used to replace contract / test. I dont see the lie there since password is never used. For me this is a bad pattern, your function should not manipulate password since your code base does require any use of it. If you need to use it, update the return type with password ... About the 'yay', you could just have typed it as a const also instead of 'string'... For the rest, yes typescript has its limits, but nothing that cant be fixed with good pattern and design, or using Zod. The biggest flaw of your argument, and probably why Prime will never settle with your opinion is the fact that this does not include what happens when you want to understand / refactor the code, thats why test are usefull more than just avoiding regression. They maintain and explain the design. Same idea for explicit typings. On the short term maybe you dont see the point and i get it, but system are meant to last, and not having explicit design will harden the learning curve for new developpers and create more problem in the long run.
@ISKLEMMI
@ISKLEMMI Жыл бұрын
I can understand Prime's frustration with TS. I initially expected TS to be a bona fide language with a real type system, but it most certainly isn't that. It's just the best JS linter that's been built so far.
@RyanBreaker
@RyanBreaker Жыл бұрын
I seriously don’t understand why we don’t just write TypeScript interpreters, why bother still going back to JavaScript if we know it has all these problems? Of course the real solution is to just use Rust.
@JeyPeyy
@JeyPeyy Жыл бұрын
@@RyanBreaker Do you mean like a typescript interpreter that does runtime checks? Browsers will still run javascript. Breaking backwards compatibility isn't an option for them, and maintaining support for two languages isn't desirable. But for server code and WASM? Sure, just call it something else than typescript since it will behave differently.
@Slashx92
@Slashx92 Жыл бұрын
@@JeyPeyy typescript++
@NathanHedglin
@NathanHedglin Жыл бұрын
Purescript is MUCH better
@ametreniuk
@ametreniuk Жыл бұрын
In the first example “yay” looks like an implementation detail, and in a general case string is acceptable. You could also make a case against returning “const” so the consumers of the function don’t rely on it. The other examples are great for highlighting of the tradeoffs of being more cautious regarding return types. I think a lot of misunderstandings regarding this topic is about “defaulting” to one strategy instead using “only” one strategy
@Szergej33
@Szergej33 Жыл бұрын
I think it depends a lot on the source of your data. In the FE working with the possibility of almost all fields can be null or undefined bc they were user generated at some point, generality is good. If the source of values or types is your own function, then const gives a lot of specificity. And consumers can depend on it, because you control the source.
@ametreniuk
@ametreniuk Жыл бұрын
@@Szergej33 agree. Specificity is great when you need it
@doc8527
@doc8527 Жыл бұрын
@@Szergej33 ​ there is one annoying thing to add on: the inconsistency of remote backend data. It's "controlled uncontrolled" source. Due to the business change and fast iteration, your strict definition can easily and quickly become a lie without notice, and break your application. In this case, the vague infer type (assumption) with undefined | null check can be useful because most of the times Frontend code will have a safe guard for them. If something happens, we might just render some empty data. Otherwise your page will be crushed due to some operations on those weird data from backend/wherever accidentally. I encounter quite amount of times frontend & backend apps were too confident about the existing defined type, and at some point the entire app was crushed unnecessarily due to those fake strict types. I see many comment keeps saying about all the strict types things are from a java/c++, pure backend background that doesn't need to handle the transaction frontend encounters. While they are being correct under their own workspaces, it's completely out of context and not useful at all. And many just failed to provide their own context. I rather hearing someone sharing examples back by its own experiences instead of randomly throwing out some concepts nowadays.
@Szergej33
@Szergej33 Жыл бұрын
@@doc8527 it's a bit messy but I think I get what you mean. And yea, i agree with inconsistent backend data. Most of the time, unless you are using trpc, when you do a fetch in the fe code you will have to manually type it, otherwise it will be any. And in those type defs you usually have to be quite vague, because of the inconsistency of the data, or backend dev added some more properties to the dto to suit another use case, on the same endpoint, what have you. But if you (in your application) own the source of the data, then using 'as const' is good, because you get both specificity and accuracy. And since the source is in your application, if you change anything, TypeScript will warn you if it breaks anything.
@Starwort
@Starwort Жыл бұрын
Additionally if you _really do_ mean that the function will always return "yay" you can just declare that as part of the return type: `Result`. You can't make that decision with inferred return types so intent is lost (and changing the implementation can easily be a breaking change - what if we change it to return "yam" instead?)
@Rihsto
@Rihsto Жыл бұрын
Disagree about "User" example. You specify that User has at least username and email, ts does not care if the object contain more properties. But you can't return less properties. User type can be used in other parts of the system
@Rahman-xm7zm
@Rahman-xm7zm Жыл бұрын
I agree with you. Typescript does not depend on the origin of a type, when deciding if it’s compatible with another one, but it compares their members. If all the members of a type exist in another type with their appropriate types, then the two types are compatible
@CottidaeSEA
@CottidaeSEA Жыл бұрын
With the example that Prime gave you, the issue with the code was the same regardless of explicit or inference. It correctly said what was going to be returned, but the issue was that it lied about when what would be returned. It is a pure logic error, those will always be hidden when overloading. There might be a way to solve the issue, but the point is that inferred types will simply not save you from that particular problem. As for the primary issue I can see, it's that TS doesn't validate return types correctly.
@zzzyyyxxx
@zzzyyyxxx Жыл бұрын
Yeah this seems more like an issue with TypeScript not having a sound type system, in other languages with a sound type system, you simply cannot return a type with the password but lie about it as a User type. They simply would not let you return that and would error.
@CottidaeSEA
@CottidaeSEA Жыл бұрын
@@zzzyyyxxx Yeah, the exception is the Prime example since it has the correct structure, but that could also be solved with types if TS had better checks.
@jordondax
@jordondax Жыл бұрын
But, how would that be relevant right now? To be giving the advice that you should ALWAYS do X thing? If typescript currently works in x way now, why are we trying to force a hard rule(always a bad idea) when we know typescript doesn't work this way? Btw I have no problem with Prime, I just absolutely detest dogmatic statements. And lo and behold a deeper look and BOOM, hard rule blows up in our face. IMO you can't cop out( this isn't aimed at your or even Prime just a general) that oh the language works X way, and that's bad they need to fix it during a conversation on "best" practices. We have to operate within the confines of what exists today, not another day in the future if it even is resolved.
@user-pc5xt3gc7z
@user-pc5xt3gc7z Жыл бұрын
can't agree more... IMHO the very idea of explicit return type - is a contract which function MUST fulfill. And if TS allow function in the example return user with passport prop which does not exist in the return type - it's a bug of TS... And it's really surprising if that issue does not adressed/assigned for fix... and similar bug related to function's args too... type O1 = { p1: string } const fn = (arg: O1) => arg const o2 = { p1: '', extraP: 'WTF???' } const res = fn(o2); console.log('res', res) WTF TS??
@rendezone
@rendezone Жыл бұрын
It will if you pass an “as const” parameter and now you also have “const T” to achieve that
@UliTroyo
@UliTroyo Жыл бұрын
I used to infer my return types out of laziness, assuming explicit returns were the correct thing to do. Now I get to change nothing but feel markedly superior!
@UliTroyo
@UliTroyo Жыл бұрын
This being the web, I feel I should explicitly asterisk this as a joke instead of leaving people to infer whether I'm serious.
@matejaivanovic1587
@matejaivanovic1587 Жыл бұрын
Same bro, same ahahah
@user-km9tp4ii9p
@user-km9tp4ii9p Жыл бұрын
Returning more narrow type it is not lie, it might be an encapsulation in some cases. When you return some subtype but declare that you return supertype, you can hide some internal details and meta information that is present in supertype. So, in my example subtype is { username: string; email: string; password: string } and supertype is User (or { username: string; email: string }).
@AndreFera43
@AndreFera43 Жыл бұрын
I'm currently working with a lot of big, weird, any-heavy legacy codebases and I do think return types point to design decisions better than inference in most cases, especially in big functions/ methods. I would still stick with inference in smaller functions but having this tool to convey design in weird code is better I think. I wrote this comment before matt's statement, hearing it know I agree 100% with him.
@ShaharHarshuv
@ShaharHarshuv Жыл бұрын
5:16 I strongly disagree with the premise of this example. Type theory is like set theory, like a single value could be in multiple sets, so does a single value can have multiple types. In typescript, there is a concept of assignability, which basically means if type A is assignable to type B, than everything you can do with type B, you can also do to type A - so it's perfectly safe to treat a value a as type B, even if there is a stricter (narrower) type that it satisfies (like type A). This is like saying that A is a subset of B - which means that If element a is in set A, it's also in set B. Both are TRUE. In the example above, your value does salsify the "User" type definition. So it is TRUE that it's of type "User". That means that it is perfectly safe to treat this value as User, because it has all the fields you expect it to have. Aka - you're not gonna cause bugs if you do that. Type "broadening" happens all the time in typescript (and even in statically type languages) and it's one of the reasons this language is so powerful and flexible. All it does it trying to validate that you're making any mistakes (aka, not trying to read a property that doesn't exist). 6:01 You are of course correct that trying to access the password property in this case will result in an error, but your diagram misses the point completely. The circle are trying to represent the type domains of password, but in this case typescript is not even inferring the type of password. It's not telling you that the type of password is something, it's telling you that this property is not available in your object. The type inference does not happen on the "password" value, it's happening on the object value, which is broader than the "truth", which is fine (safe). A real "LIE" happen when typescript tells you some property exist when it doesn't, or a variable can only be a string when it could actually be a number. This is actually the case with the overriding example. However - I do not see any way in which typescript can fixed that without removing support for declaration overriding since it's very complicated to infer the runtime response of a function without actually running it. I have a lot of situations in which giving up on declaration overriding will make the code very annoying, like forcing me to use non-null assertion (which is less safe) / null checks redundantly (which complicates code unnecessarily). I prefer trusting the provider in this case. Types can never guard you fully. If you want to be more safe in these situations you can add unit tests.
@Nil-js4bf
@Nil-js4bf Жыл бұрын
The password example didn't fully click with me. By removing the return type, you get the smallest subset of the return type possible which means you get stricter type inference downstream. I agree with this. But your example is slightly disingenuous in the way it was presented because it makes it look like it would help prevent leaking the password but that's not true because of how type compatibility works in Typescript. i.e. When you create a function logUser(user: User): void, it's going to happily accept your user object with the extra password field in it without complaining. Also lmao, I think that override example only made Primeagen more skeptical of Typescript and reinforced his love for "actual" strongly typed languages. Imo, that example was closer to what Matt Pollock would describe as a "library function". It basically improves the ergonomics by giving you a stricter type based on what you pass in. With these functions, you just have to be damn careful that your typescript definition matches the implementation of your function. Functions with type predicates are another simple example of this.
@spy4870
@spy4870 Жыл бұрын
In my opinion the lie is, the Developer how says it returns x and then goes ahead and returns y. Therefore if you have honest devs, the set the return type and make the code have a proper function definition.
@christopherpoulsenfernande1624
@christopherpoulsenfernande1624 9 ай бұрын
It's not that the developers are dishonest, the point is that with explicit return types you are introducing more room for human error, which is bound to happen at some point and we want to minimize it, not increase it.
@brucewayne2480
@brucewayne2480 Жыл бұрын
If you use it as kind of TDD you write the return type of your function first then you write its body.
@AlecMaly
@AlecMaly Жыл бұрын
Great content, thank you!
@Vim_Tim
@Vim_Tim Жыл бұрын
As Prime pointed out on stream (2/7), this diagram at 6:00 is misleading. User acts as an interface which is a contained subset of the inferred return type of getUser() (“Truth” circle in the diagram).
@excalidraw
@excalidraw Жыл бұрын
We'd have to add boolean operations to make that diagram easy to create 😇
@moromann1
@moromann1 Жыл бұрын
These are quite intricate examples though that mostly make explicit returns look bad because TypeScript sucks. In most cases, explicit return types let you catch mistakes earlier. If I have a function which should return a string and I forgot to toString a number, an explicit return type will let me know right then and there that I made a mistake and I’ll fix it right away. With inferred return types this error may be discovered a lot later, perhaps by another developer, and more time will be spent debugging. Either way, I think we can all agree that these past few days of discussion have taught us something we can all agree with: a type system that doesn’t actually enforce its types is dumb.
@jordondax
@jordondax Жыл бұрын
I don't understand comments like this. Typescript is what we have right now as it is. If we want it changed we need to open a PR and make that change. When we come to a conversation we must remain within the bounds of reality. If Return types can blow your foot off, then it's strictly bad advice to be dogmatic about it. There are tradeoffs and it's good to always remember that. Typescript sucking has little to do with the convo, because that's irrelevant to the issue at hand. The issue was saying one way or the highway.
@uchechukwuokereke8497
@uchechukwuokereke8497 Жыл бұрын
I have seen this error in production. The function was meant to return a string, but someone forgot to call the "toString()" method after "parseFloat()".
@abhinavrobinson2310
@abhinavrobinson2310 Жыл бұрын
Me: can we have types at home? Mom: We have types at home. Types at home: const notX = x as unknown as any; 👻
@arcanernz
@arcanernz Жыл бұрын
Return types can convey the intent of the writer whereas inference types can be utterly useless if someone decides to return an "any"; and inferences can still be wrong if you were to do "return blob as User" when blob is not really User. But that being said it's a preference whether you prefer correct but possibly useless types or incorrect but meaningful types. I actually do use inferences most of the times but I do use return types for functions where the return types are not meant to be dynamic.
@alexodan
@alexodan Жыл бұрын
that ending was awesome, keep it up Theo always great content!
@peter8261
@peter8261 Жыл бұрын
Prime's mustache is going to get some crisp competition from Theo at this rate.
@lkjhoiuy97yjhgghfyrthgvjhguty
@lkjhoiuy97yjhgghfyrthgvjhguty Жыл бұрын
2. TS is not lying. TS has a structural type system, so it willl look at the return and see that “yeah this object has at least the things User has”. A better way would have been to write const user: User Then TS would yell when you try to add another key.
@anthonybarsotti2723
@anthonybarsotti2723 Жыл бұрын
Not sure I get the reasoning behind the second example, that’s exactly what return types are for. If you’d specified a return type of User you’d immediately see that password is not a valid property of that type. And if that should be a valid property why not just add an optional property to the type itself? It’s like the example assumes that this one function is the only place where that type would ever be used.
@Elias-vs2dx
@Elias-vs2dx Жыл бұрын
Easy solution just use plain JavaScript
@wlockuz4467
@wlockuz4467 Жыл бұрын
Preach
@PauloMiello
@PauloMiello Жыл бұрын
ok going into the video I was 100% sure I was going to disagree, because return types are the way to go, period. HOWEVER I didn’t know just how broken typescript type system is; I learnt it watching this. So in this particular case you are right, don’t use them. But it is only because typescript is broken: in any other language that is not, please do
@nekomatamune
@nekomatamune Жыл бұрын
In the context of this video, the benefit of the return type is to guard against the case when the so-call "truth" is incorrect (i.e. you have a bug in the function or whatever other functions it calls)
@yt-sh
@yt-sh Жыл бұрын
exactly, it helps in this case
@julienrenaux
@julienrenaux Жыл бұрын
You nailed it! Thanks for this video, I can now stop responding myself to angry people 😂
@pgrzybek
@pgrzybek Жыл бұрын
I agree that inferring is usually better as it's THE truth about the function, but I also use function declaration as contracts (as in the interface). It defines what is the function for and what is expected of it. In such case I don't want the type to be inferred because I want to detect if I'm accidentally returning something I wasn't intended to return (like "string | number" instead of just "string").
@echobucket
@echobucket Жыл бұрын
Right but in the password example the contract is that it should return a "User"... and type script just ignores the fact that you stuck "password" in there.
@pgrzybek
@pgrzybek Жыл бұрын
​@@echobucket I find this "password example" a bad one. When your function returns an object that fulfills the User interface it's a contract that you can use it whenever you need a user. It doesn't really matter if it's an AmazonUser or FacebookUser. In OOP you could compare it to polymorphism, though here it's better as no inheritance is required.
@hfspace
@hfspace Жыл бұрын
hm, if you don't mind the extra work, then for sure, you could make these examples quite safe, i think. When communicating with the backend by adding some kind of validation and by using the means of typescript to combine types and typeguards and such. So with work, you could do way better than in these examples. One reason for using types is that the typescript compiler is in some cases way better at displaying the real bug when they are used correctly. But i guess, if you don't want to invest that time and work into the type system, then you made a valid case for inference
@ordazgustavo
@ordazgustavo Жыл бұрын
So, the problem is TS, not the return types
@joshhoover1202
@joshhoover1202 Жыл бұрын
Pretty early on when I was learning typescript I realized this problem existed and because of it I always preferred inferences. What I have been doing (idk if it is a best practice or not) is, I will write up the function declaration often with a return type, but once I am done with writing the function I go back and delete the return type and then mouse over the function to see what the inferred type is and do a bit of a sanity check checking if it matches my explicit typing and if not, why is it different. I then leave it as is with just the type inference.
@thebutlah
@thebutlah Жыл бұрын
Typescript is structurally typed rather than nominally typed. This means the password example you gave is intended behavior. It's not lying - it is saying is that the type returned has *at least* those properties.
@bravelyjake
@bravelyjake Жыл бұрын
Do you think that final problem(user/admin) could possibly be solved by a library, similar to what Zod does with enums, or ts-pattern does with its exhaustiveness checking?
@anwarsjy
@anwarsjy Жыл бұрын
Nicely done
@dahlton8310
@dahlton8310 Жыл бұрын
I’m confused, wouldn’t you want it to error out when you’re adding a field to a type that doesn’t exist on that type? Since you never said it exists on the type? I don’t work with ts too much
@le_ch1mp
@le_ch1mp Жыл бұрын
OBJECTION. If you want a specific result truth you should use Result as return type. This way the value is typed according your expectation BUT the return type does what it's supposed to do imho: It helps you develop the actual fn that your typing. Like a little tiny test it tells me what my "return goals" are whereas "as const" is just a bandaid that types the fn from within its implementation.
@le_ch1mp
@le_ch1mp Жыл бұрын
Before you all go up in flames add the following: const random = Math.random(); if (random > 0.4 && random < 0.3) { return random } Hf when some junior decides to add something like that. With "as const" you cant catch that in advance - happy bug hunting. With the method mentioned above, ts will scream at you as soon as you try to return anything else than the typed values. Have a nice day
@le_ch1mp
@le_ch1mp Жыл бұрын
But I see you are starting to master the dark arts of controversial max-user-engagement content creation. Well done Anakin.
@DoctorSoulis
@DoctorSoulis Жыл бұрын
The type wars have began.
@PwrXenon
@PwrXenon Жыл бұрын
Explicit > Implicit, its the reason we switched to typescript because of javascript's looseness.
@JimmySting
@JimmySting Жыл бұрын
For the password example, wouldn't a generated error for the "return type" scenario be something developers actually want from a structural type check? I might be missing how this a "lie" when TS would be indicating (truthfully) that the the specified return type does not match the structure of the actual data.
@architbhonsle7356
@architbhonsle7356 Жыл бұрын
Isn't expecting `value: "yay"` a contrived example too? That would most likely change during Runtime, right? If not, then it should be defined in the `Result` type too. Maybe this is explained later in the video.
@tech3425
@tech3425 Жыл бұрын
That's exactly what I was thinking! He purposely made the Return type a more vague type. That's a straw man argument. This whole video is full of logical fallacies
@SonAyoD
@SonAyoD Жыл бұрын
Can you explain further?
@tech3425
@tech3425 Жыл бұрын
​@@SonAyoD He could've set the return type as {status: 'ok', value: 'yay'} instead of {status:string, value: string}. Not doing that is a choice he made consciously, to make the return type yield an inferior, more vague type.
@FaisalAfroz
@FaisalAfroz Жыл бұрын
@@tech3425 Exactly poor design choices I must say. If there is some info being lost that means the contract has been broken or the choice for types was too narrow. So instead of fixing earlier with explicit types he is saying that we should leak it for later.
@n4bb12
@n4bb12 Жыл бұрын
In the user examle, with and without return types, you don't get completion for defining the user. And if you use a return type and a property is missing, the error is on the return statement, not in the place where it's missing. What I'd recommend instead: If you want the user to conform to the type, declare it as `const user: User = { ... }` and no return type. You'll get completion for the properties. If a property is misspelled, you'll get an error directly on the wrong line. Similarly, if you return the result of an `Array.map`, pass the type to the map call instead of using a return type: `return array.map(item => ...)`. This gives you an error on the correct line instead of on the whole return. If you don't need the user to conform to the type, just return the object, and no return type. Either way, you don't need a return type. Just use types in the locations where you want errors to show up, preferably as deep down as possible.
@techwithattila
@techwithattila Жыл бұрын
Neat, weren’t aware of some of these TypeScript concepts. Need to step up my game. Might do something similar in Kotlin and see how that behaves 👀
@nikfp
@nikfp Жыл бұрын
Gotta say, as unpopular as this opinion might be I still agree with Prime. To be clear, I don't mean to be the "Actually" guy, but some things should be pointed out for further consideration by others. You might strongly disagree with me and that's fine, but it's important to see both sides of this. First, the Result type is actually a discriminated union, not a tuple as Theo mentioned. The status property on the return type is the discriminator and it's actually a very powerful thing as it allows you to narrow the Result type to one of two shapes. Second, the generic T in the Result type will specify what it can give back. If you want it to be 'yay' explicitly, that means 'yay' is a literal and NOT a just a string. You type the function with a return type of Result and typescript will enforce that your code return that literal. Third, these examples are all contrived as Theo points out. But Typescript is designed to help devs define the runtime behavior of Javascript. So using it like Theo does in the examples doesn't account for the dynamic nature of a running Javascript system. Third and as other have mentioned, Typescript types are designed to detect the presence of a defined set of properties, not anything extra. So by it's very nature the password example is covering up a bigger problem: why would you get the user with the password and then allow that object to be passed to an in secure part of the system? The only reason you would need that password is to check it against user supplied credentials, so do the check you need to do and release that object for the GC to clean up. If you need the other user info, either query only what you need, or query the whole object and then explicitly build a return object with name and email, and leave password out of it. And if you query only what you need from the data source, you have the added benefit of the password only existing in the JS runtime when it's needed, decreasing your possible attack surface overall. In Theo's example you can partially defend the password as he states, and his example will work. But in a real world, running system, you should be more explicit to only use what you need and leave the rest of the data at rest. One last point, explicit return types also better define the contract a function has with the surrounding system. By using strictly inference, you can introduce subtle bugs that only appear further down the line in the calling code. By adding return types, you can see at a glance what the function is supposed to do, as well as better safeguard calling code against breaking changes.
@Mitsunee_
@Mitsunee_ Жыл бұрын
the problem with overloads is that inside the function bodies all typechecks run against the implementation signature and IGNORES the overload signatures entirely. Literally everytime I try to use overloads I end up with the output typed the way I want but having to lie a bunch of times in the function implementation to the point where I'm no longer confident in my code, delete it all and figure out a way to use generics instead or just do something else entirely.
@JonathanRose24
@JonathanRose24 Жыл бұрын
My only regret, is that I have but one like to give. You nailed it in this one
@neilmm
@neilmm 11 ай бұрын
In the example of returning an ok | error result, what if you wanted to use a function to create the ok result or the error result and you also wanted the RT to be Result for readability instead instead of the union of two objects?
@benjamin_fdw
@benjamin_fdw Жыл бұрын
I was a C++ dev when I learn function overloading, I was still a C++ dev when I learned it was a bad idea... I was doing python when I learned that I can return different types with the same function, it instantly felt like a bad idea... Now I am doing typescript, functions get either one or two parameters (most of the time an option object with a bunch of ? fields and it returns a single type of value or at worst a type | undefined. In some very rare occasion it may be useful but most of the time you are trying to provide different answer for the same question. I am no phylosopher, I can only provide a buggy but consistent truth to each question.
@zzzyyyxxx
@zzzyyyxxx Жыл бұрын
This seems more like an issue with TypeScript not having a sound type system, in other languages with a sound type system, you simply cannot return a type with the password but lie about it as a User type. They simply would not let you return that and would error.
@davidhusicka8440
@davidhusicka8440 Жыл бұрын
This. It should be a hard error so you don't accidentally return the wrong type.
@user-hk3ej4hk7m
@user-hk3ej4hk7m 10 ай бұрын
You could also just do Result. It'd be great to have an option to specify covariance and contravariance of return types types in TS, as it looks like it should solve most of these problems. In the case of typescript not being that strict with types I most of the time remove the return type, check the inferred type and then add the type back.
@doc8527
@doc8527 Жыл бұрын
My ultimate "correctest" suggestion based on the video: - If you are Source Controller, you can safely do return type most of the times. - If you are Source Consumer, infer type might be your best friend. It's not just about type language, but also application and communication.
@thanhn2001
@thanhn2001 Жыл бұрын
If you didn't want the return type to lie, shouldn't you just declare a return type the tells the truth. Having said that, I really like the idea of not needing to define a return type and let inference do the work. I'm fairly new to TS so I wasn't aware of any of these dangers. I learned a lot. Thanks for the great content.
@n4bb12
@n4bb12 Жыл бұрын
I appreciate both of you. I enjoy learning from the reasons and arguments. To me it's not about who's right.
@JLarky
@JLarky Жыл бұрын
It's actually possible to test if return types do what you think they do... With unit tests 😱 and tools like dtslint and tsd can help as well
@eduardstefan6833
@eduardstefan6833 Жыл бұрын
Wait im on the phone so i cant check but shouldn’t TS worn you that you’re trying to pass a field that doesn’t exist on User type at 5:16?
@godofwar327
@godofwar327 Жыл бұрын
Theo summoned the avengers to win a Twitter beef, you love to see it
@christopherpoulsenfernande1624
@christopherpoulsenfernande1624 9 ай бұрын
A really good argument of why not to use explicit return types unless it's absolutely necessary. I just saw Prime's video on this topic and I have to say I find yoir argument more thorough and compelling.
@witchmorrow
@witchmorrow 5 ай бұрын
Can someone answer this for me? The industry standard for typing at boundaries seems to be to use zod library. Yet when you make a schema in zod, my understanding is that (unless you use `strictObject`), it will allow any extra properties that you haven't listed to be present, but it will 'cut them off' ie 'be lying' in the way that Theo has a problem with, ie if you try to access that property elsewhere, it will error. So if all these prominent people agree with Theo about using inferred const types instead, how does zod fit into that?
@bustamantedev
@bustamantedev Жыл бұрын
I think of Return Types like a contract for the function consumers of what to expect from there, which in my opinion is the better approach. The first two examples you're giving are because you're concerned about (1) narrowing the type values and (2) missing attribute in the type definition; those 2 scenarios sound like an incorrect/incomplete type definition rather than an issue with the return type approach
@t3dotgg
@t3dotgg Жыл бұрын
The problem is that the "correct" definition is overridden by the return type. It is TRIVIAL to cast over the truth with lies. "But if your code was correct it would be fine" isn't a real argument.
@bustamantedev
@bustamantedev Жыл бұрын
​@@t3dotgg You are right, the definition is indeed overriden. However, the return value still complies with the expected return type, it is not like you can just return anything you want. To be fair to your point of view, I too rely much more on inference than explicit return types but I just don't think those arguments are as solid as you think they are. Great video, keep it up 👍
@FATPENGUIN96
@FATPENGUIN96 Жыл бұрын
Was in the return type camp but thanks for the quality video Theo. Seeing all of the different ways TypeScript is broken compared with how you would expect a typed language to behave is a compelling argument.
@echobucket
@echobucket Жыл бұрын
Curiously enough if you manually type "const user: User = " inside the function typescript happily complains that password isn't allowed on this type. Why it's not doing that for the return type seems like a bug?
@ssshenkie
@ssshenkie Жыл бұрын
Great video 👍 this is painful DX, yikes. I wonder if typescript has some flags to make it more strict. PHP is quite flexible with types by default and allows you to make it stricter
@ParkourGrip
@ParkourGrip Жыл бұрын
I have to disagree with this video. 1) In the first example you can easily use explicit return type Result. A developer can also put a explicit return type Result if he want's to tell the caller of the function not to rely on the implementation detail that the string is always going to be "yay". That's because in the future the implementation might change and some other string could be returned. When that change happens all the code that used this function and that did not rely on this string to be exactly "yay" will not brake. 2) The 2nd example is the only valid problem in this video. However implicit return types still do not solve the problem that is stated. Even if the return type of the "getUserInferred" includes the "password" property, whenever you pass that return value to a function "sendUser" that expects a user without a password as a argument, TypeScript would still show no errors and the password would still get leaked. 3) The 3rd example does not argue that explicit return types are bad. It argues that TypeScript function overloadings are bad. You can still get the same vaigue return type by using explicit return types without function overloadings. The moral of this example is to use function overloadings only when the benefits outweigh the massive drawbacks, and when that is the case, developers should be extra careful. It's just like using unsafe blocks in Rust.
@yonavoss-andreae4952
@yonavoss-andreae4952 Жыл бұрын
Had the privilege of meeting Josh Goldberg a few weeks ago, incredibly talented. Loved his cameo!
@t3dotgg
@t3dotgg Жыл бұрын
Great dude! We went to college together
@yonavoss-andreae4952
@yonavoss-andreae4952 Жыл бұрын
@@t3dotgg my best friend just graduated from RPI last spring. I’m applying to transfer there for fall
@samuelgunter
@samuelgunter Жыл бұрын
getting ready to eat that prime steak
@loucyx
@loucyx Жыл бұрын
Thanks for doing this, Theo! Folks coming from languages like Java expect to use JavaScript the same way they used Java through TypeScript instead of learning to use TypeScript. TypeScript is the best when you type when it can't infer the type, so it is actually "JavaScript with Types" instead of "Typed JavaScript," which is VERY different. Not to mention that TS is there to help with DX, not to validate your implementation. Types are closer to being a linter than to being a validation or a test, as some folks think they are. The original video by "ThePrimeagen" had some glaring issues, mainly when you try to use his functions and get very bad DX (no autocompletion) compared to relying on inference which had a great DX. Again, thanks for doing this! Again, thanks for doing this!
@xDJxG0LD3Nx
@xDJxG0LD3Nx Жыл бұрын
If you try to return something that isn't the return type, it errors in typescript does it not?
@aleksandr2245
@aleksandr2245 Жыл бұрын
what about const Generics that will be introduced in 5.0? Won't they fix it?
@HHJoshHH
@HHJoshHH 4 ай бұрын
Super bad ass video concept to have all those Engineers give their opinion at the end of the video. That style of editing? video design? Idk but very cool.
@joeellul-turner1280
@joeellul-turner1280 Жыл бұрын
Can you do a breakdown of 'type' Vs 'interface'? Might be a silly question but I don't get the difference 😂
@CottidaeSEA
@CottidaeSEA Жыл бұрын
This is a bit of a simplification, but a type is a structure, an interface is a functionality schema.
@roys2970
@roys2970 Жыл бұрын
I believe a lot of the explicit return type mindset comes from mainly working in languages that are strongly/statically typed. Return types are seen as a contract where the "truth" is how well the code inside a function honors that contract. Correct types lead to correct contracts which lead to correct syntax that leads to correct code... Until your API sends you something that de-serializes an int into a null. I definitely have a lot to learn with working in TypeScript. Definitely appreciate the video.
@amigoAsterisk
@amigoAsterisk Ай бұрын
What is that plugin that shows types after “^?” ?
@xtinctspecies
@xtinctspecies Жыл бұрын
Anonymous types is what you get by inference.. some people including myself.. hate it.
@MaxPicAxe
@MaxPicAxe Жыл бұрын
Notice that the type of a deterministic function that is being passed with compile-time constants is the result of the function itself. I believe that we should still specify return-types if an API is open to changing, that is, we expose a certain interface that can have different implementations and we want the end-user to depend on this slightly-more vague interface rather than the specific implementation outcome.
@CodeCowboy
@CodeCowboy Жыл бұрын
What is that LF stuff you have (8:58) on your screen? (line 25)
@NoelmineZockt
@NoelmineZockt Жыл бұрын
You mean the // ^? Comment in the Typescript playground i guess. There is a vscode extension for this called twoslash queries
@tech3425
@tech3425 Жыл бұрын
@@NoelmineZockt Is there a plugin for neovim?
@asdqwe4427
@asdqwe4427 Жыл бұрын
As const is just another form of typing, if you have gone through the trouble of creating the type, why not name it? Having a pre defined type saves you a lot of time when you refactor though. I use as const a lot, but it’s more of a superior alternative to enums, most of the time. I would omit types when the return type is obvious and the function is small and not exported. But other then that I would leave the type inference to anything but return types.
@joshman1019
@joshman1019 Жыл бұрын
You may benefit from further study of TypeScript's structural typing system. You will find an appropriate resource, with a live sample on TypeScript's documentation page. Unfortunately, posting links is disabled and so I cannot directly link to it. You will need to treat a type in TypeScript as an interface.
@MrChickenpoulet
@MrChickenpoulet Жыл бұрын
8:50 i'm not sure this example is great to advo:ate against return type, as you mentioned we get back a "vague" type whereas with the specific return types we can be wrong (in the case you showed). BUT, if we type the `getUserInferred` to return a `{ role: User } | null`, we get a better return type, and it's correct: ```ts type User = "admin" | "user" const getUserReturnType = (role: User): {role: User} | null => { if (role === 'admin') { return { role: 'admin' } } if (role === 'user') { return { role: 'user' } } return null } const result = getUserReturnType("admin") // result is { role: User } | null, obviously, and there are no lies ```
@Imjoshnewton
@Imjoshnewton Жыл бұрын
This is why you have to override the user type in create-t3-app to access things on the user table without compiler errors.
@NuncNuncNuncNunc
@NuncNuncNuncNunc 11 ай бұрын
There's a bit of a lie here. @2:00 adding the return type Result is not overriding the truth. It is telling you what the truth is. If that is not the return type you want, don't define it as it is here. If "yay" is the only valid "ok" value you want, then clearly Result should not take a generic parameter. Getting rid of the generic and making all the attributes readonly is the "truth" you can define Result as to match the "truth" you want. Not sure there is any solution to Prime's example other than to recognize that overriding a function with different return types is inherently dangerous and overriding with a less specific return type is probably never ok.
@DrewLytle
@DrewLytle Жыл бұрын
I just love Kent C Dodds 😂
@adaliszk
@adaliszk Жыл бұрын
Interesting. I wonder though what is the reason to have super accurate representation of your data instead of its type? Like, once you try to tell typescript about how your data is mapped in various scenarios, it becomes less accurate and even lies as you have shown. I personally prefer using return types for enforcing a contract on my functions with a type, but without assuming what the data is, since that makes it easier to extend while maintaining the exact interface that the rest of the codebase expects. While inferred types does the same check within the codebase level, it does not block developers changing the interface itself if the stars align up right and they narrow down the use-case well enough that only one portion of the codebase is affected.
@morningstarsci
@morningstarsci Жыл бұрын
how is this any different than in other OO languages where if you cast to a super type you lose the subtypes data?
@echobucket
@echobucket Жыл бұрын
Waiting for TypeScript team to add a new tsconfig option "strictReturnTypes: true"
@Vim_Tim
@Vim_Tim Жыл бұрын
This is what linters (e.g. ESLint) are for.
@sabasayer
@sabasayer Жыл бұрын
I think one of the best thing about typescript is infering and we should use it more. Because the main code is javascript and if typescript does type checking from javascript code it will be better. There are scenarios that we need to define return type. Like api calls or JSON.parse()
@josephizang6187
@josephizang6187 Жыл бұрын
Thanks Theo
@Meow_YT
@Meow_YT Жыл бұрын
I mostly infer types, unless I'm not sure what I'm doing when writing the function and still deciding how it's going to work. If I know what I'm expecting to return, I'll type it, just to make sure I don't lose track and make sure the errors the IDE gives me remind me. Nya
@legotechnic27
@legotechnic27 Жыл бұрын
Imo return type is important to declare and document intent of the function to some degree. It also helps preventing mistakes in the implementation of the function, and even provide autocomplete/intellisense while typing return values. What would be interesting tho, to also address your concerns with having return types, is if you could specify `myFunc(...) satisfies someType {...}`. This would work similar to how the satisfies operator already exists in TS now, but applied to type checking of return values without overriding the inferred type of the function.
@lindit6849
@lindit6849 Жыл бұрын
How is null included in the truth? role can only be either "user" or "admin" and both those cases are handled. So we never reach the return null case. Typescript is the only language where I'd rather use switches than ifs in these kinds of cases.
@draconpern4132
@draconpern4132 Жыл бұрын
Wouldn't throwing the Error be better instead of trying to return a complex type that has an error?
@toddjudd9552
@toddjudd9552 Жыл бұрын
I wish I could smash that like harder! I've noticed this as an issue but couldn't figure out how to handle this.. using as const is gonna make things awesome!
@lkjhoiuy97yjhgghfyrthgvjhguty
@lkjhoiuy97yjhgghfyrthgvjhguty Жыл бұрын
1. You told TS that you were okay with the typeof value to be string. You could have used Result.
@zheil9152
@zheil9152 Жыл бұрын
All these nerd influencers can keep telling me not to type my returns, but it sure is easy to see the return type not match it’s designated contract of that function in that file immediately rather than change a function in a giant monolith and then wait for ts server to catch up 2 mins later, telling me the call of that function is breaking on its return throughout the code base because the return type changed when I messed up modifying the function. The “easier to lie” thing is such a non-issue if you type the variable in the function as User before returning it
@Krzysiekoy
@Krzysiekoy Жыл бұрын
This whole "I'm gonna say my last word and then "end the convo" feels kinda shitty. Not gonna lie.
@user-kw1lx9rf2s
@user-kw1lx9rf2s Жыл бұрын
True, the "I just want to end the convo" is just a passive-agressive dance-around way of admitting defeat and a lack of any based arguments.
@Ultrajuiced
@Ultrajuiced 8 ай бұрын
4:55 Structural typing vs nominal typing. 💀
How I Deal With Unsafe Packages
16:10
Theo - t3․gg
Рет қаралды 23 М.
The TRUTH About TypeScript Enums
12:04
James Q Quick
Рет қаралды 4,7 М.
Glow Stick Secret (part 2) 😱 #shorts
00:33
Mr DegrEE
Рет қаралды 48 МЛН
Do you have a friend like this? 🤣#shorts
00:12
dednahype
Рет қаралды 4,6 МЛН
FOUND MONEY 😱 #shorts
00:31
dednahype
Рет қаралды 5 МЛН
How NextJS REALLY Works
28:25
Theo - t3․gg
Рет қаралды 134 М.
TypeScript Wizardry: Recursive Template Literals
14:47
Tech Talks with Simon
Рет қаралды 36 М.
TypeScript Slows You Down - Here’s Why
5:44
Theo - t3․gg
Рет қаралды 38 М.
The Problem With UUIDs
25:53
Theo - t3․gg
Рет қаралды 146 М.
My Dev Environment Might Surprise You...
6:57
Theo - t3․gg
Рет қаралды 76 М.
Is TypeScript (NodeJS) Faster than Go?? |  A server comparison
9:54
ThePrimeagen
Рет қаралды 210 М.
These TypeScript Tricks are POWERFUL
10:44
Jolly Coding
Рет қаралды 3,6 М.
Готовый миниПК от Intel (но от китайцев)
36:25
Ремонтяш
Рет қаралды 405 М.
Apple. 10 Интересных Фактов
24:26
Dameoz
Рет қаралды 89 М.
Best Gun Stock for VR gaming. #vr #vrgaming  #glistco
0:15
Glistco
Рет қаралды 6 МЛН
Apple watch hidden camera
0:34
_vector_
Рет қаралды 4,6 МЛН