MrBrN197
MrBrN197

Reputation: 27

why do typescript function types not make sense in this case

I'm experimenting with Typescript and I'm a little puzzled that this piece of code works. is there a reason why this example isn't invalid.

function foo(x: string) {
  return x;
}

function bar(fn: (x: any) => string): string {
  const obj = fn({
    name: 'phillip' // make sense here according to the signature of fn.
  });
  return obj;
}

const result = bar(foo); // why is this allowed, foo shouldn't match that type
// result is assumed to be string, but it can't be

console.log(result);

foo takes one argument that is of type string and returns it; so it returns type string the bar method, however, takes a callback that accepts one argument of type any but bar takes in foo without typescript complaining even though it doesn't match that signature. shouldn't a function taking string not be able to match the definition of a function that wants to take any?

Upvotes: 0

Views: 89

Answers (1)

Forrest
Forrest

Reputation: 1440

Why do you believe that foo does not match the signature? You pass a function to bar which takes in anything, and has to return a string, which foo matches perfectly fine. Let's expand your example to better understand why this works and what wouldn't work:

I recommend following along at the playground

const foo = (x: string) => {
    return x
}

const baz = (x: number) => {
    return ''
}

const xyz = (x: number) => {
    return x
}

const bar = (fn: (x: any) => string): string => {
    const obj = fn({
        name: 'phillip' // make sense here according to the signature of fn.
    })
    return obj
}


const fooResult = bar(foo) // This is fine because x is a string which satisfies the any constraint and a string is returned

const bazResult = bar(baz) // This is also fine because even though x is a number, a string is returned

const xyzResult = bar(xyz) // this is not fine because while the x constraint is satisfied (any), a number is returned

console.log('fooResult', fooResult)
console.log('bazResult', bazResult)
console.log('xyzResult', xyzResult)

Start by looking what typescript shows for foo, baz, and xyz functions when you hover over them in the call to `bar.

foo:

const foo: (x: string) => string

Looks like our constraint of bar is satisfied:

const bar: (fn: (x: any) => string) => string

Things look good here, we take in any which could be a string and return a string, so this helper function satisfies our constraint.

baz:

const baz: (x: number) => string

Looks like our constraint of bar is satisfied again, x is any (in this case a number), but we still return a string so there is no complaint.

xyz:

const xyz: (x: number) => number

Our bar constraint isn't satisfied here because even though number satisfies any, we return a number when we have to return a string.

Let's look at the response for the xyzResult and what the compiler is telling us because I see an error there:

const xyz: (x: number) => number
Argument of type '(x: number) => number' is not assignable to parameter of type '(x: any) => string'.
  Type 'number' is not assignable to type 'string'.(2345)

This makes a lot of sense, the compiler doesn't think it's okay to return a number when you should return a string to satisfy the constraints.

What's more confusing is our output on baz because we can only take a number here, yet somehow passing the object in the way you have still works and we get this:

[LOG]: "bazResult",  ""

So we somehow properly return that empty string (or any string, it doesn't matter what the contents are) even though the function doesn't match. On top of all of that mess, even though the compiler complains about xyz this is our result on that console.log:

[LOG]: "xyzResult",  {
  "name": "phillip"
} 

How can we be returning the same thing as foo even though it seems like our function can't work? It gets even worse however, let's say we add this to our helper functions:

const ab = (x: number) => {
    return 12
}

Surely this will break right? No it doesn't:

[LOG]: "abResult",  12 

So now we've somehow only matched our input type (any accepts a number) and it still works. Why is this? Because we're still compiling everything to Javascript in the end and Javascript doesn't have any of the niceties Typescript provides. This is what that Javascript looks like:

"use strict";
const foo = (x) => {
    return x;
};
const baz = (x) => {
    return '';
};
const xyz = (x) => {
    return x;
};
const ab = (x) => {
    return 12;
};
const bar = (fn) => {
    const obj = fn({
        name: 'phillip' // make sense here according to the signature of fn.
    });
    console.log(obj);
    return obj;
};
const fooResult = bar(foo); // This is fine because x is a string which satisfies the any constraint and a string is returned
const bazResult = bar(baz); // This is also fine because even though x is a number, a string is returned
const xyzResult = bar(xyz); // this is not fine because while the x constraint is satisfied (any), a number is returned
const abResult = bar(ab);
console.log('fooResult', fooResult);
console.log('bazResult', bazResult);
console.log('xyzResult', xyzResult);
console.log('abResult', abResult);

There are no types so there are no problems and things simply work even though they work in a way we don't expect them to and don't want them to. This is where the power of Typescript comes in. Yes you could force the compilation and this would do what the Javascript has compiled to do, but you know that there are these problems and the Typescript compiler is trying to save you because it also knows there are these problems.

Hopefully this has answered both your original question (any can handle a function that takes a string because any takes anything), as well as expanding a bit to show why Typescript is so powerful and enjoyable to work with.

Upvotes: 2

Related Questions