+6

🧐So You Want to be a Functional Programmer (Part 1)

The first step to understanding Functional Programming is the hardest, but it doesn't have to be if you have the right attitude.

Learning to Drive

image.png

When we first started learning to drive, it seemed easy when we watched other people do it, but it was harder than we expected. We practiced in our parents' car and only drove on the highway once we had mastered the streets in our neighborhood.

After a lot of practice and some scary moments, we got our license and drove our car whenever we could. We got better and better and our confidence grew. Eventually, we had to drive someone else's car or buy a new one. The first time was strange because we had only been a passenger before, but the second time was easier because the car was similar to the old one.

Learning programming languages is similar - the first is the hardest, but subsequent ones are easier. You ask questions like how to create a module or search an array, and you know you can learn the language because it's similar to the one you already know, with a few new features.

Your First Spaceship

image.png

No matter how much experience you have driving cars, flying a spaceship would be a completely new experience. You would need to start from the beginning and learn how to fly the spaceship, as the skills you have from driving on the ground would not be useful.

Similarly, when learning Functional Programming, you should expect that things will be very different and that much of what you know about programming will not be applicable. Functional Programming will teach you to think differently and you may never go back to the old way of thinking.

Forget Everything You Know

image.png

People often say that learning Functional Programming is like starting from scratch, but it's not completely true. There are some similarities, but it's best to expect that you will need to relearn a lot of things. If you have the right attitude and expectations, you won't give up when things get difficult.

There are a lot of things that you are used to doing as a programmer that you can't do with Functional Programming, like not having a reverse gear in a car. It's important to understand that you don't need reverse in a spaceship because of its ability to move in three dimensions.

Learning Functional Programming takes time, so be patient and take your time to understand the concepts and coding examples. Don't rush, and make sure you understand everything before you move on.

Purity

image.png

When Functional Programmers talk about purity, they mean that a function only uses its input parameters and does not have any other effects. An example of a Pure Function in Javascript is a function that only uses its input parameters and does not have any other effects.

Here’s an example in Javascript of a Pure Function:

var z = 10;
function add(x, y) {
    return x + y;
}

The add function only uses the inputs x and y and does not interact with any other variables. It simply adds the two inputs together and returns the result. If the add function did interact with other variables, it would no longer be considered a pure function.

Here’s another function to consider:

function justTen() {
    return 10;
}

A pure function called justTen can only return a constant value because it has no inputs. This is not very useful, so it would be better to define justTen as a constant instead. To be useful, a pure function must take at least one parameter.

Consider this function:

function addNoReturn(x, y) {
    var z = x + y
}

This function takes two numbers, adds them together, and stores the result in a variable, but it does not give the result back. This means it is a pure function, since it only works with the inputs it is given. However, since it does not return anything, it is not useful. All pure functions must return something in order to be useful.

Let’s consider the first add function again:

function add(x, y) {
    return x + y;
}
console.log(add(1, 2)); // prints 3
console.log(add(1, 2)); // still prints 3
console.log(add(1, 2)); // WILL ALWAYS print 3

The add function will always produce the same result when given the same inputs. However, if the add function uses an outside value, then the result may be unpredictable.

writeFile(fileName);
updateDatabaseTable(sqlCmd);
sendAjaxRequest(ajaxRequest);
openSocket(ipAddress);

Functions in imperative programming languages like Javascript, Java, and C# often have side effects, which means they do more than just take in inputs and return outputs. This makes debugging difficult because a variable can be changed anywhere in the program, making it hard to find the source of the bug. Pure functions, on the other hand, have no side effects.

You are probably wondering now, “HOW THE HELL DO I DO ANYTHING WITH ONLY PURE FUNCTIONS?!”

In Functional Programming, you cannot completely avoid writing code that has side effects, as programs need to interact with the real world. However, you should try to minimize the amount of code with side effects and keep it separate from the rest of the program.

Immutability

image.png

Do you remember when you first saw the following bit of code:

var x = 1;
x = x + 1;

