The Practical Client

Managing Functions in TypeScript

Functions in TypeScript aren't handled the way you'd expect, based on any other language (including JavaScript). Fortunately, because of the way TypeScript handles functions, you're protected from a common JavaScript error (in addition to getting better IntelliSense support and compile-time checking, of course).

Before beginning, one caveat: For this column, I used the Visual Studio 2015 RC, Community Edition (it's free!) and TypeScript 1.5 beta. However, this column's code will work unchanged in other versions of TypeScript and Visual Studio.

If you come to TypeScript (or to JavaScript, for that matter) from another language, it's easy to misunderstand how functions are handled. The major difference between functions in TypeScript/JavaScript and other languages is that, in TypeScript/JavaScript, a function is just something that a variable can hold. This is obvious when you create a TypeScript function using the syntax in the following code, which defines a variable called FirstName and stores a function in it:

var FirstName = function (cust: Customer): string
{
  return cust.name.substr(0, cust.name.indexOf(" "));
};

However, this code (which looks more familiar to .NET server-side programmers) does exactly the same thing:

function FirstName(cust: Customer): string
{
  return cust.name.substr(0, cust.name.indexOf(" "));
};

Regardless of which of these two syntaxes you use, the code does the same thing: defines a variable called FirstName and stores a function in that variable. Because I'm doing this in TypeScript, I'm able to take advantage of the TypeScript datatyping support to specify a datatype for my cust parameter (it's expecting a Customer object) and what my function will return (in this case, a string).

Because a function is just a variable holding executable code, I can store the function in multiple variables, using the assignment operator (the equals sign: =). This code puts my function in another variable called FirstNameOther:

var FirstNameOther = FirstName;

In that line of code, I didn't execute my FirstName function and store its results in FirstNameOther; instead, I stored a copy of the FirstName function in FirstNameOther. To actually execute the function and store its results in FirstNameOther, I have to add the execution operator -- open and close parentheses: () -- to the end of my variable name. This code, if it compiled, would execute the function in the FirstName variable and store the results in the variable called fname:

var fname = FirstName();

While this code would execute in JavaScript, this code won't even compile in TypeScript because my FirstName function is expecting to be passed a Customer parameter.

As you can imagine, the similarity between the JavaScript code that just copies the function to another variable (no parentheses) and the code that executes a function (with parentheses) can be a source of errors in JavaScript applications. Ninja JavaScript programmers have fingers that just automatically do the right thing. I do not.

I prefer the TypeScript approach: Problems caught at compile time are better than problems that must be found at runtime when there's always someone who has input to my appraisal watching.

DataTyping Functions
The compile-time checks are possible because, in TypeScript, you have implicitly datatyped the variable holding the function just by storing the function in the variable. You can also explicitly datatype a function variable. That datatyping protects you against that JavaScript error of inadvertently storing or executing a function.

To explicitly declare a function datatype you use keyword var, the variable name, a colon, the parameter list, a fat arrow (=>), and the function's return type. For example, to declare a variable called FirstNameTyped to hold a function that accepts a Customer object and returns a string, you'd use this code:

var FirstNameTyped: (cust: Customer) => string;

If a function doesn't return a value, you need to specify that by using the void keyword for the return type, as in this example:

var FirstNameTyped: (cust: Customer) => void;

If your function accepts no parameters, then you still must provide the parentheses that would go around the parameters:

var FirstNameTyped: () => string;

Providing Implementations
Of course, the variable isn't much help unless it's holding a function that implements (and is compatible with) the declaration. To provide a function that implements that declaration, just use the equals sign to assign a function to the variable. This example sets the FirstNameTyped variable to a function compatible with FirstNameTyped's declaration (I'll show a terser way to do this later in this column):

FirstNameTyped =
  function (cust: Customer): string 
  {
    return cust.name.substr(0, cust.name.indexOf(" "));
  };

Here's where TypeScript saves you from the error I mentioned earlier: Because my function variables are datatyped, the following code won't compile because, among other issues, fname is defined as string and TypeScript won't let me store a function in it:

var fname: string;
fname = FirstNameTyped;

This code will also not compile because it attempts to store a string in a variable declared as a function:

var FirstNameAgain: (cust: Customer) => string;
FirstNameAgain = FirstNameTyped();

Both of the following sets of code, on the other hand, will compile and will either assign the result of calling the FirstName function to the variable fname or assign the function to another variable:

var fname: string;
fname = FirstNameTyped();

var FirstNameAgain: (cust: Customer) => string;
FirstNameAgain = FirstNameTyped;

If declaring a variable and then assigning an implementation seems like a lot of typing, you can declare the variable and assign an implementation function in a single statement. This example defines the variable FirstNameTyped and gives it an implementation in one statement:

var FirstNameTyped: (custParm: Customer) => string =
  function (cust: Customer): string
  {
    return cust.name.substr(0, cust.name.indexOf(" "));
  };

If that still seems like a lot of typing, you can also simplify the implementation portion (C# programmers comfortable with lambda expressions will find this syntax very familiar). First, you can omit the function keyword and the datatypes on the implementation (those datatypes are still specified in the datatype declaration); Second, if you have only a single parameter, you can omit the parentheses around the parameter; Finally, if you have only a single line of code inside your function, you can omit the return keyword and replace the curly braces with a fat arrow. As a result, I can cut my declaration and implementation assignment statement down to this:

var FirstNameTyped: (custParm: Customer) => string =
  cust  => cust.name.substr(0, cust.name.indexOf(" ")) ;

On a personal note, I'm not sure that it's helpful to have the fat arrow used for what seems to me like two different things: separating the return type from the parameters in the declaration and separating the parameter list from the function body in the implementation. Life's just like that, though. You might want to consider inserting some line breaks and additional (though not meaningful) parentheses to improve readability:

var FirstNameTyped: ((custParm: Customer) => string)
  = (  cust  => cust.name.substr(0, cust.name.indexOf(" "))  ) ;

While you can now take advantage of datatyping in TypeScript when defining functions, I do realize that this column has been a lot of theory and syntax with not a lot of cool functionality.

Next time, I'm going to show how you can leverage datatyping your functions when passing functions as parameters (implementing the strategy pattern) or when returning functions from a function (implementing callbacks). That will be cool.

About the Author

Peter Vogel is a system architect and principal in PH&V Information Services. PH&V provides full-stack consulting from UX design through object modeling to database design. Peter tweets about his VSM columns with the hashtag #vogelarticles. His blog posts on user experience design can be found at http://blog.learningtree.com/tag/ui/.

comments powered by Disqus

Featured

  • Compare New GitHub Copilot Free Plan for Visual Studio/VS Code to Paid Plans

    The free plan restricts the number of completions, chat requests and access to AI models, being suitable for occasional users and small projects.

  • Diving Deep into .NET MAUI

    Ever since someone figured out that fiddling bits results in source code, developers have sought one codebase for all types of apps on all platforms, with Microsoft's latest attempt to further that effort being .NET MAUI.

  • Copilot AI Boosts Abound in New VS Code v1.96

    Microsoft improved on its new "Copilot Edit" functionality in the latest release of Visual Studio Code, v1.96, its open-source based code editor that has become the most popular in the world according to many surveys.

  • AdaBoost Regression Using C#

    Dr. James McCaffrey from Microsoft Research presents a complete end-to-end demonstration of the AdaBoost.R2 algorithm for regression problems (where the goal is to predict a single numeric value). The implementation follows the original source research paper closely, so you can use it as a guide for customization for specific scenarios.

  • Versioning and Documenting ASP.NET Core Services

    Building an API with ASP.NET Core is only half the job. If your API is going to live more than one release cycle, you're going to need to version it. If you have other people building clients for it, you're going to need to document it.

Subscribe on YouTube