-
Notifications
You must be signed in to change notification settings - Fork 47
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
nextEvents.includes
inferred to never
#62
Comments
I'll look into it, though you're in the non-stale
That indeed is weird. |
Yes, that's on |
Welp, I'm afraid we can't do anything about this. Here's an explanation why it happens. TypeScript is doing it's job as declare let x: "a"[] | "b"[]
x.includes("a")
// Error: Argument of type 'string' is not assignable to parameter of type 'never'.(2345)
/*
("a"[] | "b"[])["includes"]
= "a"[]["includes"] | "b"[]["includes"]
= ((search: "a") => boolean) | ((search: "b") => boolean)
= (search: "a" & "b") => boolean | boolean
= (search: never) => boolean
*/
const includes = <T extends any[]>(xs: T, x: T[number]) => xs.includes(x)
includes(x, "a")
// compiles Some ways to go about this -
declare let stateOriginal:
| { value: "a"
, nextEvents: "X"[]
}
| { value: "b"
, nextEvents: "Y"[]
}
declare let stateIncludeFriendly:
| { value: "a"
, nextEvents: "X"[] & { includes: ("X" | "Y")["includes"] }
}
| { value: "b"
, nextEvents: "Y"[] & { includes: ("X" | "Y")["includes"] }
}
// stateOriginal
// pro: errors if we're checking something that will never happen
if (stateOriginal.value === "a") {
stateOriginal.nextEvents.includes("Y")
// Error: Argument of type '"Y"' is not assignable to parameter of type '"X"'.(2345)
}
// con: this doesn't work
stateOriginal.nextEvents.includes("X")
// Error: Argument of type 'string' is not assignable to parameter of type 'never'.(2345)
// stateIncludeFriendly
// con: does not error if we're checking something that will never happen
if (stateIncludeFriendly.value === "a") {
stateIncludeFriendly.nextEvents.includes("Y")
}
// pro: this works
stateIncludeFriendly.nextEvents.includes("X")
// If JavaScript was more functional-esque and had a static `Array.include`,
// wouldn't have the con in former and still keep the pro
declare let x: "a"[] | "b"[]
const normalise = <T extends any[]>(xs: T) => xs as T[number][]
normalise(x).includes("a")
declare let x: "a"[] | "b"[]
const withNormalised: <T extends any[]>(xs: T) => {
xs.$ = xs;
return xs as T & { $: T[number][] }
}
let nextEvents = withNormalised(x) // will be done internally
nextEvents.$.includes("a") I actually like 5 or 4, because the problem is not with |
Wow, I understand now - thanks for the detailed summary. When possible, I'll always prefer the option that introduces less API. I honestly can't think of a use case where a typestated So I guess for me it's option 1, but only degrading the type for @devanshj I can probably tackle this one myself, unless you have a strong opinion that degrading the nextEvents type is not the way to go. |
I do think that. Because we really don't know what users would do with And let's say we squash it, And anyway, I really don't think it's fair to trade precise types for not having an extra property imo. I'd suggest we go with But again, it's your library you get to take the final call :P Edit - also in case it's not obvious |
If we are indeed adding to the API (and your example convinced me) I'd prefer to have more distinct naming. Food for thought: I already want to introduce a Maybe we could keep the |
Actually I'd suggest the exact opposite. You see more distinct the name is, more it looks as if does it something else or is something else; but in our case it's literally the same thing, just a difference of its type. And adding a single character change has prior art -
So I'd strongly suggest to not keep a distinct name, it'd set up wrong expectations. Let the users enter the domain of fp & types and experience naming they haven't seen before :P
I think that would be solving the wrong problem, as I said it's not just about |
Ok, makes sense. Let's go with the normalized version as default and nextEventsT for the typed one. |
Done! |
While updating the timer example, I noticed this bug:
The weird part is that
nextEvents
type looks correct"START"[] | "PAUSE"[] | ("START" | "RESET")[] | undefined
@devanshj
The text was updated successfully, but these errors were encountered: