-
Notifications
You must be signed in to change notification settings - Fork 226
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
add overrides for type guard predicates #107
Conversation
where
override for type guard predicatessingleOrDefault<TDefault>(predicate: (element: T, index: number) => boolean, defaultValue: TDefault): T | TDefault; | ||
singleOrDefault(predicate: (element: T, index: number) => boolean): T | undefined; | ||
singleOrDefault<TDefault>(defaultValue: TDefault): T | TDefault; | ||
singleOrDefault(): T | undefined; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are two fly-by improvements for the ...OrDefault
functions here.
- It is common to do something like the code below, which uses a different type for the default value than the type of the elements of the input array. However, the pre-existing definitions required that the default value be a
T
.
const input: number[] = ...;
const output = Enumerable
.from(input)
.firstOrDefault(null); // this would not compile because `null` is not a `number`
- With the old definitions, the return value of these functions included
| undefined
even in cases where the default value was supplied.
const input: number[] = ...;
const output = Enumerable // `output` is a `number | undefined`, but it can only possibly be a `number`
.from(input)
.firstOrDefault(123);
As I was adding support for type guard predicates, these weaknesses came to the forefront. If I were to follow the pattern of the pre-existing functions, then the new overloads would be like the following. Note that the return type is T | TOther | undefined
, which allows for the type guard to work but still confuses the point by insisting that the default has to be a T
, and insisting that the return value could be undefined
even if a default value is given.
firstOrDefault<TOther extends T>(predicate?: (element: T, index: number) => element is TOther, defaultValue?: T): T | TOther | undefined;
By adding the additional overloads with generic type TDefault
, the default value is allowed to be any type appropriate for the call site, whether that be a T
, a TOther
, a null
or anything else the developer uses as the default value. And by changing the optional parameters to explicit overloads, we can adjust the return types for the different scenarios to narrow the type in the cases where the return value cannot possibly be undefined
. The end result of all of these changes is is enablement of code like this:
const input: (number | string)[] = ...; // `input` is a `(number | string)[]`, something like `[23, 42, "foo", -1, 452]`
const output = Enumerable // all good! `output` is a `number | null`
.from(input)
.where(isNumber)
.firstOrDefault(value => value > 0, null);
59d04f5
to
43298c6
Compare
Apologies for all the force-pushes. I spotted some nuances in the types that needed additional ironing out. I am now done with this commit. |
Nice, thank you. I'll release it later this week. |
Prior to this change, there was no advantage to using a type guard as a predicate in any of the functions that support predicates.
Example for
where
:With this change, the type of
output
isstring[]
.Example for
first
:With this change, the type of
output
isstring
.