0

I have multiple functions that get the same type but return a different type of interface. I want to create a type for those functions, but when I write:

const f: (arg: number) => Object = func;

I get an error: Type (arg: number) => SomeInteface is not assignable to type (arg: number) => Object

I could just use (arg: number) => any but I kind of defeats the whole purpose of it being typed. I just want it to accept all Objects and not just a specific interface because I have plenty.

For example, I'm implementing communication protocols:

interface splPacket {
    serviceType: number,
    serviceSubType: number,
    satelliteTime: Date,
    data: Buffer
}

function decodeSpl(input: Buffer): splPacket {...}
function encodeSpl(input: splPacket): Buffer {...}

const spl = {
    decode: decodeSpl,
    encode: encodeSpl
};

interface ax25Packet {
    destCallsign: string,
    destSSID: number,
    srcCallsign: string,
    srcSSID: number,
    data: Buffer
}

function decodeAx25(input: Buffer): ax25Packet {...}
function encodeAx25(input: ax25Packet): Buffer {...}

const ax25 = {
    decode: decodeAx25,
    encode: encodeAx25
};


interface commProtcol {
   decode: (input: Buffer): Object;
   encode: (input: Object): Buffer;
};

const protocol: commProtocol = spl;

But spl and ax25 won't be assignable to commProtocol. I want to create an interface that will allow all my communication protocols implementations.

What should I do?

5
  • Does object work for you? Notice lower case Commented Jan 18, 2020 at 8:14
  • @SebastianKaczmarek object still doesn't work Commented Jan 18, 2020 at 9:14
  • 1
    Can you provide a minimal reproducible example as described in How to Ask? Right now I can't reproduce your issue so I can't advise. Good luck! Commented Jan 18, 2020 at 20:29
  • Which version of typescript do you use? Commented Jan 18, 2020 at 21:21
  • @jcalz I've updated my answer Commented Jan 20, 2020 at 15:11

1 Answer 1

1

So, I can't reproduce the error as you've written it. If I have types X, T, and U, where U extends T, then a function of the form (a: X)=>U should be assignable to a variable annotated as (a: X)=>T, and not vice versa. Another way of saying this is that functions should be covariant in their output types. In particular, (a: number)=>SomeInterface should be assignable to a variable annotated as (a: number)=>object. And it is; I don't see an error of that sort in your code.

When I put your example code into an IDE, the error I see is this:

const protocol: CommProtocol = spl;
/* Types of property 'encode' are incompatible.
   Type '(input: SplPacket) => Buffer' is not assignable to type '(input: object) => Buffer'.
*/

This is complaining about the argument to the function, not to the return type. If I have types X, T, and U, where U extends T, then a function of the form (a: T)=>X should be assignable to a variable annotated as (a: U)=>X and not vice versa. Another way of saying this is that functions should be contravariant in their input types. In particular, (a: object)=>Buffer should not be assignable to a variable annotated as (a: SplPacket)=>Buffer. And it isn't. That's why you're getting the error.

It's unsound to try to treat spl.encode as if it were a valid encode property of CommProtocol. After all, I expect protocol.encode() to accept any object whatsoever as its input. So protocol.encode({foo: "", bar: 1}) should be fine. But spl.encode() expects an SplPacket, not a {foo: string, bar: number}. It's likely to blow up at runtime.


So what can we do here? Assuming you actually want type safety, the solution is probably to make CommProtocol generic in the type of object you're talking about. Like this:

interface CommProtocol<T extends object> {
    decode: (input: Buffer) => T;
    encode: (input: T) => Buffer;
}

CommProtocol is no longer a complete interface name by itself... it needs a type parameter to make it complete. A CommProtocol<T> can encode a T into a Buffer and decode a Buffer into a T. A CommProtocol<object> is equivalent to your old definition of CommProtocol (and therefore inappropriate for spl). And a CommProtocol<SplPacket> can encode a SplPacket into a Buffer and decode a Buffer into a SplPacket, so the following compiles with no error:

const goodProtocol: CommProtocol<SplPacket> = spl; // okay

If you don't want to have to hand-annotate each CommProtocol type parameter, you can have a helper function that lets the compiler infer the particular type parameter for each value:

const asProtocol = <T extends object>(t: CommProtocol<T>) => t;

And here's how you'd use it:

const alsoGoodProtocol = asProtocol(spl); // CommProtocol<SplPacket>
const anotherProtocol = asProtocol(ax25); // CommProtocol<Ax25Packet>

Okay, looks good. Hope that gives you some direction; good luck!

Playground Link to code

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.