Search code examples
typescriptgenerics

How to fix Typescript generic class when "Two different types with this name exist, but they are unrelated"


Consider the following code. ColumnSorter is intended to encapsulate a set of sorting functions by their string names, then apply the right sort function to an array.

export class ColumnSorter<T> {
    constructor(private map: Map<string, (a: T, b: T) => number>) {
    }
    public sortBy<T>(sortcol: string, items: T[]): void {
        // *** DOES NOT COMPILE:
        items.sort(this.map.get(sortcol));
        // *** DOES NOT COMPILE EITHER:
        let fn: (a: T, b: T) => number = this.map.get(sortcol);
    }
}

// here is an example Quote arrays. Others could be position, account, etc.
// implementing with sort functions for that type.
const quoteFunctionMap: Map<string, (a: Quote, b: Quote) => number> = new Map([
    ['byLastPrice', (q1: Quote, q2: Quote) => {
        return q1.lastPrice - q2.lastPrice;
    }],
    ['bySymbol', (q1: Quote, q2: Quote) => {
        return q1.symbol.localeCompare(q2.symbol);
    }]
]);

let colsort = new ColumnSorter<Quote>(quoteFunctionMap);
let quotes: Quote[] = [new Quote(), new Quote()]; // blank quotes, for compile test
colsort.sortBy("byLastPrice", quotes);

The problem is fetching the sort function out of the map. The error message is detailed but isn't enough for me to find the root of the problem. Is there something missing from my code?

Argument of type '((a: T, b: T) => number) | undefined' is not assignable to parameter of type '((a: T, b: T) => number) | undefined'.
  Type '(a: T, b: T) => number' is not assignable to type '(a: T, b: T) => number'. Two different types with this name exist, but they are unrelated.
    Types of parameters 'a' and 'a' are incompatible.
      Type 'T' is not assignable to type 'T'. Two different types with this name exist, but they are unrelated.
        'T' could be instantiated with an arbitrary type which could be unrelated to 'T'.ts(2345)

Solution

  • Your ColumnSorter.sortBy method should use the T generic from the class rather than defining its own.

    export class ColumnSorter<T> {
      constructor(private map: Map<string, (a: T, b: T) => number>) {
      }
      // NOTE: changed from sortBy<T> to just sortBy below!
      public sortBy(sortcol: string, items: T[]): void {
        items.sort(this.map.get(sortcol));
        let fn = this.map.get(sortcol);
        if (!fn) {
          throw new Error(`No sorter exists for ${sortcol}`);
        }
        // ...
      }
    }