1

I'm trying to create a higher order function that allows me to create a function which confirms a type via a type guard.

type Shape = Circle | Triangle | Rectangle
enum Type { Circle, Triangle, Rectangle }

interface Circle {
  type: Type.Circle;
  diameter: number;
}

interface Triangle {
  type: Type.Triangle;
  width: number;
}

interface Rectangle {
  type: Type.Rectangle;
  width: number;
  height: number;
}

const isShape = (condition: Type) => (shape: Shape): shape is ? => shape.type == condition;

const isCircle = isShape(Type.Circle);

In the above example, I want the isCircle function to return whether the type is Circle. The ? is a placeholder since I can't get it to work.

2
  • Related Mapping from discriminated union property to object type. In short, without restructuring your types, it won't be possible to give a signature to isShape that does what you want. Commented Oct 16, 2018 at 22:58
  • @CRice, how would the types have to be structured in order to achieve this? Commented Oct 17, 2018 at 1:27

2 Answers 2

2

To achieve your objective, here is what your isShape function should look like:

const isShape = <T extends Shape>(condition: Type) => {
  return function(shape: T): shape is T {
    return shape.type == condition;
  };
};

const isCircle = isShape<Circle>(Type.Circle);
Sign up to request clarification or add additional context in comments.

5 Comments

This is wrong: it does not establish a relationship between condition and T.
@Akshar Patel, unfortunately when this function is implemented Typescript will only tell that the type is Shape.
@LennardSchutter: How about now?
@Akshar Patel, this works, amazing! I didn't realise I could pass the <Circle> to the function caller.
@Akshar Patel, I ended up making it a little more generic. const isType = <U extends { type: E }, E>() => <T extends U>(condition: E) => (obj: U): obj is T => obj.type == condition;
2

You need to provide a direct way for TypeScript to figure out what Shape is associated with a given Type. One way to do that is to explicitly provide a mapping between the two, which can then be used to replace your definition for Shape. So, for example:

// Instead of this:
// type Shape = Circle | Triangle | Rectangle

// Use this:
type TypeToShape = {
  [Type.Circle]: Circle,
  [Type.Triangle]: Triangle,
  [Type.Rectangle]: Rectangle,
}

// Circle | Triangle | Rectangle
type Shape = TypeToShape[keyof TypeToShape];

Then, given a specific Type, it's easy to map that to the Shape associated with it:

// Use the type map to tell typescript what shape goes with the Type K:
const isShape = <K extends Type>(condition: K) => (shape: Shape): shape is TypeToShapeMap[K] => shape.type == condition;

const isCircle = isShape(Type.Circle); // return type: shape is Circle
const isTriangle = isShape(Type.Triangle) // return type: shape is Triangle

2 Comments

Thanks for your effort, @CRice. Although this works also, I find the other answer to be easier to implement and would not require a change to my current code.
@CRice: This is a really clean solution.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.