Typescript: wrapper function prevents inferred typing of anonymous function












2















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












share|improve this question

























  • 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


















2















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












share|improve this question

























  • 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
















2












2








2


1






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












share|improve this question
















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






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited Nov 19 '18 at 17:17







bjnsn

















asked Nov 15 '18 at 23:11









bjnsnbjnsn

1,3581711




1,3581711













  • 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



















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














2 Answers
2






active

oldest

votes


















1














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:



enter image description here



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.






share|improve this answer































    1














    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.






    share|improve this answer

























      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
      });


      }
      });














      draft saved

      draft discarded


















      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









      1














      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:



      enter image description here



      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.






      share|improve this answer




























        1














        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:



        enter image description here



        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.






        share|improve this answer


























          1












          1








          1







          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:



          enter image description here



          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.






          share|improve this answer













          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:



          enter image description here



          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.







          share|improve this answer












          share|improve this answer



          share|improve this answer










          answered Nov 15 '18 at 23:39









          SilvermindSilvermind

          3,67011733




          3,67011733

























              1














              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.






              share|improve this answer






























                1














                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.






                share|improve this answer




























                  1












                  1








                  1







                  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.






                  share|improve this answer















                  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.







                  share|improve this answer














                  share|improve this answer



                  share|improve this answer








                  edited Nov 16 '18 at 0:29

























                  answered Nov 15 '18 at 23:54









                  Nurbol AlpysbayevNurbol Alpysbayev

                  4,7341433




                  4,7341433






























                      draft saved

                      draft discarded




















































                      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.




                      draft saved


                      draft discarded














                      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





















































                      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







                      Popular posts from this blog

                      Bressuire

                      Vorschmack

                      Quarantine