Hone logo
Hone
Problems

TypeScript Protocol Implementation

This challenge focuses on understanding and implementing "protocol types" in TypeScript, a concept that allows you to define a set of required properties and methods that a type must conform to, without necessarily inheriting from a specific class. This is crucial for creating flexible and interoperable code, especially when dealing with external data or libraries where you can't control the exact class definitions.

Problem Description

Your task is to create a TypeScript system that allows you to define and enforce "protocol types" for objects. A protocol type specifies a contract that an object must fulfill, meaning it must possess certain properties with specific types and potentially certain methods. You should implement a mechanism to check if a given object conforms to a defined protocol.

Key Requirements:

  1. Define Protocols: Create a way to define a protocol type. A protocol should be definable using TypeScript's type system.
  2. Type Checking: Implement a function that takes an object and a protocol type, and returns true if the object conforms to the protocol, and false otherwise.
  3. Property and Method Checks: The checking mechanism should verify the presence and correct type of properties, as well as the presence and (optionally) signature of methods defined in the protocol.

Expected Behavior:

  • A function, let's call it conformsToProtocol, should take two arguments: obj (the object to check) and protocol (the protocol type definition).
  • It should return true if obj has all the properties and methods specified by protocol with matching types.
  • It should return false if obj is missing any required properties or methods, or if any of them have incorrect types.

Edge Cases to Consider:

  • Protocols with optional properties.
  • Protocols with methods that accept arguments.
  • Protocols with methods that return values.
  • Handling null or undefined as input objects.

Examples

Example 1:

Input:

interface Printable {
  print(): void;
}

const myObject = {
  print: () => console.log("Hello!"),
  data: 123
};

const anotherObject = {
  data: 456
};

Output of conformsToProtocol(myObject, Printable): true Output of conformsToProtocol(anotherObject, Printable): false

Explanation: myObject has a print method matching the Printable protocol. anotherObject is missing the print method.

Example 2:

interface User {
  id: number;
  name: string;
  isActive?: boolean; // Optional property
  getDisplayName(): string;
}

const validUser = {
  id: 1,
  name: "Alice",
  isActive: true,
  getDisplayName: function() { return this.name; }
};

const missingMethodUser = {
  id: 2,
  name: "Bob",
  isActive: false
};

const wrongTypeUser = {
  id: "3", // Wrong type for id
  name: "Charlie",
  getDisplayName: () => "Charlie"
};

const missingOptionalUser = {
    id: 4,
    name: "David",
    getDisplayName: () => "David"
}

Output of conformsToProtocol(validUser, User): true Output of conformsToProtocol(missingMethodUser, User): false Output of conformsToProtocol(wrongTypeUser, User): false Output of conformsToProtocol(missingOptionalUser, User): true

Explanation: validUser meets all requirements of the User protocol. missingMethodUser lacks getDisplayName. wrongTypeUser has an incorrect type for id. missingOptionalUser is valid because isActive is optional.

Example 3:

interface Calculator {
  add(a: number, b: number): number;
  subtract(a: number, b: number): number;
  multiply?(a: number, b: number): number; // Optional method
}

const basicCalculator = {
  add: (a, b) => a + b,
  subtract: (a, b) => a - b
};

const advancedCalculator = {
  add: (a, b) => a + b,
  subtract: (a, b) => a - b,
  multiply: (a, b) => a * b
};

const incompleteCalculator = {
  add: (a, b) => a + b,
  // subtract is missing
  multiply: (a, b) => a * b
};

Output of conformsToProtocol(basicCalculator, Calculator): true Output of conformsToProtocol(advancedCalculator, Calculator): true Output of conformsToProtocol(incompleteCalculator, Calculator): false

Explanation: basicCalculator has the required add and subtract methods. advancedCalculator has all methods, including the optional multiply. incompleteCalculator is missing the mandatory subtract method.

Constraints

  • The conformsToProtocol function should be purely runtime JavaScript code, but it will be used with TypeScript types. You will need to design your protocol definitions and checking logic in TypeScript.
  • The checking should be as robust as possible within the limits of runtime JavaScript and TypeScript's type system. You cannot perfectly replicate compile-time type checking at runtime, but aim to cover the common cases.
  • Assume obj can be null or undefined. The function should gracefully handle these inputs (likely returning false).
  • Performance is not a primary concern for this challenge, but avoid excessively inefficient algorithms.

Notes

  • Consider how you will represent the "protocol type" at runtime for your checking function. You might use TypeScript's interface or type aliases for definition, but you'll need a way to inspect these definitions or their expected structure at runtime.
  • Think about how to differentiate between properties and methods during your check.
  • For methods, you'll need to check if the property exists and if it's a function. You might also consider if you need to check method signatures (though this can become very complex at runtime). For this challenge, simply checking if it's a function is sufficient for methods.
  • The goal is to create a practical and understandable way to check for structural compatibility, similar to how duck typing works in other languages.
Loading editor...
typescript