Briebug Blog

Sharing our thoughts with the community

Foundations of Functional Programming in Java/TypeScript (Part 1)

Foundations of Functional Programming in Java/TypeScript (Part 1)

What is functional and what are functions?

Functional programming. You've probably heard the term. You may even have dabbled in it. If you are a veteran, this article will not provide any useful new knowledge. Otherwise, keep reading, and start your journey into the world of functional programming.


This is the start of a multi-part series on the Foundations of Functional Programming in JS/TS. This article aims to lay a solid foundation and understanding of functional programming, how it compares to other kinds of programming paradigms and what functions really are.

"Functional" Programming

First and foremost: what exactly is functional programming? If I told you it was programming with functions, I'm sure you'll end up cocking your head to the side while you quip: "But I already program with functions! Right?!" So let me be more clear:


Functional programming is programming ONLY with functions.


I'll let that sink in for a moment. ONLY functions. But what does that mean? How does that differ from "normal" javascript? Why is it better than what I do now?

What It's Not

There are many styles of programming. Far back, in the earliest days of programming, people actually used something called "punch cards" to program computer-like systems. After that we used assembly languages to control the CPU at a very low level.


Higher level languages introduced procedural and functional means of programming computers. From those early languages, much more advanced languages supporting high level imperative, object-oriented, and even more functional capabilities have been developed.


As complex systems emerged, such as relational database systems, more specialized languages came about. These languages, such as SQL, provided domain-specific capabilities and are often highly declarative in nature.


Declarative languages are often targeted to solving specific problems like querying a database. They tend to expose ready-made functionality, allowing the developer to simply state what they need and want done, and the underlying system deals with the nitty-gritty details of actually getting it done.


Let's dive into some of the ways we can program today, clarify what functional programming is NOT, and also cover some of the things we can't or should not do with it.

Not Imperative

Normal javascript, given how it is most commonly used, is usually "imperative". In other words, programming with explicit statements or instructions that describe the exact mechanisms that control flow and change a program's state. Imperative programs explain to the computer exactly how to perform a task: branch, loop, change program state and to do anything.

Inherent to the imperative style of programming are many potential pitfalls that can give rise to whole classes of bugs. Many of which can often be difficult to track down. Imperative code is often easier to start writing; it is highly explicit about every single aspect of what a given program must do. Exactly what. Exactly how. Reasoning about your code is direct and explicit.


As programs grow, however, imperative code often allows certain idioms to become "crutches" and "focal points" for poor practices. Ultimately leading to large, complex, sprawling codebases that are often harder to understand in the long run than they were to write initially.

Not Object Oriented

Another common paradigm in programming languages is objects. Objects are constructs that we can use to organize related functionality into cohesive units of code that work together to provide some complete piece of functionality. Objects encapsulate functions (called methods) as well as data (called fields, properties) collectively called members.

A class may often provide levels of "protection" for those members. Some members may be private; only accessible within the containing class. Some members may be protected and accessible within the containing or any derived classes. Other members may be public; accessible to consumers of the class as well as the class and its derivatives.

Note how properties of a class are often assigned within its functions. This is a mutation of local state: something that, down the road, can potentially lead to obscure bugs. Mutations are Bad (usually)!


In purely functional languages there are no objects. There may be some level of structured data types. However, they would be more akin to the "structs" of C and C++ where they are purely data without functionality. However, for the most part, functions are the only truly first-class behavioral construct.


JavaScript and TypeScript differ here, a bit, in that they do support classes and objects. Classes have their place. However, it is better to use them sparingly and only when essential. Otherwise, aim to keep your JavaScript/TypeScript code functional whenever possible.

If something does not need to reference this...then it really does not need to be a part of a class!! There may be some exceptions to that rule, and often certain frameworks will impose expectations that ultimately force some functions back into classes at one point or another. Unless and until...if you don't need it, don't create a class to encapsulate functions. Refactor as necessary, when necessary, to pull functions back into classes. In fact, whenever possible, expose existing "bare" or "free" functions in classes (as properties) while also keeping the free function for other use cases.

No Variables!

