Type Predicates: Refining Type Safety in TypeScript
Type predicates are a powerful feature in TypeScript that allow you to narrow down the type of a variable based on a runtime check. They're particularly useful when dealing with union types or when you need to ensure a value conforms to a specific structure. This challenge will guide you through implementing and utilizing type predicates to enhance type safety and code clarity.
Problem Description
You are tasked with creating and using type predicates in TypeScript to refine the type of variables based on their properties. Specifically, you'll be working with a union type representing different shapes (Circle and Rectangle) and creating a type predicate function to determine if a given object is a Circle. The goal is to write a function that takes a generic object and returns a boolean indicating whether it's a Circle, and then use this predicate to safely access properties specific to the Circle type.
What needs to be achieved:
- Define a union type
Shapethat can be either aCircleor aRectangle.Circlehas aradiusproperty (number), andRectanglehaswidthandheightproperties (both numbers). - Create a type predicate function
isCirclethat accepts a generic object and returnstrueif the object is aCircle(i.e., has aradiusproperty), andfalseotherwise. - Write a function
calculateAreathat takes aShapeand, using theisCirclepredicate, calculates the area if it's a circle or returnsundefinedif it's a rectangle.
Key Requirements:
- The
isCirclefunction must be a type predicate. This means its return type must beobj is Circle. - The
calculateAreafunction must correctly calculate the area of a circle using theradiusproperty and returnundefinedfor rectangles. - Type safety is paramount. The code should leverage the type predicate to ensure that properties are accessed only when the type is confirmed.
Expected Behavior:
isCircle({ radius: 5 })should returntrue.isCircle({ width: 10, height: 20 })should returnfalse.calculateArea({ radius: 5 })should return approximately 78.54.calculateArea({ width: 10, height: 20 })should returnundefined.
Edge Cases to Consider:
- Objects with unexpected properties. The
isCirclepredicate should only check for the existence of theradiusproperty. - Null or undefined input. While not explicitly required, consider how your code handles these cases gracefully (e.g., by returning
falsefromisCircleif the input is null/undefined).
Examples
Example 1:
Input: { radius: 5 }
Output: true
Explanation: The object has a 'radius' property, so it's considered a Circle.
Example 2:
Input: { width: 10, height: 20 }
Output: false
Explanation: The object does not have a 'radius' property, so it's not a Circle.
Example 3:
Input: { radius: 5 }
Output: 78.54 (approximately)
Explanation: The calculateArea function correctly identifies the input as a Circle and calculates its area.
Constraints
- The
radiusproperty of aCirclemust be a number. - The
widthandheightproperties of aRectanglemust be numbers. - The
calculateAreafunction should returnundefinedif the input is aRectangle. - The area calculation should be reasonably accurate (within a small margin of error due to floating-point arithmetic).
Notes
- Remember that type predicates are functions that return a boolean and have a return type annotation of the form
obj is Type. - Consider using the
typeofoperator to check for the existence of a property. - Think about how to leverage the type predicate within the
calculateAreafunction to safely access theradiusproperty only when it's confirmed that the object is aCircle.