Exactly. If something looks confusing, it should be refactored to not be. This is a major reason why we have types to begin with - remove mental overhead. If you are adding mental overhead from the types, that should signal you to refactor your code to be manageable in your mental model. The answer isn’t to just pretend that the confusion isn’t there by hiding the confusion in type inference.
Great breakdown.
I strongly agree with adding explicit return types. Sure it helps you write the function initially, but it's main advantage is guaranteeing that when 5 other people mess with that function in the future, that if they accidentally change the returned value, it shows the error IN THE FUNCTION, rather than in the calling code in some other file.
And for projects which are not 100% fully typed (allow the use of implicit any), it's a must. You have to make sure that at least YOUR function is typed and always will be.
A few days ago I saw a video advocating for dropping return times most if the time. I totally agree with your assessment. The more type declaration the better, since it's automatic documentation for the next Developer.
Just use inlay hints in your editor, it's *actual* automatic documentation for you and the next developer. If your editor doesn't have inlay hints, upgrade to a better editor, it's 2023 ffs your editor should have inlay hints!
I just make sure the inlay hints generated look reasonable. If they don't, I create a type alias and add it as an explicit return type. My code is safe, it looks cleaner and it represents the true form of the returned element more accurately.
@@JeyPeyy depending on your company, you might not have control over your editor, plus, code views in GitHub (for example) won't help you either. Also, if by chance you add an "return" in an if statement (for example) by accident, the whole return type of the method changes, and chances are, you won't even notice it, since the method won't break. You'd have to rely on the code invoking the method to break, or rely on you detecting it with your inlay hints by chance.
@@JeyPeyy so you are working around things, basically. Just use the damn types, they are there for that reason
@@javierflores09 I'm not working around things, I'm using the strength of the typescript type system. I speak from experience and knowledge about how the type system works: in many cases you'll write a worse type than typescript can infer, and you'll suffer in some other part of the code due to it.
It's not about being lazy, it's about writing more maintainable code.
Next TS hot take: DON'T use types in TypeScript, writing without them is so much faster!
I'd ask: "faster" than "slow", or "faster" than "without return types"?
Because, guys, also convincing business to ditch the idea and not code the thing at all... is faster.
TypeScript is too typed! Let's come back to Javascript unconstrained foot-shooting universe! It's faster!
(yes, faster to write down.... but slower to debug and fix and keep coherent, after the fact)
You're joking, but in a language with good type inference (e.g. basic implementation of Hindley-Milner), you can absolutely have a program that's completely typesafe without actually writing any types yourself. In fact, it would be perfectly feasible to have a statically typed language like this that doesn't even *allow* you to write type annotations.
That said, in practice there's two reasons not to do this, 1. types are good for documentation, and 2. most languages have features beyond Hindley-Milner, e.g. typescript's union types and inheritance
They should make a language just like typescript, but without strong typing, it would be so much better
Respectfully disagree. But if IT works for you... by all means!
I like explicit return types more, but the first argument about not seeing return types for old / foreign code is puzzling. You can just look at the return type in your IDE with hover / virtual text LSP feature. Kind of similar to the old C convention of prefixing variable names with its data type, or C# 'I' before interface. I don't think these conventions are used anymore, only because of how powerful IDEs (or text editors) have become. I 100% agree with the other points though
You can but sometimes the return type gets messy. Basic example:
type UserData = (AdminUserData | DefaultUserData);
function getUserData() {
if (x) return { … }
else return { … }
}
The annotation in the IDE will not show the UserData alias. This can be confusing especially when types are big since it will truncate the type with “…”
@@benheidemann3836 from the other side, seeing alias may make things more clear but as well it may be a source of additional confusion
One of my favourites of your videos for a while. How to use types, made the case for why you should use em.
I like this format. Clear, concise and educational.
Thanks for taking the time to explain your position.
I didn't even realise this was the second channel!
The live conversation with Theo was actually amazing. I always feel like I'm learning much more when I watch that type of content than any other.
Hey, is the live conversation between the two available somewhere? :) Sounds like I could learn something from it, as u describe!
My flow is define a function (i.e. name, inputs and return type) then implement it. I already know what type I want to return and, by specifying it upfront, the type checker can help me implement the function successfully. Leaving the return type implicit means I'll realise I messed up when trying to _use_ the function, not when _writing_ it.
Thanks for this. Was watching through a typescript course where the guy just implicitly returns everything, and I was second guessing myself (who started off by explicitly typing everything).
Saw the argument with Theo first before watching this vid. I completely agree that defining return types will improve ones codebase in the long run. Implicit returns can be useful when dealing with inlining functions or factory functions/methods.
A step further is to use function overloading and match input data with the return type. Makes it clearer what role returns what type.
With generics, you can also have the correct shape returned based on the input which is better than having a union of types where you have to place guards outside the function too.
could you show how the function would look like with generics? Would appreciate
Darnit you've convinced me.
I had these sort of issues with TRPC. I really like the library but the types are incredibly opaque and inferred to hell and back.
I eventually gave up extracting some common logic because wrangling the types was such a pain. Liberally employing typeof... Paramaters... ReturnType... etc. was exhausting. Also, I think this ties into the Svelte Authors latest preference for writing libs in javascript with a 'facade' of types. Libraries are better tested with unit tests than types and types tend to simply get in the way more that they help. Libraries should rather focus on DX (and that includes well-named types). I would still prefer Typescript for building libs but he's not wrong. I think that explicit return types in applications do make for better DX, and AI tools like copilot know how to autocomplete the return types.
Love both your channels, but 100% agree with you in this case.
Love these vids
You should do this in Python too. I have saved myself SO much time, especially coming back after six months, or a year, to add a feature. Oh ok, I just need to input this thing into this new feature function and we are rocking and rolling. It also saves time for tests too. Types are just awesome!
Since I'm working almost exclusively in C I really can't imagine _not_ specifying what you wish to return if you have the option to do so. It simply makes things a whole lot easier to read and understand.
I see some here mention that their IDE or LSP will show the return type, but if you're reviewing code somewhere which has limited access to such things you're really making things difficult for everyone...
I use implicit return types for simple functions and explicit return types for more complicated functions, especially if that type is potentially useful elsewhere, since I'll have to be exporting that type anyway. I guess I don't feel the need to explicitly write that a math function returns a number if the actual calculation is 1-5 lines long. But functions that return objects, or unions of primitives, or anything more complicated than an existing type, I might need that type in a different part of the code where I'm actually calling this function (such as a React useState hook).
My hot take: I use explicit return types in some places and then in other places I think it is better to use implicit. Both are useful!
Yeah, for simple 3-5 line functions I feel like the return type is not that important. I still put them most of the times, but if a functions is lacking an explicit return type, it better be a function I can understand in under 10 seconds
Agree 💯 but how do you approach TS “specifics” like having weird behaviours on return types or God forbid “any”? Do you adapt depending on the type system?
It's simple as that - without types you won't be able to survive in really big and complex project. You can avoid types if you are experimenting with something or your project is small, but in complex domain you either use types and feel yourself comfortable in Production or good luck and have fun!
Honestly I see both sides of the argument, but the only reason this is even discussed is because of the flaws of TypeScript. It just makes me sad that you can't really trust the type system, which makes me crazy. I just think the TS/JS ecosystem has a lot of benefits over other languages and makes if worth using for now.
Since JS I was already using StandardJS and when I moved to TS I keep using it, as one of the rules for Standard for TS is to explicitly declare the return type, I never have used inference for functions. Points on Standard for that!
It is so interesting but also so confusing! Both Theo's and yours, Prime, arguments seem so VALID whey you talk about them :) It is hard to choose from! For the backend code - I do agree with you, Prime, because I personally wrote a small backend for one of my freelance projects using explicit return types in Express functions serving REST api. But for the frontend code I would like to inter return types, as Theo is doing using zod and tRPC ;) Obliously I didn't worked in a big projects, where type inference would slow down TS server to a halt :), so I'm kind of a noob here ;)
I find it can help catch bugs within the function itself. If you forget to return the right thing in some branch of the function, it shows the error in the right place. With implicit types, the errors are thrown ouside the function.
I write F# and the F# language is famous for having type inference and inferring types for you.
I still type all my parameters and return types because it makes it clear to the compiler what I'm actually intending to do. This makes debugging compiler output much easier to understand.
Explicit typing is also faster. Much less work for the LSP to do. If you open a file that uses this function, TS only has to go as far as the function declaration to get the return type. Otherwise it would have to go through the whole function, even if this file wasn't even open.
In a few dozen files, it doesn't matter.
In a large codebase, especially with lots of hot reloads, your TS Server will be many steps behind you if it runs slowly.
TS gives us the illusion of type-safety.
Matt Pocock agrees with you on this example! If you have if statements.. he recommends adding return types..
Honestly, it’s cool to see how dynamic typescript is that we can have conversations like these even though it’s obviously wrong to never include return 😁
Verbosity people. I've always been as explicit as possible with types in TS projects and it's served me well.
the thing is that when you find that you haven't got them right, its because your thinking of types was wrong. that is a GOOD thing.
We can always hover over the function name to know the return type but giving return types does helps
Something I've learned from writing quite a lot of fennel code
(*Fennel is a dialect of lisp that is written in (and works a lot like) lua and thus doesn't have types is to put comments at the start of most functions (technically it's a docstring, which is a part of the language itself(actual fennel code): "
(fn add25 [args]
"Takes args and returns args + 25"
(+ args 25)
"
, but it can be replicated by a comment )
It's just nice to have the errors of "we changed the contract for this function" be at the function itself, not in some other functions somewhere else.
Especially when you didn't intend to change the contract.
I agree with this totally, typescript is meant to be used as a guide and not for intellisense purposes. People who want inferred type just care for the intellisense feature of typescript. Statically typed languages have type guard as a core feature code intellisense will and always will be an add on
Absolutely agree. Programmers nowadays cannot even think without LSP. Explicitly typing out stuff to make the code easier to understand is a confusing concept for them. Because we can hover over and see the inferred types and all we don't care about the ones who do not use these features
Hey Prime-aging you're absolutely right man! And I suppose it all comes down to what project you are working on, types are a tool - use them in your advantage. If you're hacking together a super simple React app with super simple data, go ahead and just use implicit types be my guest; if on the other hand you're writing a library or a module with complex logic probably you want to go with explicit types.
As you said, explicit types are all about expressing your intention. So it also depends on how many people contribute (or will contribute) to the project. If it's a solo thing and you won't come back to it in the future hack it however you want; if it's a joint effort, perhaps it's easier to express your intention with good explicit types instead of having people come over to you every 10 minutes to ask "Hey man what was this function intended to do when you wrote it one year ago? Because I'm going to fiddle with it".
The example you gave is a bit of a complex function which for sure deserves the extra care of explicit types, there's no question about it. Keep up the good work!
"explicit is better than implicit" is part of PEP 20 - "The zen of python".
And I hate it so much, the fact that this is in the zen of python, that is. Because python is full of implicit stuff.
Zen of python:
- explicit > implicit
- only one - and obvious! - way to do things
- Special cases aren't special enough to break the rules
and yet we have stuff like implicit string concatination:
def f():
return "hello " "world"
The Compiler compiles down the "hello " "world" into a single constant "hello world" implicitly. This is an exception, where with other types like number you have to explicitly add them. And this is all over the language.
Big fan and didnt know about this channel. #JustSaying
yeah! its me trying to make content that is "easy" to make
i want to talk about a lot of things but i don't have time to 8 hour edit + 5 hour script write. instead, just turn on the camera and just spout my raw feelings.
I see your point here, but I would not agree regarding readability part (on the function declaration) as you can just hover over the function and see that it does return even if implicitly typed (atleast in vscode). Though, I agree, it still may cause problems because there are no guards against returning wrong type.
but i would argue back, using your LSP to tell you what your function returns has to set some flag off in your head of "... hmmm"
Agreed on the return types!
I'd upgrade the return type to use discriminated unions, though.
What do you do as a newbie to the codebase? You hover the function name with your mouse and get told the type by your IDE. However, I also think explicit is better than implicit, because it is a contract. If you're not explicit about your return type it can change without you noticing screwing with users of the function.
Thanx best of luck.
I agree 100% although I will say in VS Code, intellisense tells you the implicit return type when you hover your mouse on the function definition.
great vid
Ultimately, the return type is part of the contract and if you are leaving information out if that contract, it becomes harder to figure out where things go wrong.
100% agree with you Mr Prime!
Having a well defined interface contract for classes/function allows for dependents to rely upon this contract. Return types are en essential part of if ! Being explicit allows for a clearly defined architecture/code, enables better maintainability, and serves a purpose as a documentation of what was the intent of the developer.
As we developers may often fail to express our thoughts in code or align the code with the requirements, I find it better to not rely on the compiler but trying to document how we conceived the system by defining types/interfaces/contracts of all kinds. It also allows for easier code review. It avoids you to scratch your head in the next 6 months when you have to introduce a new feature and make the code evolve, cause contracts and return types helps you read what this is all about. Development is not just about code, it's about structure, logic, and clearly expressed well-articulated concepts.
I think Theo missed that point and will one day end up with a big legacy PoS, and that's his problem ! But I'm not fond of his devil may care attitude, kinda dangerous for the new minds in the game of development IMHO.
Prime, you are the voice of thruth. Thank you for taking the time and effort to debunk this.
Anyone who has graduated from an CS department will 100% agree with you
Even in the greatest type systems (so, not typescript, but anyway) typing the return type will save you from a lot of ridiculous type errors
I prefer explicit typed functions, for some reason it forces the dev to think more about the inputs/outputs of that data and help define a clear contract for that work. As they say lock that shit up and ship it out
What plugin is prime using for the ohmyzsh like autocomplete suggestions that we see in his text editor?
In case you're using a modern IDE you'd just hover over that function and see the implicit return type, meaning you have to weight the arguments again.
Prime > Theo
I largely agree with you.
Use implicit types for basically everything, but always explicitly type function return values - the benefits outweigh the cost. It also makes me pause and think about what the function should be returning, before actually starting to flesh it out.
I think finished code should always have explicit types. Explicit types are in-code documentation that can actually be confirmed by a static analyzer. While prototyping, implicit types are convenient (maybe you're not sure what the type should be), but once the code is "done," types should be made explicit for documentation and a first check that the code is correct.
I feel strongly that your opinion is correct
Maybe write the function then have something auto gen the return type and confirm it to lock it in place. I only type things now when it’s a complex type beyond a few params but it’s hard to make a rule to say “don’t type it unless…”
Handsome man
I get it but you could just hover on the function to see what it returns, usually the typing is in the variable where you are setting the result of the function so breaking things because of not typing the function seems unlikely
I mean, why use TS if you don't use one of the best features it provides... Types?
I mean you can use JS if you don't want to do types.. I really like your approach even tho I work with frontend for years, I see the benefit of doing this and in my company we even have a rule enabled where you MUST provide a return type.
@@ThePrimeTimeagen yeah, and we have those type of functions.. it gets annoying tbf with some function that return Promises but still it forces you to have a type safe function and always know what is being returned and not say "oh i hope it does not break here".. I've been too long in JS world, so I don't trust JS to get anything correctly
I agree with you 💯💯
With software engineering, especially in teams, "intent" is imperative and types (especially with a language like JS/TS where general use can clobber anything) are declaring intent. I'd also like to bleed into this same mentality when it comes to refactoring/updates etc ... and the monolithic commits (version control commits) ((RANT)) I understand PR's can be big but in refactoring and large PR's you should do the minimal amount of work to retain functionality then commit .. making point in time milestones that are more easily digestible during reviews and also your own development ... I commit often and sometimes find myself combining commits after the fact .... stop the monolithic commits!
I think it's wild that people are trying to come up with generic rules for all different types of functions.
- react components
- api calls
- functions in side [].map
- functions handed off to libraries or the DOM
- pure functions
- main functions in side index files
All need different appraoches to types.
My own soap box is that people over type things. Type the function, not what your expection of how it will be used is, then Typescript can then compare the types and understand if the types are compatible.
Viewing all these different point of views, i'd have to say using return types is the best. You did miss one thing when explaining, and I know you don't use a mouse, but if you hover the function, it will show you the return type, so there isn't any point in reading the code to find each return unless your IDE is behind the times. Now with that being said, you can easily break what ever that inferred return type was, and you'd have to go hover the function again to make sure the return is correct, even make sure a null or undefined didn't sneak in there, but that brings me back to just using return types as it would just give you and error if you break the return type contract. You can also write your function first, then copy and paste the inferred return type if the function is return some crazy unions you don't feel like typing out.
Excelent video I love it. How do you replace variables names in vim??
Always do what other people in the project do, if you want change, run it by the team.
Team Prime on this one, especially the part where you talk about going with whatever the team you are on is doing. In my own work, I'm very opinionated. In a team setting I'm fine with whatever the team has agreed on. But if the team is all doing it differently, I do have a problem with that and generally try to push for us to come up with a way we all agree on. And if no one has opinions I default to mine.
Personally, it's fine not to specify a return type for simple 1-5ish liners that return string/number/bool, but everything beyond that gets a type
I do agree with you that types should be explicit. However, I don't 100% agree with your reason of a new dev having to inspect code to understand what is being returned.
In VS Code, you can hover over the function name and the code editor will inform you of the return type of the function. So a new dev can just hover over the function and understand the return type.
But then it is up to the developer to make sure if they make any changes, the return type is same as before (again, by hovering over the function name).
I agree. Also the "ask another dev if return type xyz is valid in certain situations" will probably lead to nothing, because it is rather unlikely everyone keeps all this code in their head at all times. In most codebases the other dev will likely have forgotten most of the intricacies of certain code after a couple of days or weeks and the person who knows this code best will be the one asking the question at that point.
I am still absolutely pro explicitness, but I as stated I don't think the reasoning here is particularly helpful...
This debate about return types reminds me of how you and Theo disagree about unit testing. I respect both of you imensely and try to tune in to both of you as often as I can, but I think you are correct in both discussions. Type safety is there for a reason, use it or not.. Like you said yesterday, you each just have choose where and to what degree you want to be bit.
dont know about vim but the implicit return type is revealed in my editor when I mouseover it
Well, the thing is, your cool code editor doesn't show return types as hints, but WebStorm does, and I guess VSCode does it too.
Agree. Explicit is always better imo.
As a PHP dev I 1000% agree with this take. Return types make it substantially easier to be able to know what you are trying to accomplish with your function and more often than not will prevent you from making silly mistakes. Working with a codebase that has weak return types used for the sake of documentation is such a terrible experience to deal with when you have to deal with runtime bugs happening in your codebase that are coming through because nothing is preventing that code from being seen as an error from either your IDE or from whatever intellisense you're using.
The argumentation against using types specifically in typescript seem at least to me to be top priority bugs that should be addressed immediately since return types can actually lie to you, deriving how truthy a value actually is seems arbitrary to me in the exampels theo counters with as ultimately if your expecting a specific type of value coming from a type, your likely going to be wrapping that value in a conditional to check if its true, otherwise handle it in whatever different way that you're looking for.
You should probably do that after writing the function unless you have a precise requirement and then you must do it first maybe 🤔 😅😅
I vehemently oppose overriding TS inference because of the fact that I can (A) hover/highlight variables to get typings if inlay hints are disabled, (B) am rarely returning complex types from functions, and (C) am usually wrong when writing initial implementations.
An isolated instance of a refactor is a rarity. I typically need to touch 2-3 functions, and if those functions are returning data generated from a nested function. This either means trivially definining poorly-abstracted types for groups of related functions, and all the wasted time on naming that results from such, or being required to touch each fn manually, leading to longer PRs to scan through without meaningful changes.
LSPs are supposed to save time. If we're not going to bother using their features, why bother with anything outside compilation errors in our terminals?
I always thought that implicit return types are an indicator of technical debt somewhere down the line
100% agree here. Code is for people and this costs nothing performance wise
I would agree with this except Theo made a really great point about how TypeScript return types can actually exist exclusively from their runtime values. The fact that this departure is possible is a bit of an issue with the type system itself, but if implicit return types alleviates the risk of type lies, then I think the price is worth paying. Why use types if they can lie, might as well infer them and be more correct.
he didn't, re watch, he mistakes "different from" to "doesn't include all," of which is a regular pattern that people use all the time in every language ever existing.
if you use return types it doesn't lie. now you are going to make me make a video.
but he did show was that overloading is clearly broken and i told him that it was clearly broken
@@ThePrimeTimeagen I see, I guess I didn't understand it as much as I thought I did. Will have to dig deeper into it.
what colorschema are you using now, good sir?
I agree
As a VSCode user I would just hover the function name and see what Typescript thinks the method is returning. If your variables are typed properly then your functions and methods will be auto typed properly by Typescript.
vscode user, emacs, intellij, vim, helix, they all contain this. its "hover" implementation from the LC.
@@ThePrimeTimeagen So wouldn't you be able to see the return type from there? I can see the benefits from a "hardening of the codebase" perspective but having all these hardcoded types everywhere will slow down devs in the future as things iterate, at least thats what I've run into in the past
you didnt puts the long code block into if statement, do you?
Conclusion: write short and simple functions and 90% of your bike shed is done.
The only Type we need is Coconut Oil.
With VSCode and other editors, if you don't explicitly declare the return type, you can simply hover the function name and tooling tells you the return type. Can't vim do that???
Vscode and the compiler, can infer what you are returning, and that is what they do they infer, defining a return type indicates what you intended to do. I as the developer are saying this is what I intended rather than the compiler saying this is what you did.
It is easier looking at a function declaration and knowing what it should do, yes you can do this with inference also. But there are many other benefits, refactoring etc, and isn't this what typescript does partially, introduces types in a non-typed language if you are typing params then why not type returns.
The reason why not adding explicit return types works is because tooling like the ide/editor are getting smarter, and the complier is getting smarter.
You have confused many techs while making the argument.
VSCode knows nothing. TSServer does the work. The LC in VS communicates and that's it. It's the same tool in vim
@@ThePrimeTimeagen I partially agree with your response, I don't know if I confused anyone. However you are correct, that these editors be it vscode, vim or whatever are reliant on other parts to do this like a LSP integration whether that be with tsserver or whichever provider. The cohesive nature of how this works leads people to believe that their editor is responsible and this could have been better clarified by me.
The same tooling which does the inference, is also the same tooling which does the type checking on declared types. I believe being declarative has more benefits like function/method design, and refactoring.
I just hover over the function to see what it returns
What about boilerplate code
what font he use?
There are only a few cases where I think return types should be avoided, or narrowed:
1. If you're returning a union of primitive constants, you probably want to know exactly what those consts are and generalizing the return type to say, string, would detract from the experience from the consumer side of things. In that case, either explicitly define the return type, or leverage inference.
2. Some library hooks can have absolutely gnarly return types that offer no benefit when explicitly defined. You just end up copying your inferred return type. Maybe it's worth it for refactoring purposes? Maybe not. In any case, the DX sucks, so I completely understand just not explicitly defining those return types.
with implicit returns, won't the LSP hover tell you the return type?
No, it tries to guess the return type based on what the function is doing, but that might not be the full truth. For example, a property on the returned object might be optional, but it just happens to always be included right now (but that might change in the future).
You’re right explicit is obviously better. However even that perm return type is gross and would lead to horrendous code. “Because I know what you are I know what you do” is a terrible thing to have in your programs
i can see a case for private helper function, but for pretty much everything else it should be explicit.
I don't understand the mindset behind loose typing. Types make me think much more clearer about everything that's going on. Like return types help channel my problem solving and helps so much with in the long run. I don't get the other side
Dynamically typed languages are a true nightmare. I hate this in python as well. That's why not never use python for anything serious that other people might use at some point. I'm not in the web dev space, but as an outsider, it's truly a feat that a language like javascript with its lose type system managed to take over the Internet, even at the enterprise level.
No wonder I have to close my browser window and delete cookies if I want to log into the Microsoft teams account of a different organisation.
I agree with this. Recently I've been looking back on some old typescripts projects I made years ago and having no return types or messy types everwhere it took me ages to understand what each function was doing, and after refactoring them to have clearer types it was so much easier to undesrstand
thats how i landed on this position as well.
i loved the convenience of everything,... then... i realized what i had done
Yeah, as someone that has to support someone else's code, I'm hunting logic left and right to understand what's happening
@@oskrm getting the error of { /* some long implicit type */ } does not match { /* some other long implicit type */ } is just the worst
I'm going to play the devil's advocate here but, you can't tell if it's objectively better immediately after refactoring your code because of course it's going to be clearer at that point.
You need to wait days, weeks or months, come back and then judge if it was better. Hopefully it should be better, or you messed up.
Half of programming is about a clear understanding of Input & Output. The other 50% is the processing inside the algorithm, whatever it is about.