Ziggy
Ziggy

Reputation: 22375

In typescript, can a function be typed to return a particular instance?

In typescript I would like to have a function, like

getMin<T>(a: T, b: T): T

Which will return either a, or b such that, if a < b

getMin(a, b) === a

Is always true. For example, the following function would not qualify:

const getMin = (a: IUser, b: IUser): IUser => {
  if (min(a.userId, b.userId) === a) {
    return new User(a); // copy constructor
  } else {
    return new User(b);
  }
}

Because getMin(a, b) === a will return false since a is not referentially equal to the return value of getMin. This is because getMin returns a copy of a, not the a that was passed into the function. What I want is a function that returns either of its inputs, something like,

const getMin = (a: IUser, b: IUser) => {
  if (min(a.userId, b.userId) === a) {
    return a; // reference
  } else {
    return b;
  }
}

That way, if I wrote a function that was accidentally creating a copy when I wanted it to return one of the given references, it would be a compile time error. I'm imagining a type signature like,

getMin<T>(a: T, b: T): a | b

Similar to how we can do,

getThreeOrFive (): 3 | 5

It seems like the this keyword already works this way in TS, since I can write a function like,

this.doSomething = (): this => { /* does something */ return this }

Is there some way to do this in TS?

Upvotes: 0

Views: 44

Answers (1)

Fenton
Fenton

Reputation: 250972

My understanding is that you want to guarantee that one of the inputs is returned as the output - not just a matching type.

I don't believe a type system can do this for you, but a good unit test can.

I have written the test without a framework for demonstration purposes - but you can see that a unit test around this would prevent a new instance from being returned:

interface IUser {
    userId: number;
}

class User implements IUser {
    userId: number;
    constructor(u: IUser) { 
        this.userId = u.userId;
    }
}

const getMinA = (a: IUser, b: IUser): IUser => {
    if (a.userId < b.userId) {
        return new User(a); // copy constructor
    } else {
        return new User(b);
    }
}


const getMinB = (a: IUser, b: IUser): IUser => {
    if (a.userId < b.userId) {
        return a;
    } else {
        return b;
    }
}

const a = new User({ userId: 1 });
const b = new User({ userId: 2 });

if (getMinA(a, b) === a) {
    // Okay
} else {
    alert('getMinA: Not original A');
}

if (getMinB(a, b) === a) {
    // Okay
} else {
    alert('getMinB: Not original A');
}

The output from this is:

getMinA: Not original A

Upvotes: 3

Related Questions