Lukas_Skywalker
Lukas_Skywalker

Reputation: 2070

Method signature based on previous method call

I need to program a value transformation based on various steps. In order to reuse the steps, an architecture was proposed where the steps can be passed to the transformation, similar to this code (e.g. to uppercase a long string and truncate it after 13 characters):

Transformation.from("This is a long story").step(Uppercase).step(Truncate);

class Uppercase {
  public run(str: string): string {
    return str.toUpperCase();
  }
}

class Truncate {
  public run(str: string): string {
    return str.substr(0, 10) + '...';
  }
}

Is there a way to make sure (preferrably at compile time) that the steps are compatible with each other, i.e. that the output of one step is type-compatible with the input of the next, so the following class isn't allowed after a step producing a string:

class SquareRoot {
  public run(num: number): number {
    return Math.sqrt(num);
  }
}

Achieving this at runtime seems possible by adding the two members inputType and outputType to the steps and comparing them, but I'd like to do the check at compile time.

Upvotes: 1

Views: 34

Answers (1)

Karol Majewski
Karol Majewski

Reputation: 25820

First, let's define the Transformer class.

class Transformation<T> {
  constructor(readonly value: T) {
    this.value = value;
  }

  public static from<U>(input: U): Transformation<U> {
    return new Transformation(input);
  }

  public step<U>(transformer: Transformer<T, U>): Transformation<U> {
    return new Transformation(transformer.run(this.value));
  }
}

It uses a Transformer interface. We can define it as:

interface Transformer<T, U> {
  run(input: T): U;
}

With that in place, the transformations are guaranteed to be correct in compile-time.

Transformation.from("This is a long story")
  .step(new Uppercase())
  .step(new Truncate()); // OK

Transformation.from("This is a long story")
  .step(new Uppercase())
  .step(new SquareRoot()); // Compile-time error: Type 'string' is not assignable to type 'number'.ts(2345)

Update

Note: in your example, you're passing constructors of Uppercase and Truncate as your steps. In my solution, instances are used instead. If you insist on passing constructors instead, you will need to make the run method static.

class Uppercase {
  public static run(str: string): string {
    return str.toUpperCase();
  }
}

class Truncate {
  public static run(str: string): string {
    return str.substr(0, 10) + '...';
  }
}

class SquareRoot {
  public static run(num: number): number {
    return Math.sqrt(num);
  }
}

Transformation.from("This is a long story")
  .step(Uppercase)
  .step(Truncate);

Transformation.from("This is a long story")
  .step(Uppercase)
  .step(SquareRoot); // Compile-time error: Type 'string' is not assignable to type 'number'.ts(2345)

Upvotes: 2

Related Questions