In Functional Programming, you cannot change the value of a variable once it has been set. This is different from Imperative Programming, where you can take the current value of a variable, add 1 to it, and put the result back into the variable. In Elm, a Pure Functional Programming Language for Web Development, variables are constants and cannot be changed.

addOneToSum y z =
    let
        x = 1
    in
        x + y + z

If you don't know what ML-Style syntax is, I can explain. The function addOneToSum takes two numbers, y and z. Inside the function, the number 1 is assigned to the variable x. The result of the calculation 1 + y + z is then returned.

Once again, I can hear you ask “HOW THE HELL AM I SUPPOSED TO DO ANYTHING WITHOUT VARIABLES?!”

When we want to change variables, there are two main types of changes: changing one value of an object or record (multi-valued changes) and changing a loop counter (single-valued changes). Functional programming solves these changes by making a copy of the record or loop counter with the changed values, without having to copy all parts of the record. It does this efficiently by using data structures that make this possible, and without using loops.

“WHAT NO VARIABLES AND NOW NO LOOPS?! I HATE YOU!!!”

Wait a minute, we can still do looping in Functional Programming, it's just that there are no specific loop commands like for, while, do, repeat, etc. Instead, we use recursion to do looping. Here are two ways to do looping in Javascript.

// simple loop construct
var acc = 0;
for (var i = 1; i <= 10; ++i)
    acc += i;
console.log(acc); // prints 55
// without loop construct or variables (recursion)
function sumRange(start, end, acc) {
    if (start > end)
        return acc;
    return sumRange(start + 1, end, acc + start)
}
console.log(sumRange(1, 10, 0)); // prints 55

Recursion is a way of achieving the same result as a for loop, but instead of modifying the old values, it uses new values calculated from the old. This can be hard to understand in Javascript because the syntax is complicated and people are not used to thinking recursively. In Elm, it is easier to read and understand.

sumRange start end acc =
    if start > end then
        acc
    else
        sumRange (start + 1) end (acc + start) 

Here’s how it runs:

sumRange 1 10 0 =      -- sumRange (1 + 1)  10 (0 + 1)
sumRange 2 10 1 =      -- sumRange (2 + 1)  10 (1 + 2)
sumRange 3 10 3 =      -- sumRange (3 + 1)  10 (3 + 3)
sumRange 4 10 6 =      -- sumRange (4 + 1)  10 (6 + 4)
sumRange 5 10 10 =     -- sumRange (5 + 1)  10 (10 + 5)
sumRange 6 10 15 =     -- sumRange (6 + 1)  10 (15 + 6)
sumRange 7 10 21 =     -- sumRange (7 + 1)  10 (21 + 7)
sumRange 8 10 28 =     -- sumRange (8 + 1)  10 (28 + 8)
sumRange 9 10 36 =     -- sumRange (9 + 1)  10 (36 + 9)
sumRange 10 10 45 =    -- sumRange (10 + 1) 10 (45 + 10)
sumRange 11 10 55 =    -- 11 > 10 => 55
55

It may seem like for loops are easier to understand, but non-recursive loops require Mutability, which is not ideal. Immutability has many benefits, such as preventing accidental mutations and making multi-threaded programs safer. If I had known about immutability when I was writing a game engine in before, I would have had fewer bugs. Immutability makes code simpler and safer.

My Brain!!!!

image.png

That is all I will discuss in this article, but I will talk about Higher-order Functions, Functional Composition, Currying and other topics in future articles.

Up next: Part 2

Mình hy vọng bạn thích bài viết này và học thêm được điều gì đó mới.

Donate mình một ly cafe hoặc 1 cây bút bi để mình có thêm động lực cho ra nhiều bài viết hay và chất lượng hơn trong tương lai nhé. À mà nếu bạn có bất kỳ câu hỏi nào thì đừng ngại comment hoặc liên hệ mình qua: Zalo - 0374226770 hoặc Facebook. Mình xin cảm ơn.

Momo: NGUYỄN ANH TUẤN - 0374226770

TPBank: NGUYỄN ANH TUẤN - 0374226770 (hoặc 01681423001)

image.png


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí