Search code examples
typescriptfunctional-programmingpartial-application

Typescript: Type a group of partially applied functions with variable number and type of parameters that all return function of same type


I am looking for a way to group a set of partially applied functions with only the first parameter(s) being different. Or in other words; The group contains functions accepting any number of parameters of any type but the first application always has to return a function of type (state: State) => Event[].

For example a group of functions like this:

const group = { 
  func1: () => (state: State) => Event[],
  func2: (arg: string) => (state: State) => Event[],
  func3: (arg: string, arg2: number) => (state: State) => Event[],
}

All of these functions follow the pattern (not updated to accept multiple args):

export type Command = <T>(arg: T) => (state: State) => Partial<Event>[];

However, when I try to type the group like this:

const command: Record<string, Command> = {
  func1: () => (state: State) => [{}],
  func2: (arg: string) => (state: State) => [{}],
  func3: (arg: string, arg2: number) => (state: State) => [{}],
};

Typescript warns me that type T is not assignable to type string.

Type '(arg: string) => (state: State) => {}[]' is not assignable to type 'Command'.
  Types of parameters 'arg' and 'arg' are incompatible.
    Type 'T' is not assignable to type 'string'

I understand why it is not assignable but I can't figure out how I would then type this grouping of partially applied functions. I basically want to make sure that every function in this grouping follows the pattern of the type Command. That is to say, it should be a partially applied function with any parameters of any type that return a function of the type: (state: State) => Event[]

Is this possible, and if so, how would I do this?


Solution

  • Is it helping to use any in your type declaration and type the function parameter later in your group like so?

    export type Command = (...arg: any[]) => (state: State) => Partial<Event>[];
    
    const command: Record<string, Command> = {
      func1: () => (state: State) => [{}],
      func2: (arg: string) => (state: State) => [{}],
      func3: (arg: string, arg2: number) => (state: State) => [{}],
    };
    

    Update

    To be more specific with the parameters I would let Typescript infer the function signatures:

    export type Command = (state: State) => Partial<Event>[];
    
    const command = {
      func1: (): Command => (state: State) => [{}],
      func2: (arg: string): Command  => (state: State) => [{}],
      func3: (arg: string, arg2: number): Command => (state: State) => [{}],
    };
    
    command.func1() // OK
    command.func1("test") // Err
    command.func2() // Err
    command.func2("test") // OK
    command.func2([]) // Err
    command.func3() // Err
    command.func3("test", 2) // OK
    command.func3([]) // Err
    

    Or explicitly type the group:

    export type Command = (state: State) => Partial<Event>[];
    interface CommandsGroup {
        func1: () => Command;
        func2: (arg: string) => Command;
        func3: (arg: string, arg2: number) => Command
    }
    
    const command: CommandsGroup = {
      func1: () => (state: State) => [{}],
      func2: (arg: string) => (state: State) => [{}],
      func3: (arg: string, arg2: number) => (state: State) => [{}],
    };
    
    command.func1() // OK
    command.func1("test") // Err
    command.func2() // Err
    command.func2("test") // OK
    command.func2([]) // Err
    command.func3() // Err
    command.func3("test", 2) // OK
    command.func3([]) // Err