Home > Back-end >  How to write a Generic Function over a pair of related types
How to write a Generic Function over a pair of related types

Time:11-24

I want to create some abstract relation between Message and Response sub-types, such that I can have a generic function that takes a Message as an argument and returns a Response. However whenever the function is called with type MessageTypeA I want the response type to be of ResponseTypeA and I want TypeScript to assure this relation in some way. Consider the following TypeScript Code to illustrate the problem:

type MessageTypeA = { a: string };
type MessageTypeB = { b: number };
type MessageTypeC = [number, number];

type Message = MessageTypeA | MessageTypeB | MessageTypeC;

type ResponseTypeA = { a: boolean; aa: number };
type ResponseTypeB = "hello" | "bye";
type ResponseTypeC = number;

type Reponse = ResponseTypeA | ResponseTypeB | ResponseTypeC;

type Pair<M extends Message, R extends Reponse> = {
  message: M;
  response: R;
};

// I know this should be some type of Map/Record instead
type ValidPairsMap =
  | Pair<MessageTypeA, ResponseTypeA>
  | Pair<MessageTypeB, ResponseTypeB>
  | Pair<MessageTypeC, ResponseTypeC>;

// Something like this, I know syntax is wrong here:
function sendMessageReturnResponse<T extends ValidPair>(
  message: typeof T.message
): typeof T.response {
  throw "idk how to do this";
}

function main() {
  let mesA: MessageTypeA = { a: "msg" };
  let resA: ResponseTypeA = sendMessageReturnResponse(mesA);
  // error: return value of sendMessageReturnResponse cannot be inferred to be ResponseTypeA
}

What can I do to achieve the objective described above?

CodePudding user response:

I believe you are looking for something like this:

type MessageTypeA = { a: string };
type MessageTypeB = { b: number };
type MessageTypeC = [number, number];

type Message = MessageTypeA | MessageTypeB | MessageTypeC;

type ResponseTypeA = { a: boolean; aa: number };
type ResponseTypeB = "hello" | "bye";
type ResponseTypeC = number;

type Reponse = ResponseTypeA | ResponseTypeB | ResponseTypeC;

type Pair<M extends Message, R extends Reponse> = {
    message: M;
    response: R;
};

type ValidPairsMap =
    | Pair<MessageTypeA, ResponseTypeA>
    | Pair<MessageTypeB, ResponseTypeB>
    | Pair<MessageTypeC, ResponseTypeC>;

// Something like this, I know syntax is wrong here:
function sendMessageReturnResponse<Msg extends Message>(
    message: Msg
): Extract<ValidPairsMap, Pair<Msg, any>>['response'] {
    return null as any
}

function main() {
    let mesA: MessageTypeA = { a: "msg" };
    let resA: ResponseTypeA = sendMessageReturnResponse(mesA); // ok

}

Playground

In sendMessageReturnResponse it is enough to infer only Message, then you are allowed to filter union type with help of Extract

This line Extract<ValidPairsMap, Pair<Msg, any>> means: get Pair where message is Msg from union of ValidPairsMap

  • Related