Another key distinction with functional languages, and a very important one for JavaScript/TypeScript developers, is that ONLY functions are used. With a true functional language, you will often, if not usually, find that "assignment" of values to variables does not happen. Assignment in that fashion does not generally exist in a purely functional language and can violate tenets like immutability outside of special contexts.


No variable assignment. I'll let that one sink in for a moment as well.


Consider: how can you write a function without variables, without assignment? Can you? For sure, it is possible! I write such code myself on a daily basis. So, it is possible. Can YOU do it?


By the end of this series of articles, my goal will be to make sure you can!


It does take a bit of a paradigm shift in how you think about software and about how you write your code. As you continue to read, take a close look at the examples that follow and take note of how, without variable assignment and with only function calls, we are able to implement the same behavior as our imperative code example above.

What It Is

If functional programming is none of these things above, the things you are likely already familiar with, what IS functional programming? As mentioned, at the beginning of the article, it is writing code with only functions (At least as far as is possible, within the restrictions imposed by the frameworks we may use: Angular, React, Vue, Node, Nest, etc.).

What DO we have?

Well, if we don't have variables, what do we have? What can we do and what can we use in functional programming?


We have a lot, actually...in fact, we have many of the same tools we have in other procedural, object-oriented, imperative languages. With JavaScript, we still have objects, which can be useful at times, even if we generally try to avoid them with functional programming! We of course have functions, and not just functions but higher order functions (more in future articles!)


We have parameters that may be passed to functions. We have parameters that may be other functions. We have the return values from functions. Which of course, may also be other functions. Functions accepting functions and functions returning functions are a source of REAL POWER in functional languages.


We have expressions, such as the ternary (b ? t : f) the epitome of basic decision making and branching. We also have nullish coalescing (??), truthy & falsy coalescing (&& and ||), etc. We have standard operators for all those basic little mathematical and logical things we must do in effective programs: +, -, ===, !==, !, %, ^, ~, and so much more.


Expressions and operators provide a significant amount of power to functional programs. The depth of their capabilities is often lost within more complex imperative codebases, where explicit control flow and instructions and override often simpler alternatives, and highly expressive implementations might do the job just as well (and frequently, in far fewer characters!).

Declarative in Nature

Of all the styles of programming originally mentioned, functional programming is often more of a "declarative" style of programming. Not quite as strictly speaking (as is the case with SQL) but with functional programming, we often do rely on the underlying system (language, platform, standard library) to deal with low-level details about getting things done and, more or less, "declare" what we want to do.


At the lowest level, some limited instructional code may exist in the form of basic expressions...but at higher levels, functional programming is highly declarative in nature. We map, filter, and reduce, rather than for, while and do loop. We pattern match or maybe use a ternary, rather than switch or if for branching.

The above code explains what needs to be done (declarative). It does not instruct, line by line, detail by detail, exactly how to achieve what must be done (imperative). The function combineCustomersWithOrdersAndFilterHighTotals above "tells a story" about what it does. It is highly descriptive, completely self-explanatory. Its very name explains what it does, but further...so too does its implementation! Contrast this with the much more complex original imperative implementation above!

Aim to write functional code that is clear and self-explanatory enough to tell little stories as often as you can. Code "stories" eliminate ambiguity in code, limit the amount of cognitive effort that is required to interpret and understand code, and ultimately make your entire code base clearer, more maintainable, and more testable in the long run.

What's Missing Here?

There is some detail missing from the function above. What about the implementation of each of the other functions? Well, they too can be declarative; relying on standard and even third-party libraries to remain as declarative as possible:

Calling .filter on an array is declarative. Calling .map on an array...is declarative. Calling .reduce on an array is...DECLARATIVE! You are not explaining in detail exactly how to do those things...simply what you want done. Further, summing a total, using basic operators...is expressive! Using an equality operator is expressive. Expressions replace a lot of the imperative keyword usage for computation and control flow with functional programming.

Functions

So what, exactly, is a function? I'm sure you know what a function "is": they are one of the most fundamental constructs in most programming languages. However, have you ever stopped to think about exactly, in explicit terms, what a function is and what it is composed of?


Before we can learn how to program with only functions it may be worth taking a closer look at functions themselves and what makes them tick.

Function Anatomy

First off, let's just break down the anatomy of a function. Every function has four basic parts:


