I want to create a curried function that can either accept an argument in the outer call, or the inner one - but never both/neither (xor). Is such a thing possible? I can't quite figure out how to make it work. The following clearly doesn't work because I'm not passing on any type information to the inner function:
const outerFunc:
| ((outerVar: string) => (innerVar: never) => string)
| ((outerVar: never) => (innerVar: string) => string) =
(outerVar?: string) =>
(innerVar?: string): string =>
outerVar || innerVar // typescript still thinks this may be undefined
// What should pass:
outerFunc()('foo')
outerFunc('foo')()
// What should fail:
outerFunc('foo')('foo')
outerFunc()()
Anyone know any tricks to get this to work using a combination of generics and conditional types or something? Or maybe function overloads?
CodePudding user response:
Here is one variant with function overloads:
function outerFunc(x: string): () => string;
function outerFunc(): (y: string) => string;
function outerFunc(x?: string): (y: string) => string {
return (y) => x || y;
}
// What should pass:
outerFunc()('foo') // OK
outerFunc('foo')() // OK
// What should fail:
outerFunc('foo')('foo') // Fails: "Expected 0 arguments, but got 1"
outerFunc()() // Fails: "Expected 1 arguments, but got 0"
CodePudding user response:
A parameter of type never
is not very useful, since it means you cannot call the function; for those cases you want a function with no parameters. Also, what you want here is a function with two overload signatures, so you get a different result depending on if you call it with an argument or not; overloads are an intersection of function types, not a union.
function fail(): never {
throw new Error();
}
function outerFunc(outerVar: string): () => string;
function outerFunc(): (innerVar: string) => string;
function outerFunc(outerVar?: string) {
return (innerVar?: string) => outerVar ?? innerVar ?? fail();
}
// ok
outerFunc()('foo')
outerFunc('foo')()
// errors, as expected
outerFunc('foo')('foo')
outerFunc()()