Lodin
Lodin

Reputation: 2068

How to make function argument that is generic object consuming two functions with one type or void in Typescript

I have following situation in typescript:

type Matcher<T, U> = {
  First: (arg: T) => U,
  Second: () => U
};

class Main<T> {    
  constructor(private value: T) {
  }

  match<U>(matcher: Matcher<T, U>): U {
    return this.value
      ? matcher.First(this.value)
      : matcher.Second();
  }
}

const main = new Main(10);

const res = main.match({ // there is a problem
  First: v => v + 10,
  Second: () => console.log()
});

So I have an object that user have to pass to the match method of class instance. This object should contain two functions: First and Second. This functions returns value of one type (e.g. number) or of one type + void (e.g. number + void), but nothing else. There cannot be string + number types.

This code fails with error

The type argument for type parameter 'U' cannot be inferred from the usage. Consider specifying the type arguments explicitly. 
Type argument candidat 'void' is not a valid type argument because it is not a supertype of candidate 'number'.

I understand why this error happens (U is the single type, but functions have two different types and they cannot be merged and so on), but how can I solve this problem? I need:

Is it possible to do with typescript type system?

Upvotes: 0

Views: 119

Answers (1)

Nitzan Tomer
Nitzan Tomer

Reputation: 164397

You can use union types:

type Matcher<T, U> = {
    First: (arg: T) => U;
    Second: () => U | void
};

I added the void just to the second function, but you can have it in the first as well.

But you will then need the match method to return | void as well:

match<U>(matcher: Matcher<T, U>): U | void {
    return this.value
        ? matcher.First(this.value)
        : matcher.Second();
}

(code in playground)


Edit

If I understand you correctly, then this might help:

type Matcher<T, U> = {
    First: (arg: T) => U;
    Second: () => U;
};

type MatcherOne<T, U> = {
    First: (arg: T) => void;
    Second: () => U;
};

type MatcherTwo<T, U> = {
    First: (arg: T) => U;
    Second: () => void;
};

class Main<T> {
    constructor(private value: T) { }

    match<U>(matcher: Matcher<T, U>): U;
    match<U>(matcher: MatcherOne<T, U>): U | void;
    match<U>(matcher: MatcherTwo<T, U>): U | void;
    match<U>(matcher: Matcher<T, U> | MatcherOne<T, U> | MatcherTwo<T, U>): U | void {
        return this.value
            ? matcher.First(this.value)
            : matcher.Second();
    }
}

(code in playground)

Upvotes: 1

Related Questions