• So You Want to be a Functional Programmer (Part 1)
  • Charles Scalfani
  • The Nuggets translation Project
  • Translator: cdpath
  • Proofreader: luoyaqifei, DeadLion (Jasper Zhong)

Ready to learn functional programming? (Part I)

Taking the first step in understanding functional programming concepts is the most important, and sometimes the hardest. But it doesn’t have to be particularly difficult. It’s easy if you choose the right way to think.

Pilot a spaceship for the first time

Whether you’ve driven one car or a dozen in your life, imagine you’re about to drive a spaceship.

If you’re going to fly a spaceship, you can’t expect to learn how to drive on the road, you have to learn everything from scratch. (We’re programmers, and we count from zero.)

Before we start practicing, we need to remember that everything is different in space, and driving this thing is totally different from driving it on the road.

Physics will not change. What has changed is the way we navigate the same universe.

That’s what learning functional programming is all about. Be prepared for most things to change. And existing programming knowledge doesn’t help much.

Programming is the way of thinking, and functional programming is the new way of thinking. Once you get used to this new way of thinking, you may not even be able to go back.

I forgot everything I knew

People like to say this, and there’s some truth to it. Learning functional programming is like learning from scratch. It’s not accurate, but it makes sense. Functional programming has many similar concepts, but it’s best to relearn everything yourself.

With the right way of thinking, there will be the right expectations, and the right expectations will not give up easily in the face of difficulties.

A lot of the things that you’re used to in functional programming don’t work.

It’s like driving a car when you’re used to backing out of a garage, but when you drive a spaceship, you realize there’s no reverse gear. You think, “What? No reverse?! How the hell am I supposed to drive when I can’t reverse?!”

Well, you don’t actually have to go into reverse gear when you fly a spaceship, because it’s moving in three dimensions. Once you know that, you’ll never think about reversing. In fact, one day you’ll remember how many restrictions you had when you were driving.

Learning functional programming is not that fast. So don’t worry.

So let’s leave the cold world of instruction programming and dive into the hot springs of functional programming.

This article is divided into several sections and then introduces some functional programming concepts that you can look at before delving into your first functional programming language. If you have already started studying them, they will also help you to deepen your understanding.

Don’t be impatient. Read slowly from here, slowly understanding the sample code. You may even want to pause for a moment after reading each verse to let it sink in and then move on.

The most important thing is understanding.

pure

Pure in functional programming languages refers to pure functions.

Pure functions are extremely simple functions that operate only on input parameters.

Here is an example of a pure function in Javascript:

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

Notice that the add function doesn’t touch the z variable. It neither reads nor writes z. It just reads the x and y arguments and returns the sum of the two.

This is a pure function. If add moves the z variable, it’s no longer pure.

Here’s another example:

function justTen() {
    return 10;
}
Copy the code

If justTen were pure, it would only return a constant. Why is that?

Because we haven’t given it any input arguments yet. If it is a pure function, it cannot access anything other than its own input parameters, so it can only return a constant.

Since pure functions with no arguments do nothing and are useless, it’s better to just define justTen as a constant.

Most useful pure functions take at least one argument.

Consider this function:

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

Notice that this function returns nothing. It assigns the sum of x and y to the variable z, but it doesn’t return z.

This is also a pure function, it just handles its own arguments. There’s an addition, but there’s no return, so it doesn’t matter.

A useful pure function always returns something.

Let’s go back to the first add function:

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
Copy the code

Add (1, 2) always returns 3. It turns out, unsurprisingly, that this is a pure function after all. If the Add function uses other external values, it cannot predict its behavior.

A pure function can always produce the same output for the same input.

Because pure functions cannot modify any external variables, the following functions are not pure.

writeFile(fileName);
updateDatabaseTable(sqlCmd);
sendAjaxRequest(ajaxRequest);
openSocket(ipAddress);
Copy the code

All of these functions have side effects. Calling them modifies the tables of files and databases, sends data to the server, or calls the system to fetch sockets. They do more than just process input parameters and return values. So you can never predict what these functions will return.