1. Function Name *

2. Parameter List *

3. Return Type *

4. Implementation


The starred parts, together, represent the function signature. So at a high level, every function has a signature and an implementation. In some contexts, a function may have more than these four basic parts. With methods in a class, we may also have accessors, and with TypeScript, we may also have generic type definitions and restrictions.


5. Accessors *

6. Generic Type Definition *


These two additional parts also comprise the signature, adding more specificity. A quick example function and breakdown of its parts:

Function Name

The first part of a function is its name. This is an identifier, which is a unique name associated with a particular piece of code: a variable, a parameter, etc. The uniqueness may be scoped, and the same identifier may be used in different, isolated, unrelated scopes. A function name, being an identifier, must conform to the rules about identifier syntax for the language.


It may be helpful to reference the TypeScript rules about identifiers:

Identifiers are names given to elements in a program like variables, functions etc. The rules for identifiers are −


  • Identifiers can include both, characters and digits. However, the identifier cannot begin with a digit.
  • Identifiers cannot include special symbols except for underscore (_) or a dollar sign ($).
  • Identifiers cannot be keywords.
  • They must be unique.
  • Identifiers are case-sensitive.
  • Identifiers cannot contain spaces.

JavaScript's rules for identifiers are much the same...although with pure JS some keywords, in certain circumstances, may be used as identifiers.

Descriptive, Self-Explanatory

In a purely functional language, you will usually have lots of functions. If you enforce the rule of no assignments then you will often find that longer and more descriptive function names are not only useful but often essential (if simply to avoid function name conflicts or convoluted coded names when trying to keep them short).


Function names are one of the few cases where I call for some verbosity. This is not a bad thing. Be descriptive, at least to a degree. Allow your function names to support self-explanatory code. I find that well-disciplined developers will name their functions as descriptively as they can; according to not only what they do but also where and how they are usually used. In other words, contextually descriptive.


Identifiers in general should be as short as you can make them but with function names (in contrast to parameter, variable, and class names). I tend to prioritize descriptiveness over brevity (rare case!). You will see more about why as you continue to read through the article.

JavaScript and TypeScript will not inherently impose these functional paradigm restrictions...this is a matter of DISCIPLINE for the developer!! Be a disciplined developer! Enforce quality code from within. Always. It is the hallmark of a GREAT developer!

Note the nature of these functions. Some of them are implemented as functions...that return functions. Higher order functions! These are an important aspects of writing functional code in an effective manner and allowing the creation of such highly descriptive and self-explanatory code. More on this later in the article.

As a small side note, in the above code: No variables. No assignment. Yes, it is possible! And highly functional!

Input Parameters

Many functions, although not all functions, may also specify one or more input parameters. Input parameters are anything passed to the function when called that the function may reference within the body of the function. Parameters, similar to variables, may represent scalar values (primitives: number, boolean, string, Date, Symbol, etc.) or complex values (objects, arrays, tuples (with TS), custom types (especially with TS), or even other functions!).


Parameters are a very important aspect of functions in general but often critically so with functional programming. They are the primary source of data and how we deal with parameters & provide them to each function, how we process the inputs and convert them to outputs, is a fundamental functional design aspect.

Parameter Names

Like functions, parameters must be named and must follow the same identifier naming rules as functions themselves. Further, within a given scope parameter, names must be unique; including in comparison to the function name itself. Be wary of overloading identifier names within a given scope (including closures!).

With pure functional programming, nested scopes may become common and the possible cases where "hiding" identifiers from a parent scope with identifiers from a child (more nested) scope may increase in frequency. Use descriptive names & avoid hiding identifiers.


Parameter names need not be complex. They need to be unique and specific enough to be properly understood within the context they are used. In some cases, you may find that refactoring parameter names helps to be more specific and less prone to conflict with child scopes (i.e. nested functions that may, in and of themselves, need to use similar parameters). This is an easy improvement if and when necessary:

Functions of lesser scope may then utilize simpler identifiers for their parameters as necessary. Descriptive, but only so far as is necessary!

A small tip for those who like to optimize. You can optimize yourself and the way you work as well! As programmers, we type...a lot. A LOT! It is basically what we do, day in, day out, 8, 10, 12 hours a day. One of the biggest efficiency wins you can give yourself...is to find ways to type less! To that end, be descriptive, clear, and explanatory as possible, whenever possible, with as few characters as possible! These two goals may seem at direct odds with each other...and fundamentally they are...but at times opposing yet concurrent goals lead to the best outcomes!

Note that this recommendation for function parameter names is in some cases at odds with my recommendation for function names. Parameters have a clear and specific local context...the function. That context provides meaning to parameters. Functions, on the other hand often have little to no local context, if they have any clear context in the greater application as a whole outside of the filename they are declared within. Function names, therefore, can often benefit from a certain amount of verbosity.

Another tip: AVOID naming parameters after their types as much as possible! Naming parameters after types is usually not as helpful nor descriptive as it seems, often leads to unnecessary verbosity and excessive typing, and may in fact lead to more confusing code. Greater verbosity may be necessary in some cases, but be descriptive within context, and let the context assist in the reader's understanding of parameter names.

Return Type

The next part of a function is its return type. Some functions may not have a return value, others may have a return value, and some may return an object or an array, or even a tuple (an array of fixed length with fixed value potentials at each index). By default, technically the return type of any function in TypeScript is any, which aligns with the arbitrary and dynamic nature of JavaScript. In some cases, the return type of a function may be inferred from its implementation, and that inferred type may be used down the line when the return value is passed to other functions.

Note: With TypeScript, the compiler option noImplicitAny may be used to prevent implicit any from being used anywhere, and force the developer to explicitly specify the type in all cases (parameters, return types, etc.)

In JavaScript

With plain old JavaScript, the return type is a non-factor. There is no way to define a return type for any function, and the return type is always, in effect, any for lack of a better way to describe it. In actual ECMAScript terms, it would be a variant or a type that can vary in the actual concrete runtime type. That means, a function may return a string in one case, and a number in another, and maybe even a date in another (so on and so forth).

In TypeScript

With TypeScript, return types allow the type checker to constrict what types of values may be returned from a function. Those types may then be clear at the time the developer is implementing code and can help drive the way various functions are composed. If a function returns a value that cannot be used as input to another function, a type error will be displayed.

With TypeScript, return types may be quite complex and may support a wide range of potential output scenarios for a given function, even going so far (with clever generic type definitions) as to DENY usage of a function if a proper output type cannot be inferred from the functions inputs during actual use.


Finally, when using TypeScript, remember that the implicit return type of each function (if not explicitly specified) is usually any, although that can potentially become unknown or even never at times, depending on the exact implementation of the function, other functions it may call, and specific nuances of generic type definitions.


If a function should NOT return a value, then it should be specified as void, or potentially Promise<void> to ensure that TypeScript can validate that no return values are actually returned by the function's implementation. This helps avoid potential subtle classes of bugs.

Implementation

Implementation is where things start to get very interesting with functional programming. There are many tenets that can and should be followed when implementing functional code with Javascript and TypeScript. Fundamentally, Purity, Immutability, and Composability are some of the key tenets that we will cover in future articles.

Prepped and Graded!

For now, this provides a base foundation for functional programming. The plot is cleared, prepped, and graded. Not only should you have a clear understanding of the different parts of a function: you should be able to explain the structure and nature of a function to other developers.


You should also understand some of the best practices involved in naming those parts as well as how to declare functions in an efficient manner with minimal waste. The nomenclature used to describe various parts of a function and best practices, introduced here, around declaring and implementing will be foundational to subsequent articles in this series.


Finally, this article should have imparted at least a high-level understanding of what an arrow function is and to a very basic degree what higher order functions are. We will be exploring these specific concepts as well as concepts like currying, applying curried functions, composing functions with pipes, and Array method chaining. Even leveraging TypeScript generics to constrict typings and enable richer code-time type checks in functional code and more in future installments.

Need Support With a Project?

Start experiencing the peace and security your team needs, and continue getting the recognition you deserve. Connect with us below. 

First Name* Required field!
Last Name* Required field!
Job Title* Required field!
Business Email* Required field!
Phone Required field!
Tell us about your project Required field!
View Details
- +
Sold Out