Home > Enterprise >  Types for flexibly currying functions
Types for flexibly currying functions

Time:11-11

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()()

Playground Link

  • Related