Pure functions have no side effects.

Instruction programming languages such as JavaScript, Java, and C# are riddled with side effects. This makes debugging difficult, since variables can be changed anywhere. Where do you start when you encounter a bug caused by a variable being modified incorrectly? That’s not good at all.

At this point, you might be thinking, “How on earth can you do anything with pure functions?!”

Functional programming is not about writing pure functions.

Functional programming languages do not eliminate side effects, only limit them. Because functions always have to deal with the real world, every program always has functions that are not pure. The goal is to reduce the amount of non-pure code and isolate it from pure code.

immutability

Remember when you first saw the following code?

var x = 1;
x = x + 1;
Copy the code

Remember who told you to forget something you learned in math class? X in mathematics will never equal x plus 1.

But in instruction programming, this code means adding the current value of x by 1 and assigning it back to x.

But x = x + 1 is an error in functional programming. So remember what you forgot in math class… Something like that.

There are no variables in functional programming.

Stored values are still called variables, but for historical reasons, they’re really constants. For example, once x has a value, the variable stays that value.

Don’t worry, x is usually a local variable with a short lifetime. But as long as it’s there, it doesn’t change.

Here is an example of a constant in Elm (Elm is a purely functional programming language for Web development) :

addOneToSum y z =
    let
        x = 1
    in
        x + y + z
Copy the code

If you’re not familiar with ML style syntax, I’ll explain it here. AddOneToSum is a function that takes two arguments (y and z).

In the let block, x is bound to a value of 1, so it will always be equal to 1 thereafter. The life cycle of X ends when the function exits, or more precisely when the let block is evaluated.

In the IN block, the calculation can use the values defined by the let block, that is, x. The result of x plus y plus z, or more accurately 1 plus y plus z will be returned, because x is equal to 1.

Well, I hear you ask again “How on earth can you do anything with pure functions?!”

Let’s think about when we need to change variables. There are usually two cases: modifying multiple values (such as a value in an object or Record) and modifying a single value (such as a circular counter).

Functional programming modifies a value in a Record by creating a copy of the Record that modifies the value. However, the use of data structures can be very efficient without copying all the attributes in the Record.

Functional programming to modify a single value is also done by creating copies.

Oh, right. This also doesn’t use loops.

“Why no variables and loops?! I hate you!!”

Wait a minute. That’s not to say we can’t use loops (I’m not playing with game text), just that functional programming languages don’t have explicit loop constructs like for, while, Do, and repeat.

The loop of functional programming is implemented recursively.

The following code shows two ways in which Javascript can create loops:

// 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
Copy the code

Look at how recursion in functional programming implements the for loop: it calls itself repeatedly with new starting values (start + 1) and new cumulative values (acc + start). Instead of modifying the old value, it uses the new value calculated from the old value.

Unfortunately, even if you’ve been learning JavaScript for a while, it’s hard to use this for two reasons. One, JavaScript syntax is cumbersome, and two, you’re probably not used to thinking recursively.

Elm is easier to read and understand:

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

The execution process is as follows:

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
Copy the code

You might think the for loop is easier to understand. This is debatable, and more because we are more familiar with the for loop. This non-recursive implementation of the loop requires mutable variables, which is worse.

I haven’t covered all the benefits of immutability here, see the global Mutable State section in why Programmers Need to Limit more.

One obvious benefit, however, is that if you have access to a variable in your program, it is only read-only, which means that no one, not even you, can change the value again. So it saves a lot of unexpected trouble.

Moreover, if the program is multithreaded, other threads cannot interfere with the current thread. If the current thread has a constant and another thread tries to change it, the other thread can only copy the new value with the old value.

When I wrote the Resident Evil game engine in the mid 90’s, the biggest bug source was various multithreading issues. I wish I had known immutability back then. I should have been more worried about the difference in game rendering between 2X and 4x CD-ROM drivers.

Immutability makes code simpler and safer.

My brain !!!!

This part ends here.

In the next installment, I’ll continue with higher-order functions, composition functions, and Currization.

Next: Part 2