Functional TypeScript
At Halodoc, we as developers extensively use the below functional programming concepts in Typescript.
TypeScript is not a purely functional programming language but offers a lot of concepts that are in line with functional programming languages. Most of the developers are oblivious to these concepts in TypeScript. So let us discuss how TypeScript helps us in applying functional programming concepts in our code.
1. First-Class and Higher-Order Functions
1.1 First-Class Functions
A programming language is said to have First-class functions when functions in that language are treated like any other variable. In this type of programming language, functions can be assigned to variables, you can pass a function as an argument to another function and also return a function from another function.(Here functions can also be referred as First-class citizen).
1.2 Higher-Order Functions
A Higher-order-function is a function that accepts one or more functions as parameters or returns another function as a result.
First-class functions is supported by TypeScript which makes concepts like Closures, Currying, and Higher-order-functions easy to implement in our code.
In TypeScript, we can easily do this:
But in TypeScript we can also achieve the above functionality using built-in functional methods like filter
:
We can use the built-in declarative Higher-order-functions like map
, reduce
, forEach
, filter
etc provided by TypeScript/JavaScript instead of creating custom functions in our code.
2. Currying
From the Higher-order functions, we know now that it is possible for functions to accept and return functions. Currying is basically the fact of nesting returning functions and being able to partially consume a function. Curried function takes arguments one at a time.
For example :
Now compute
is very compact, it’s a function that takes a number and returns a new function. It's type is number => newFunctionType
. This new function takes a function fn
and returns a number. So newFunctionType
is fType => number
. At this step, compute type is number => (fType) => number
. fn
is a function that takes a number and returns a number, number => number Therefore compute is number => (number => number) => number
.
In the above example partialResult
returns a function because we consumed only the first argument. Passing value 5 to compute
, fn
stored it in its lexical scope (closure)
. And now partialResult
is waiting for a function with type number => number
to get the final result.
3. Lazy evaluation
The process of delaying the evaluation of an expression until it is needed in the code is known as Lazy evaluation. It can be also referred as Non-strict evaluation.
Generally, TypeScript does strict evaluation but for operands like &&
, ||
and ?:
it does a lazy evaluation. We can easily make use of Higher-order-functions in Typescript to do lazy evaluations.
For example :
The above code will produce the below output and we can see that both functions are always executed :
We can use Higher-order functions to rewrite the above piece of code into a lazily evaluated version as shown below :
Lazy-evaluation of expression produces the below output and we can see that only required functions were executed :
4. Referential Transparency
JavaScript does not provide us with many ways to strictly limit data mutation, however by using only pure functions and by explicitly avoiding data mutations and reassignment, data mutations can be avoided. By default primitive variables are passed by value and objects are passed by reference in JavaScript so we need to take care not to mutate data inside functions. We should use const
as much as possible to avoid reassignments.
For example, the below piece of code will produce an error :
But the below piece of code will mutate the data when variables are holding references to other objects. For example, the below mutation will work irrespective of the const
keyword :
As const
keyword allows the mutation of internal state of referenced variables, from a Functional programming view point const
keyword is useful only for primitive constants and to catch reassignments.
However, TypeScript helps us to deal with the above situation by providing special mapped types like readonly
. We can use these special mapped types to make objects read-only, avoiding accidental data mutations which are caught during compile time as shown below :
5. Recursion
In computer science, Recursion is the process of solving a problem where the solution depends on solutions of smaller instances of the same problem. Functional programming favors recursion over looping. Let us see an example for calculating the sum of first n natural numbers.
In traditional iterative approach:
The same can be done using recursion as below :
5.1 Tail Recursion
The drawback of solving a problem with recursive approach is that most of the times it will be slower compared to its iterative approach counterpart.
JavaScript engine uses a call stack to manage execution contexts: the Global Execution Context and Function Execution Contexts.
The recursive approach might result in stack overflow errors since every function call needs to be saved as a frame in the call stack.
We are aiming to achieve simplicity and readability in our code through recursion. To avoid the above issue tail recursion should be preferred while solving a problem with recursive approach. In tail recursion, the recursive call is the last thing executed by the function and hence the function stack frame need not to be saved by the compiler.
Now using tail recursion the above recursive function can be written as below:
Summary
In this blog, we have discussed how we can apply some of the functional programming concepts in TypeScript. Hope this blog will help you to incorporate some of these functional programming concepts in your TypeScript projects.
We are always looking out for top engineering talent across all roles for our tech team. If challenging problems that drive big impact enthral you, do reach out to us at careers.india@halodoc.com
About Halodoc
Halodoc is the number 1 all around Healthcare application in Indonesia. Our mission is to simplify and bring quality healthcare across Indonesia, from Sabang to Merauke.
We connect 20,000+ doctors with patients in need through our Tele-consultation service. We partner with 2500+ pharmacies in 100+ cities to bring medicine to your doorstep. We've also partnered with Indonesia's largest lab provider to provide lab home services, and to top it off we have recently launched a premium appointment service that partners with 500+ hospitals that allows patients to book a doctor appointment inside our application.
We are extremely fortunate to be trusted by our investors, such as the Bill & Melinda Gates foundation, Singtel, UOB Ventures, Allianz, Gojek and many more. We recently closed our Series B round and In total have raised USD$100million for our mission.
Our team work tirelessly to make sure that we create the best healthcare solution personalized for all of our patient's needs, and are continuously on a path to simplify healthcare for Indonesia.