- So You Want to be a Functional Programmer (Part 3)
- Charles Scalfani
- The Nuggets translation Project
- Translator: Airmacho
- Proofread by Cyseria and Xuxiaokang
Ready to learn functional programming? (Part 3)
To understand functional programming, the first step is always the most important and difficult. But with the right mindset, it’s not too difficult. Previous part: Part 1, Part 2
As programmers, laziness is our virtue. We don’t want to repeatedly build, test, and deploy code that we’ve written.
We want to find a way to write it all in one place and reuse it everywhere.
Code reuse sounds great, but it’s hard to implement. If the code is written explicitly, it cannot be reused. If you generalize too much, it’s hard to use at first.
So we have to trade off, one solution is to write short reusable code that we can use as components to assemble more complex code.
In functional programming, functions are our parts. We can use them to complete assigned tasks and put them together like Lego bricks.
This is called a combination of functions.
To do this, let’s start with two JavaScript functions:
var add10 = function(value) {
return value + 10;
};
var mult5 = function(value) {
return value * 5;
};
Copy the code
This is too verbose, so let’s rewrite it as the arrow function expression:
var add10 = value => value + 10;
var mult5 = value => value * 5;
Copy the code
Much better. Now imagine we need another function that takes a value as an argument, increments it by 10, multiplicates it by 5, and returns the result. We can write it as:
var mult5AfterAdd10 = value => 5 * (value + 10)
Copy the code
Even though this is a simple requirement, we don’t want to write a new function from scratch. First of all, we might make an error by forgetting the parentheses.
Second, we already have a function that adds 10 to the value, and another function that multiplies the value by 5. We’re reinventing the wheel here.
So we build our new function with add10 and mult5:
var mult5AfterAdd10 = value => mult5(add10(value));
Copy the code
We could have created the mult5AfterAdd10 function using an existing one, but there is a better way.
In math, F ∘ G is a combination of functions, so it’s read “CVD of function F and function G” or, more generically, “Call F after G.” So (F ∘ G)(X) is CVD to call CVD function G with X as the independent variable and CVD function F with the result as the independent variable. It is short-written as F (G (x)).
For our example, we can do mult5 ∘ ADD10 or “Mult5 ∘ Add10”, so our function is called MULt5 (ADD10 (value)).
add10 value =
value + 10
mult5 value =
value * 5
mult5AfterAdd10 value =
(mult5 << add10) value
Copy the code
In Elm, you can combine functions with the insert operator <<. This gives us a visual idea of how data flows. First, value is passed to ADD10, and the result is passed as a parameter to Mult5.
Note the parentheses in mult5AfterAdd10, such as (mult5 << add10). The function is composed first and then passed the parameter value.
You can combine functions arbitrarily in this way:
f x =
(g << h << s << r << t) x
Copy the code
Here x passes in t, passes the result to r, passes the result to S, and so on. If you were to implement something similar in JavaScript, it would look like this: g(h(s(r(t(x)))))).
Point – Free representation
There is a way to write functions without specifying parameters, called point-free notation. This style may seem strange at first, but as you continue to use it, you’ll begin to appreciate the simplicity it brings.
You’ll notice that in mult5AfterAdd10 we use the value variable in two places. One is in the parameter list and one is for internal use.
-- This is a function that expects 1 parameter
mult5AfterAdd10 value =
(mult5 << add10) value
Copy the code
This argument is not necessary, because add10, the outermost function in the combination, takes the same argument as the combination of functions. This is equivalent to the point-free version below:
-- This is also a function that expects 1 parameter
mult5AfterAdd10 =
(mult5 << add10)
Copy the code
There are many advantages to using this point-free style notation.
First, we don’t need to specify extra parameters. Because we don’t specify them explicitly, we don’t have to bother naming them.
Second, because it’s simpler, it’s easier to read and understand. This example is very simple, but imagine a function with many arguments.
Trouble in heaven
So far, we’ve seen how function composition works, and how we can use point-free style writing to improve code simplicity, clarity, and flexibility.
Now let’s try using these ideas in a slightly different scenario. Imagine we replace add10 with add:
add x y =
x + y
mult5 value =
value * 5
Copy the code
How can we combine mult5After10 with just these two functions?
Think about this before you read on, think about it, and try to do it.
Well, if you really take the time to think about it, you might come up with something like this:
-- This is wrong !!!!
mult5AfterAdd10 =
(mult5 << add) 10
Copy the code
But it doesn’t work. Why? Because the add function takes two arguments.
Elm may not be obvious, but you can write it in JavaScript:
var mult5AfterAdd10 = mult5(add(10)); // this doesn't work
Copy the code
This code is wrong, why?
Because here the add function takes only one of the two arguments, and then the error result is passed to the mult5 function, the result is also wrong.
In fact, in Elm, the compiler won’t let you write this kind of broken code (one of Elm’s strengths)
var mult5AfterAdd10 = y => mult5(add(10, y)); // not point-free
Copy the code
It’s not point-free, but it works. But instead of a combination of functions, I wrote a new function. Also, if the function is more complex, for example, if I want to combine mult5AfterAdd10 with other functions, that would be a hassle.
The usability of visible function combinations is limited because we cannot combine the two functions together. Too bad.
How can we solve this problem? What do we need to do?
It would be great if we could find a way to make the add function take one argument and then a second one when we call mult5AfterAdd10 later.
There is such a thing as corrification.
My brain!
That’s enough to digest for now.
In the rest of the article, I’ll cover Currification, functions common in functional programming (map, filter, fold, etc.), referential transparency, etc.
And then part four