Pointfree programming style guide
This article will answer a very important question: What is the use of functional programming ?
At present, the mainstream programming languages are not functional, and they have been able to meet the demand. Why do you still need to learn functional programming, just to understand more novel concepts?
A netizen said:
“What are the advantages of functional programming?”
“I feel that this way of writing may be a headache.”
For a long time, I didn’t know where to start and how to use it for actual projects? Until one day, I learned the concept of Pointfree, and I suddenly understood that I should use it like this!
I now think that Pointfree is the answer to how to use functional programming.
1. The nature of the program
In order to understand Pointfree, please think about it first, what is a program?
The picture above is a programming task, the left side is the data input (input), the middle is a series of calculation steps to process the data, and the right side is the final data output (output). One or more of these tasks constitute a program.
Input and output (collectively referred to as I/O) are related to keyboards, screens, files, databases, etc., which have nothing to do with this article. The key here is that the middle arithmetic part cannot have I/O operations, it should be pure arithmetic, that is, it is evaluated through pure mathematical operations. Otherwise, another task should be split.
I/O operations often have ready-made commands. Most of the time, programming is mainly about writing the middle part of the operation logic. Now, the mainstream writing methods are procedural programming and object-oriented programming, but I think the most suitable pure operation is functional programming.
Second, the split and synthesis of functions
In the above figure, the calculation process can be represented by a function
fn
.
fn
The types are as follows.
fn :: a -> b
The above formula means that
fn
the input of the
function
is data
a
, and the output is data
b
.
If the operation is more complicated, it usually needs to be
fn
split into multiple functions.
f1
,
f2
,
f3
The following types.
f1 :: a -> m f2 :: m -> n f3 :: n -> b
In the above formula, the input data is still the same
a
, and the output data is still the same
b
, but there are two more intermediate values
m
and the sum
n
.
We can think of the entire calculation process as a pipe, with data coming in from this end and coming out from the other end.
The splitting of the function is nothing more than splitting one water pipe into three.
The data that goes in is still the same
a
, and the data that comes out is still
b
.
fn
And
f1
,
f2
,
f3
the relationship is as follows.
fn = R.pipe(f1, f2, f3);
In the above code, I used the
Ramda
function library
pipe
method to combine three functions into one.
Ramda is a very useful library. The following examples will use it. If you don’t know it yet, you can read the
tutorial
first
.
Third, the concept of Pointfree
fn = R.pipe(f1, f2, f3);
This formula shows that if the first definition of
f1
,
f2
,
f3
, can be calculated
fn
.
There is no need to know
a
or
during the whole process
b
.
In other words, we can completely define the process of data processing as a synthetic operation that has nothing to do with parameters. There is no need to use the parameter representing the data, just combine some simple calculation steps together.
This is called Pointfree: the value to be processed is not used, only the operation process is synthesized. Chinese can be translated into “no value” style.
Please see the example below.
var addOne = x => x + 1; var square = x => x * x;
The above are two simple functions
addOne
and
square
.
Combine them into one operation.
var addOneThenSquare = R.pipe(addOne, square); addOneThenSquare(2) // 9
In the above code, it
addOneThenSquare
is a composite function.
When defining it, there is no need to mention the value to be processed at all, this is Pointfree.
Fourth, the essence of Pointfree
The essence of Pointfree is to use some common functions to combine various complex operations. The upper-level calculations do not directly manipulate the data, but use the lower-level functions to process it. This requires that some commonly used operations are encapsulated into functions.
For example, to read the
role
properties of
an object
, don’t write
obj.role
it
directly
, but encapsulate this operation as a function.
var prop = (p, obj) => obj[p]; var propRole = R.curry(prop)('role');
In the above code, the
prop
function encapsulates the read operation.
It requires two parameters
p
(property name) and
obj
(object).
At this time, the data
obj
should be placed in the last parameter, which is for the convenience of currying.
The function
propRole
is to specify the read
role
attribute, the following is its usage (see the
full code
).
var isWorker = s => s === 'worker'; var getWorkers = R.filter(R.pipe(propRole, isWorker)); var data = [ {name: '张三', role: 'worker'}, {name: '李四', role: 'worker'}, {name: '王五', role: 'manager'}, ]; getWorkers(data) // [ // {"name": "张三", "role": "worker"}, // {"name": "李四", "role": "worker"} // ]
In the above code, it
data
is the value passed in,
getWorkers
which is a function to process this value.
When it was defined
getWorkers
, it was not mentioned at all
data
. This is Pointfree.
Simply put, Pointfree is the abstraction of the calculation process, processing a value, but does not mention the value. This has many advantages. It can make the code clearer and concise, more semantically consistent, easier to reuse, and easier to test.
Five, Pointfree example one
Below, we look at an example.
var str = 'Lorem ipsum dolor sit amet consectetur adipiscing elit';
The above is a string. How many characters does the longest word have?
We first define some basic operations.
// 以空格分割单词 var splitBySpace = s => s.split(' '); // 每个单词的长度 var getLength = w => w.length; // 词的数组转换成长度的数组 var getLengthArr = arr => R.map(getLength, arr); // 返回较大的数字 var getBiggerNumber = (a, b) => a > b ? a : b; // 返回最大的一个数字 var findBiggestNumber = arr => R.reduce(getBiggerNumber, 0, arr);
Then, combine the basic operations into a function (see the full code ).
var getLongestWordLength = R.pipe( splitBySpace, getLengthArr, findBiggestNumber ); getLongestWordLength(str) // 11
It can be seen that the whole operation consists of three steps, and each step has a semantic name, which is very clear. This is the advantage of Pointfree style.
Ramda provides a lot of ready-made methods, you can use these methods directly, without having to define some common functions yourself (see the complete code ).
// 上面代码的另一种写法 var getLongestWordLength = R.pipe( R.split(' '), R.map(R.length), R.reduce(R.max, 0) );
Six, Pointfree example two
Finally, look at a practical example, copied from Scott Sauyet’s article “Favoring Curry” . That article can help you understand currying in depth, and it is highly recommended to read it.
The following is a piece of JSON data returned by the server.
The requirement now is to find all outstanding tasks of user Scott and sort them in ascending order of due date.
The code of procedural programming is as follows (see the complete code ).
The above code is not easy to read, and there is a high possibility of error.
Now rewrite it in Pointfree style (see the full code ).
var getIncompleteTaskSummaries = function(membername) { return fetchData() .then(R.prop('tasks')) .then(R.filter(R.propEq('username', membername))) .then(R.reject(R.propEq('complete', true))) .then(R.map(R.pick(['id', 'dueDate', 'title', 'priority']))) .then(R.sortBy(R.prop('dueDate'))); };
The above code is much clearer.
Another way of writing is to combine
then
the functions inside (see the
complete code
).
// 提取 tasks 属性 var SelectTasks = R.prop('tasks'); // 过滤出指定的用户 var filterMember = member => R.filter( R.propEq('username', member) ); // 排除已经完成的任务 var excludeCompletedTasks = R.reject(R.propEq('complete', true)); // 选取指定属性 var selectFields = R.map( R.pick(['id', 'dueDate', 'title', 'priority']) ); // 按照到期日期排序 var sortByDueDate = R.sortBy(R.prop('dueDate')); // 合成函数 var getIncompleteTaskSummaries = function(membername) { return fetchData().then( R.pipe( SelectTasks, filterMember(membername), excludeCompletedTasks, selectFields, sortByDueDate, ) ); };
Comparing the above code with the procedural writing method, it is clear which is better at a glance.
Seven, reference link
(over)