#Overloads VS Assertion
29 messages Β· Page 1 of 1 (latest)
Preview:```ts
// Option A
function f(a: number): string;
function f(a: string): number;
function f(a: number | string): number | string {
if (typeof a === "number") { return "hello"; }
else { return 0; }
}
// Option B
function _f<T extends number | string>(a: T): T extends number ? string : T extends string ? number : never {
...```
overloads are marginally safer, as at least tsc is checking whether the return value is a number | string (whereas with the conditional type return version you could return null as any)
in general i recommend not using overloaded functions though, regardless of how you type them. YMMV but personally i find it much easier to use and understand (as well as maintain) APIs where you'd have separate fString and fNumber functions
Agreed. But I think I had no other choice here: #1202658448425226301 message
what do you mean? you could have separate getSurprise and getSurpriseUsingDefaultParser functions
It is library code. I would like my user to import a function (the same every time, independently of their choice of using the default or custom parser). So I have to wrap these two function inside a main one that I export, right? If so, its signature would have to be overloaded, right?
well yeah if your requirement is "i want an overloaded function" then you need an overloaded function π
i was suggesting adjusting your requirements whenever possible
but like i said, YMMV
the other non-overloaded option is this, but you may not find the return type satisfying:
Preview:ts ... function getSurprise<T>( s: string, parseString: | ((s: string) => T) | ((s: string) => number) = defaultParser ) { return parseString(s) } ...
or parseString: (s: string) => T | number = defaultParser i guess is simpler
Hahaha sorry I didn't meant that in that sense
Yes exactly, the pb with this is the union type it outputs...
Thank you for your suggestions π
to be clear i was suggesting to not do this:
I would like my user to import a function (the same every time, independently of their choice of using the default or custom parser)
and instead export two separate functions (or alternatively export adefaultParserand have them explicitly pass it in)
Thanks for clarifying π
I understood this when you suggested it the first time, but I'm personally not a big fan of this UX/DX. I'll implement this idea and try it for myself to see if I change my mind though!
Would you happen to know a lib which uses this kind of UX/DX ?
A famous one, I mean
not off the top of my head, but i don't really pay that much attention to this kind of thing as a library consumer, and it's hard to think of libraries that don't use a feature (or use it sparingly) in the abstract. i'm also not sure how many "famous" libraries i use or what counts as one π
i can say i've been frustrated in the past by libraries that overuse overload signatures, because it makes it hard to abstract over their functions or extract/reuse their types (utilities like Parameters<_> don't really work with overloaded functions)
For people reading this later on, here is a discussion under Matt Pockock inital tweet about using any:
https://x.com/eyelidlessness/status/1773394643465781476?s=20
@borisfyi @mattpocockuk One last thought: itβs often possible to break the super dynamic cases down as smaller less dynamic functions, getting back the possibility of safe overloads. That can be a perf tradeoff BUT itβs not always intuitive which version will win (monomorphic often == faster)
Going to just move the discussion here.
Yeah both options are not safe, option B obviously is not safe because of the cast, while option A has no protection for your implementation. Eg even though the signature says number argument should return string, you can still return a number and completely explode at runtime.
The point about overloads ruining type manipulation with Parameters<T> and ReturnType<T> is also true for both options.
The advantage of not splitting a function into two is mainly in that consumer of the function doesn't have to change their imports if they want to change which overload to call
But the downsides imo far outweigh that minor advantage for the most part.
For application code, I will always suggest splitting; for library code, you'd have to weigh the pros and cons and decide which choice to make.