Typescript: wrapper function prevents inferred typing of anonymous function
I'm running into a typing nuisance:
function identity<T>(v: T): T{ return v; }
function execute(fn: {(n: number):string}) {}
execute((n) => {
// type of n is 'number'
return n.toFixed();
})
execute(identity((n) => {
// type of n is 'any'
return n.toFixed();
}))
When a typed higher-order function execute
receives a function, the arguments of that anonymous function are typed via inference. However, passing that anonymous function to a wrapper identity
function causes those inferred types to be lost. Is there any adjustments I could make to the construction of execute
or identity
that can allow those typings to still be inferred?
NOTE For simplicity, identity
is a pure function here. In actual practice it is not, but should have the same typing as this identity
function. See checkpoint
in context of question for more detail.
see it in the TS Playground
Context
This is the generic form of a problem I was running into when loading data in the context of a React component lifecycle. Because setState
should not be called on a no-longer-mounted component, I prevent the load callback from firing.
function loadData():Promise<MyDataType> {/*...*/}
// Wraps the passed function (handleDataLoaded),
// such that when the returned function is executed the
// passed function is conditionally executed depending
// on closure state.
function checkpoint(fn){/*...*/}
// Add data to the state of the component
function handleDataLoaded(val: MyDataType){/*...*/}
// react lifecycle hook componentDidMount
loadData()
.then(checkpoint(handleDataLoaded));
// react lifecycle hook componentWillUnmount
// adjusts state of checkpoint's closure such that handleDataloaded
// is not fired after componentWillUnmount
typescript type-inference
add a comment |
I'm running into a typing nuisance:
function identity<T>(v: T): T{ return v; }
function execute(fn: {(n: number):string}) {}
execute((n) => {
// type of n is 'number'
return n.toFixed();
})
execute(identity((n) => {
// type of n is 'any'
return n.toFixed();
}))
When a typed higher-order function execute
receives a function, the arguments of that anonymous function are typed via inference. However, passing that anonymous function to a wrapper identity
function causes those inferred types to be lost. Is there any adjustments I could make to the construction of execute
or identity
that can allow those typings to still be inferred?
NOTE For simplicity, identity
is a pure function here. In actual practice it is not, but should have the same typing as this identity
function. See checkpoint
in context of question for more detail.
see it in the TS Playground
Context
This is the generic form of a problem I was running into when loading data in the context of a React component lifecycle. Because setState
should not be called on a no-longer-mounted component, I prevent the load callback from firing.
function loadData():Promise<MyDataType> {/*...*/}
// Wraps the passed function (handleDataLoaded),
// such that when the returned function is executed the
// passed function is conditionally executed depending
// on closure state.
function checkpoint(fn){/*...*/}
// Add data to the state of the component
function handleDataLoaded(val: MyDataType){/*...*/}
// react lifecycle hook componentDidMount
loadData()
.then(checkpoint(handleDataLoaded));
// react lifecycle hook componentWillUnmount
// adjusts state of checkpoint's closure such that handleDataloaded
// is not fired after componentWillUnmount
typescript type-inference
You are invoking youridentity
method with an argument that is a function and the type of a function itself is inferred asany
. I suspect you thought it represented the return type of the result of the function, which is different. Then you need to invoke the function you passed as an argument first.
– Silvermind
Nov 15 '18 at 23:19
add a comment |
I'm running into a typing nuisance:
function identity<T>(v: T): T{ return v; }
function execute(fn: {(n: number):string}) {}
execute((n) => {
// type of n is 'number'
return n.toFixed();
})
execute(identity((n) => {
// type of n is 'any'
return n.toFixed();
}))
When a typed higher-order function execute
receives a function, the arguments of that anonymous function are typed via inference. However, passing that anonymous function to a wrapper identity
function causes those inferred types to be lost. Is there any adjustments I could make to the construction of execute
or identity
that can allow those typings to still be inferred?
NOTE For simplicity, identity
is a pure function here. In actual practice it is not, but should have the same typing as this identity
function. See checkpoint
in context of question for more detail.
see it in the TS Playground
Context
This is the generic form of a problem I was running into when loading data in the context of a React component lifecycle. Because setState
should not be called on a no-longer-mounted component, I prevent the load callback from firing.
function loadData():Promise<MyDataType> {/*...*/}
// Wraps the passed function (handleDataLoaded),
// such that when the returned function is executed the
// passed function is conditionally executed depending
// on closure state.
function checkpoint(fn){/*...*/}
// Add data to the state of the component
function handleDataLoaded(val: MyDataType){/*...*/}
// react lifecycle hook componentDidMount
loadData()
.then(checkpoint(handleDataLoaded));
// react lifecycle hook componentWillUnmount
// adjusts state of checkpoint's closure such that handleDataloaded
// is not fired after componentWillUnmount
typescript type-inference
I'm running into a typing nuisance:
function identity<T>(v: T): T{ return v; }
function execute(fn: {(n: number):string}) {}
execute((n) => {
// type of n is 'number'
return n.toFixed();
})
execute(identity((n) => {
// type of n is 'any'
return n.toFixed();
}))
When a typed higher-order function execute
receives a function, the arguments of that anonymous function are typed via inference. However, passing that anonymous function to a wrapper identity
function causes those inferred types to be lost. Is there any adjustments I could make to the construction of execute
or identity
that can allow those typings to still be inferred?
NOTE For simplicity, identity
is a pure function here. In actual practice it is not, but should have the same typing as this identity
function. See checkpoint
in context of question for more detail.
see it in the TS Playground
Context
This is the generic form of a problem I was running into when loading data in the context of a React component lifecycle. Because setState
should not be called on a no-longer-mounted component, I prevent the load callback from firing.
function loadData():Promise<MyDataType> {/*...*/}
// Wraps the passed function (handleDataLoaded),
// such that when the returned function is executed the
// passed function is conditionally executed depending
// on closure state.
function checkpoint(fn){/*...*/}
// Add data to the state of the component
function handleDataLoaded(val: MyDataType){/*...*/}
// react lifecycle hook componentDidMount
loadData()
.then(checkpoint(handleDataLoaded));
// react lifecycle hook componentWillUnmount
// adjusts state of checkpoint's closure such that handleDataloaded
// is not fired after componentWillUnmount
typescript type-inference
typescript type-inference
edited Nov 19 '18 at 17:17
bjnsn
asked Nov 15 '18 at 23:11
bjnsnbjnsn
1,3581711
1,3581711
You are invoking youridentity
method with an argument that is a function and the type of a function itself is inferred asany
. I suspect you thought it represented the return type of the result of the function, which is different. Then you need to invoke the function you passed as an argument first.
– Silvermind
Nov 15 '18 at 23:19
add a comment |
You are invoking youridentity
method with an argument that is a function and the type of a function itself is inferred asany
. I suspect you thought it represented the return type of the result of the function, which is different. Then you need to invoke the function you passed as an argument first.
– Silvermind
Nov 15 '18 at 23:19
You are invoking your
identity
method with an argument that is a function and the type of a function itself is inferred as any
. I suspect you thought it represented the return type of the result of the function, which is different. Then you need to invoke the function you passed as an argument first.– Silvermind
Nov 15 '18 at 23:19
You are invoking your
identity
method with an argument that is a function and the type of a function itself is inferred as any
. I suspect you thought it represented the return type of the result of the function, which is different. Then you need to invoke the function you passed as an argument first.– Silvermind
Nov 15 '18 at 23:19
add a comment |
2 Answers
2
active
oldest
votes
What you wrote is effectively the same as:
function identity<T>(v: T): T{ return v; }
function execute(fn: {(n: number):string}) {}
execute((n) => {
// type of n is 'number'
return n.toFixed();
})
var func = identity((n) => {
// type of n is 'any'
return n.toFixed();
});
execute(func);
No when you explicitely supply the generic parameter:
var func = identity<number>((n) => {
// type of n is 'any'
return n.toFixed();
});
You will get a compiler error:
Now you see, you are passing a function instead of a number.
If you explain what you are trying to do, we might be able to supply you with a resolution.
add a comment |
There is absolutely no nuisance. It's more like you are experiencing some fault in your logic (in your mind). Not using strict mode is another problem.
/* execute( */ identity((n) => {
// type of n is 'any', why wouldn't it be?
// There is no type constraint in `identity` function,
// hence you have absolutely no reason to expect `n` to have type `number`
// I commented out the wrapping by `execute` function
// so that it doesn't confuse you. Because, no matter
// if it's there or not, you should first figure out
// the shape and type of underlying expression,
// because this is how Typescript figures them out.
return n.toFixed();
}) /* ) */
However
function identity<T extends {(n: number): string}>(v: T): T{ return v; }
/* execute( */ identity((n) => {
// type of n is 'number', because we added a constraint to the type parameter `T` in `identity` function
return n.toFixed();
}) /* ) */
You could also do this:
/* execute( */ identity<{(n: number): string}>((n) => {
// type of n is 'number'
return n.toFixed();
}) /* ) */
And
execute(identity((n: string) => {
// this is a TS error
// "Argument of type '(n: string) => () => string' is not
// assignable to parameter of type '(n: number) => string'"
return n.toFixed;
}))
Finally, you should always use strict mode (add "strict": true
to "compilerOptions" of tsconfig.json) and you will never experience such caveats.
add a comment |
Your Answer
StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "1"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53329169%2ftypescript-wrapper-function-prevents-inferred-typing-of-anonymous-function%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
2 Answers
2
active
oldest
votes
2 Answers
2
active
oldest
votes
active
oldest
votes
active
oldest
votes
What you wrote is effectively the same as:
function identity<T>(v: T): T{ return v; }
function execute(fn: {(n: number):string}) {}
execute((n) => {
// type of n is 'number'
return n.toFixed();
})
var func = identity((n) => {
// type of n is 'any'
return n.toFixed();
});
execute(func);
No when you explicitely supply the generic parameter:
var func = identity<number>((n) => {
// type of n is 'any'
return n.toFixed();
});
You will get a compiler error:
Now you see, you are passing a function instead of a number.
If you explain what you are trying to do, we might be able to supply you with a resolution.
add a comment |
What you wrote is effectively the same as:
function identity<T>(v: T): T{ return v; }
function execute(fn: {(n: number):string}) {}
execute((n) => {
// type of n is 'number'
return n.toFixed();
})
var func = identity((n) => {
// type of n is 'any'
return n.toFixed();
});
execute(func);
No when you explicitely supply the generic parameter:
var func = identity<number>((n) => {
// type of n is 'any'
return n.toFixed();
});
You will get a compiler error:
Now you see, you are passing a function instead of a number.
If you explain what you are trying to do, we might be able to supply you with a resolution.
add a comment |
What you wrote is effectively the same as:
function identity<T>(v: T): T{ return v; }
function execute(fn: {(n: number):string}) {}
execute((n) => {
// type of n is 'number'
return n.toFixed();
})
var func = identity((n) => {
// type of n is 'any'
return n.toFixed();
});
execute(func);
No when you explicitely supply the generic parameter:
var func = identity<number>((n) => {
// type of n is 'any'
return n.toFixed();
});
You will get a compiler error:
Now you see, you are passing a function instead of a number.
If you explain what you are trying to do, we might be able to supply you with a resolution.
What you wrote is effectively the same as:
function identity<T>(v: T): T{ return v; }
function execute(fn: {(n: number):string}) {}
execute((n) => {
// type of n is 'number'
return n.toFixed();
})
var func = identity((n) => {
// type of n is 'any'
return n.toFixed();
});
execute(func);
No when you explicitely supply the generic parameter:
var func = identity<number>((n) => {
// type of n is 'any'
return n.toFixed();
});
You will get a compiler error:
Now you see, you are passing a function instead of a number.
If you explain what you are trying to do, we might be able to supply you with a resolution.
answered Nov 15 '18 at 23:39
SilvermindSilvermind
3,67011733
3,67011733
add a comment |
add a comment |
There is absolutely no nuisance. It's more like you are experiencing some fault in your logic (in your mind). Not using strict mode is another problem.
/* execute( */ identity((n) => {
// type of n is 'any', why wouldn't it be?
// There is no type constraint in `identity` function,
// hence you have absolutely no reason to expect `n` to have type `number`
// I commented out the wrapping by `execute` function
// so that it doesn't confuse you. Because, no matter
// if it's there or not, you should first figure out
// the shape and type of underlying expression,
// because this is how Typescript figures them out.
return n.toFixed();
}) /* ) */
However
function identity<T extends {(n: number): string}>(v: T): T{ return v; }
/* execute( */ identity((n) => {
// type of n is 'number', because we added a constraint to the type parameter `T` in `identity` function
return n.toFixed();
}) /* ) */
You could also do this:
/* execute( */ identity<{(n: number): string}>((n) => {
// type of n is 'number'
return n.toFixed();
}) /* ) */
And
execute(identity((n: string) => {
// this is a TS error
// "Argument of type '(n: string) => () => string' is not
// assignable to parameter of type '(n: number) => string'"
return n.toFixed;
}))
Finally, you should always use strict mode (add "strict": true
to "compilerOptions" of tsconfig.json) and you will never experience such caveats.
add a comment |
There is absolutely no nuisance. It's more like you are experiencing some fault in your logic (in your mind). Not using strict mode is another problem.
/* execute( */ identity((n) => {
// type of n is 'any', why wouldn't it be?
// There is no type constraint in `identity` function,
// hence you have absolutely no reason to expect `n` to have type `number`
// I commented out the wrapping by `execute` function
// so that it doesn't confuse you. Because, no matter
// if it's there or not, you should first figure out
// the shape and type of underlying expression,
// because this is how Typescript figures them out.
return n.toFixed();
}) /* ) */
However
function identity<T extends {(n: number): string}>(v: T): T{ return v; }
/* execute( */ identity((n) => {
// type of n is 'number', because we added a constraint to the type parameter `T` in `identity` function
return n.toFixed();
}) /* ) */
You could also do this:
/* execute( */ identity<{(n: number): string}>((n) => {
// type of n is 'number'
return n.toFixed();
}) /* ) */
And
execute(identity((n: string) => {
// this is a TS error
// "Argument of type '(n: string) => () => string' is not
// assignable to parameter of type '(n: number) => string'"
return n.toFixed;
}))
Finally, you should always use strict mode (add "strict": true
to "compilerOptions" of tsconfig.json) and you will never experience such caveats.
add a comment |
There is absolutely no nuisance. It's more like you are experiencing some fault in your logic (in your mind). Not using strict mode is another problem.
/* execute( */ identity((n) => {
// type of n is 'any', why wouldn't it be?
// There is no type constraint in `identity` function,
// hence you have absolutely no reason to expect `n` to have type `number`
// I commented out the wrapping by `execute` function
// so that it doesn't confuse you. Because, no matter
// if it's there or not, you should first figure out
// the shape and type of underlying expression,
// because this is how Typescript figures them out.
return n.toFixed();
}) /* ) */
However
function identity<T extends {(n: number): string}>(v: T): T{ return v; }
/* execute( */ identity((n) => {
// type of n is 'number', because we added a constraint to the type parameter `T` in `identity` function
return n.toFixed();
}) /* ) */
You could also do this:
/* execute( */ identity<{(n: number): string}>((n) => {
// type of n is 'number'
return n.toFixed();
}) /* ) */
And
execute(identity((n: string) => {
// this is a TS error
// "Argument of type '(n: string) => () => string' is not
// assignable to parameter of type '(n: number) => string'"
return n.toFixed;
}))
Finally, you should always use strict mode (add "strict": true
to "compilerOptions" of tsconfig.json) and you will never experience such caveats.
There is absolutely no nuisance. It's more like you are experiencing some fault in your logic (in your mind). Not using strict mode is another problem.
/* execute( */ identity((n) => {
// type of n is 'any', why wouldn't it be?
// There is no type constraint in `identity` function,
// hence you have absolutely no reason to expect `n` to have type `number`
// I commented out the wrapping by `execute` function
// so that it doesn't confuse you. Because, no matter
// if it's there or not, you should first figure out
// the shape and type of underlying expression,
// because this is how Typescript figures them out.
return n.toFixed();
}) /* ) */
However
function identity<T extends {(n: number): string}>(v: T): T{ return v; }
/* execute( */ identity((n) => {
// type of n is 'number', because we added a constraint to the type parameter `T` in `identity` function
return n.toFixed();
}) /* ) */
You could also do this:
/* execute( */ identity<{(n: number): string}>((n) => {
// type of n is 'number'
return n.toFixed();
}) /* ) */
And
execute(identity((n: string) => {
// this is a TS error
// "Argument of type '(n: string) => () => string' is not
// assignable to parameter of type '(n: number) => string'"
return n.toFixed;
}))
Finally, you should always use strict mode (add "strict": true
to "compilerOptions" of tsconfig.json) and you will never experience such caveats.
edited Nov 16 '18 at 0:29
answered Nov 15 '18 at 23:54
Nurbol AlpysbayevNurbol Alpysbayev
4,7341433
4,7341433
add a comment |
add a comment |
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53329169%2ftypescript-wrapper-function-prevents-inferred-typing-of-anonymous-function%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
You are invoking your
identity
method with an argument that is a function and the type of a function itself is inferred asany
. I suspect you thought it represented the return type of the result of the function, which is different. Then you need to invoke the function you passed as an argument first.– Silvermind
Nov 15 '18 at 23:19