Reputation: 27
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
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