-
Notifications
You must be signed in to change notification settings - Fork 12.8k
Reduce without an initialValue should be illegal on a potentially empty array #34736
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
Comments
How about we just delete the It's rare that one uses the For example, //Zero is the identity element of addition
arr.reduce((result, item) => result + item, 0);
//One is the identity element of multiplication
arr.reduce((result, item) => result * item, 1);
//Empty object is the identity element of adding properties to stuff
arr.reduce((result, item) => { result[item] = 0; return result; }, {} as Record<string, unknown>); I'm just kidding about removing the signature entirely, by the way. Of course, there are moments where there is no identity element, arr.reduce((result, item) => result / item, /*no identity element*/); |
In fact, there are no moments where there is no identity element, I wager. It would rather be the developer exploiting the mathematically clunky version of To demonstrate, the above example, arr.reduce((result, item) => result / item, /*no identity element*/); …translates to English as "the first element of the list, divided by all others in turn", a sentence where I notice the part "the first element of the list", and which therefore leaves a gaping hole in the definition for the case of empty lists: it is not defined on them. Emphasis on the fact that the first element is treated very differently from the rest, which should somehow be visible in the implementation. I believe the example is therefore more mathematically put as: arr.slice(1).reduce((result, item) => result / item, arr[0]); And the reference to the first element makes it clear as day that there will be a problem if we have nothing in the list. I believe that TS should only allow static references, e.g. let foo: [T, T, T ...T[]] = [9, 12, 4, 5, 14, 7];
foo[0], foo[1]; // ok
foo[2]; // ok
foo[3]; // error I explore this because the topic of arrays with some guarantees on their length isn't irrelevant to in-depth handling of the present issue (which I'd very much like to explore solutions for). |
Would be cool to have type guards like, declare const arr : T[];
if (arr.length > 0) {
//arr is now `[T, ...T[]]`
}
if (arr.length >= 2) {
//arr is now `[T, T, ...T[]]`
}
if (arr.length == 4) {
//arr is now `[T, T, T, T]`
} This way, the overload for I think there's a proposal for such a type guard... I'm not sure. [EDIT] Found it |
This level of enforcement, while coherent, would be far far above anything currently on the table, to say nothing of consistency -- even today Many times arrays are known to be non-empty by construction and this would introduce a lot of unneeded friction. |
There are two sides to this indeed:
I suggest this rule of thumb: when the JS engine throws a -- Concerning the "unneeded" friction (thinking of existing code, I assume), I think that is an argument for the part of the debate about whether to have array length checks as a default. I am aware that people who care less and do not actually rely on the type system, considering it a bonus, will feel it's a bother if they need to satisfy the type checker or forcefully type-assert that they know what they are doing. However, people who use
People who decide to use a type system do it specifically because they find friction far more desirable than potential run time errors. [edit] What I am questioning is whether this could indeed be "Working as Intended", not the fact that this can be classified as an improvement request rather than a bug report. |
I've changed the labels - let me know if there are others you think would be better |
This issue has been marked as "Too Complex" and has seen no recent activity. It has been automatically closed for house-keeping purposes. |
This wouldn't actually work.... How can you protect from: // Type NonEmptyArray<T>
const myNonEmptyArray = [1,2,3];
myNonEmptyArray.reduce((a, b) => a+b) // Yay, all good
while(myNonEmptyArray.length)
myNonEmptyArray.pop();
myNonEmptyArray.reduce((a, b) => a+b) // TypeError on runtime This is why I'm also on the side that trying to do something smart will always add complexity and friction, and even probably not solve the problem it was trying to solve initially. |
@voliva I expect an error here: while(myNonEmptyArray.length)
myNonEmptyArray.pop(); Because you can't empty a non-empty (aka non-emptiable) array! I'd like to send you back to #34736 (comment) in this thread and the proposal mentioned, #28837 Alternatively, its type was mutated (the opposite of a guard) when emptying it, and things work again, there's a type error on the reduce invocation. But that's too far-fetched for me (specifically, I'm not convinced about mutating types). |
TypeScript Version: 3.8.0-dev.20191025
Search Terms: reduce no initialvalue
Code
Expected behavior:
Should not compile
Actual behavior:
Compiles and crashes:
Related Issues:
None relevant, #28901 is trying to make TS accept something that it doesn't accept currently; I'm asking the opposite: that something which is accepted became not accepted.
Some ideas:
Retiring reduce with no
initialValue
from the type Array as this is unsafe to keep around; people should provide aninitialValue
if their array can be empty:In order to use
reduce
without initialValue on arrays for which it will work, it's possible to introduce something that might or might not be along the lines of:This non-empty array type can safely reintroduce the
reduce
signature withoutinitialValue
, the very one I would like to retire from the (possibly empty)Array
type.People who want to live dangerously could still use
([] as NonEmptyArray).reduce(someOperation)
if they absolutely refuse to provide an initialValue.The text was updated successfully, but these errors were encountered: