HGFP-I: Introduction to Functional Concepts

In the first article of the series, “The Humble Guide to Functional Programming” we’ve just covered our primary motivation and talked about the roadmap. Starting with this article, we’ll be exploring the Functional concepts and practical applications.

I’d like to start with a quote that I share in almost every course that I’ve delivered and find pretty useful to share before explaining fundamental concepts in computer science.

“All problems in computer science can be solved by another level of indirection”

– David Wheeler

The quote may sound assertive at first, but if we think about it, as programmers, at the most fundamental level, all we have is ones and zeros. All the other levels that we use on top of these are all abstractions. And we tend to create new levels of abstractions when problems get more complicated.

OOP is a successful and widely used example of an abstraction level that allows us to model our problems around objects and interactions between them that we tend to think about more naturally, instead of just procedures or sequential steps. Also, other conventional layers in OOP like Object Relational Mapping, Dependency Injection, and Application Servers are all different kinds of abstractions.

When things get even more complicated, we create more abstractions with higher level languages or frameworks that are easier to use by hiding unnecessary complexities, for instance, SQL is a good example of that. When we issue a query against a database, we don’t think about specific details of how a particular record fetched, joined or sorted. We just care about the results. This approach allows us to focus more on the business side of the problem rather than thinking about the tedious technical details like data structures, distribution, multithreading and so on.

The important point here is that while the level of abstraction increases, the question “WHAT” becomes more apparent than “HOW“.

Ok, this is important. Before turning back to it, let’s talk about another important concept: “paradigms“.

A paradigm can be defined as a “way of doing something”. Thus, programming paradigms enable us to solve problems in different ways. You are probably already familiar with the Object Oriented Programming, also a paradigm. Functional programming is one of the different paradigms that aim to express programs and solve problems in different ways.

Paradigms in the programming context can be thought of different ways of abstractions and solving problems. When we’re using OOP, we think about objects and their relationships, and when we’re using Functional Programming, of course, we are thinking about functions and combinations of them.

One may argue that in Computer Science, there is almost no innovation made (at least at the academic level) since 70’s and we’re now just implementing and finding practical ways of usage for what discovered along the past years, using more advanced computing capabilities.

Machine learning, fundamental concepts of big data, ancestors of all programming languages that embrace all the core concepts we have and use in modern languages now, were already there by the end of 70’s. But of course, the hardware wasn’t capable of taking advantage of what is available at the theoretical level and there were mostly no need to do so.

Functional programming is also a concept that has theoretical roots even much earlier, mid-1930’s, with the invention of Lambda Calculus by a mathematician, Alonzo Church.

We can basically say that the Lambda Calculus can be considered as the smallest universal programming language: any computable function can be evaluated in the context of it. It provides a theoretical framework for describing functions and their evaluation. The term “lambda” is used commonly in programming languages which support functional concepts to define the functions that have the academic definitions in Lambda Calculus.

So, enough talking. Finally, let’s start with an example that probably we’re all familiar with.

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
for (int i=0; i<numbers.size(); i++) {
    int val = numbers.get(i);

Here we’re just looping over a list and printing the members of it to the console. This style of programming is very common in OOP languages and is a paradigm on its own called “imperative programming”.

In Imperative Programming, we keep changing the state of the program by using statements designed to do so. We will talk about the differences between imperative and functional programming in the following articles of the series.

Let’s have a look at the functional alternative to the same code using Java 8 streams lambda expressions.

numbers.forEach(i -> System.out.println(i));

Without sticking to the syntax, for now, let’s just think about the differences.

You still remember the importance of and difference between “HOW” and “WHAT“, right ? Now it’s time to recall that. If you look at the examples above, which question would you say more apparent compared to the other ?

I guess we would agree on saying that in the imperative example, the question “HOW” is more apparent than “WHAT”. Because we are dealing with the dull details of how the collection we’re messing with suppose to hold the objects inside, how can we decide the current size of it, how we suppose to get each element and assign it to a value, etc.

This is a lot to know and do. And especially if we think that all we really want to do was printing the values to the console. Thus, what matters for us is applying a function to all members of the list. In our example, that function supposed to print the values to the console.

That’s exactly the (more) functional alternative did. We just gave the function to be applied to each member of the list, and didn’t care about “how” actually it gets done under the hood by trusting the implementor of the API to do it in an efficient way. Because we’re not suffering from NIH syndrome, and have lots of good reasons to trust the implementors of the language.

So, let’s look at another, more sophisticated usage of lambda expressions with collections:

IntStream.range(1, 1000).filter(i -> i % 2 == 1).sum();

In this example, we are taking advantage of IntStreams by giving it a range. It  simply creates a stream of integers from 1 to 1000. Then we filter the stream the same way we did in the list example, and because we’re dealing with a stream of integers, the API allows us to calculate basic mathematical operations on them like sum in this case.

Let’s wrap up with a different taste of functional programming and leave the explanations of lambda expressions in greater detail and the reasons of behind the popularity of functional paradigm to the next articles.

IntStream.range(1, 1000).parallel().filter(i -> i % 2 == 1).sum()

What is the difference in this code snippet ? The “parallel()” function call, right ? What do you think it does ?

Yes, you’ve guessed right. It executes the supported subsequent operations in parallel, without expecting anything from you. Reflect on how difficult would it be to do the same in imperative style. And again, think about the difference between imperative and functional abstractions.

Hope you enjoyed this article. As I’ve promised, we’ll cover both the reasons for using functional approach and practical applications of it in the next articles. Your comments and feedbacks are always welcome. And also feel free to reach me via Twitter and Linkedin.

See you with the next article.



Please enter your comment!
Please enter your name here