I have heard of functional programming for a long time, and I remember it as a very obscure programming mode, but I have not been to understand it.

It happened to be my turn to host the week of the group, so I didn’t think of anything to share. In a flash, I chose the topic of functional programming. Anyway, my colleagues in the group have not learned it, so I just need to explain the basic knowledge, and I can take this opportunity to force myself to learn this new way of programming.

In addition to Lisp, Scheme, Haskell, Erlang, and other specialized functional programming languages, Many of the general-purpose programming languages we use (Java, Python, Ruby, Javascript, etc.) support the functional programming pattern. In consideration of the actual situation, Python was chosen as the starting language for functional programming, since the team was familiar with Python and it would not be too difficult to use it as a starting point.

After information query and preliminary study, I have some concepts of functional programming, and formed PPT sharing after sorting.

Here’s what we shared.

The target

When learning a new technology or programming language, we usually start with concepts and features. For those new to functional programming, there are a few things you might want to know:

  • What is functional programming?
  • What are the characteristics of functional programming?
  • What is functional programming for?
  • What are the advantages and disadvantages of functional programming compared to imperative and object-oriented programming?

But I didn’t go that far with this presentation, because I felt that by filling the audience with too many concepts at the beginning, they would get confused. Because a search of data shows that it is difficult to have a coherent definition of what functional programming is. And since I am new to it, there may be a big deviation in my understanding.

Therefore, I decided to start with the familiar imperative programming as much as possible, and show the audience how functional programming can be thought of differently with lots of examples. After that, I will look back at these questions and believe that the audience should have a deeper understanding.

Taking the actual situation into consideration, we hope to achieve the following goals:

  • Understand the main differences between functional and imperative programming
  • Master the basic functions and operators of Python functional programming
  • Converts simple imperative programming statements into functional programming

Imperative programming & Functional programming

Starting with the familiar imperative of programming, let’s review the main scenarios of writing code.

In fact, no matter how complex our business code is, it needs the following types of operations:

  • Function definition: def
  • Conditional controls: if, elif, else
  • Loop control: for, break, continue, while

Of course, this is only part of the operation type, but there should also be classes and modules, exception handling, and so on. But for starters, let’s just focus on the three most common operations.

Functional programming, in turn, has its own keywords. In Python, functional programming consists mainly of three basic functions and one operator.

  • Basic functions: map(), reduce(), filter()
  • Operator: lambda

Surprisingly, you can implement almost any Python program with just a few functions and operators.

Of course, it’s one thing to implement it; it’s another thing to actually code it. It would be awkward to write all of your code in just a few basic units, both in terms of presentation and reading. However, trying to use these basic units in place of function definition, conditional control, loop control, and so on, as described above, should be helpful in understanding how functional programming expresses flow control through functions and recursion.

Before attempting to convert imperative programming into functional programming, we need to familiarize ourselves with these basic units.

The basic unit of Python functional programming

lambda

Lambda is a keyword that exists in many languages. In short, it can achieve the function creation function.

Here are two ways lambda can be used.

func1 = lambda : <expression()>
func2 = lambda x : <expression(x)>
func3 = lambda x,y : <expression(x,y)>Copy the code

In the first statement, lambda creates a function func1 with no arguments. This is the same as creating a function using def below.

def func1(a):
    <expression()>Copy the code

In the second and third statements, lambda creates a function func2 that takes one argument and a function func3 that takes two arguments, respectively. This is the same as creating a function using def below.

def func2(x):
    <expression(x)>

def func3(x,y):
    <expression(x,y)>Copy the code

It is important to note that when func1 is called, it does not take an argument, but it must have parentheses (); otherwise it returns the definition of the function, not the result of its execution. This is different from calling a no-parameter function in Ruby, and ruby programmers are expected to take note.

>>> func = lambda : 123
>>> func
<function <lambda> at 0x100f4e1b8>
>>> func()
123Copy the code

Also, although the lambda created function is assigned to a function name in each of the above examples, this is not required. As you can see from the example below, many times we call functions created by lambda directly without naming a function, which is where we often hear about anonymous functions.

map()

A common call to the map() function looks like this:

map(func, iterable)Copy the code

Map () takes two mandatory arguments, the first a function name and the second an iterable object such as a list, tuple, and so on.

Map () simply passes each element of the second argument (iterable) to the first argument (func), executes the function in turn, and returns the result as a new list object. The return result is always a list.

A simple example is as follows:

>>> double_func = lambda s : s * 2
>>> map(double_func, [1.2.3.4.5[])2.4.6.8.10]Copy the code

In addition to the common pattern of passing in one iterable, map() supports passing in multiple iterables.

map(func, iterable1, iterable2)Copy the code

In the case of multiple iterables, map() takes one element in turn from all iterables, forming a list of tuples, and passes the tuples in turn to func; If the length of the iterable is inconsistent, None is used to fill it up.

The following example should make it easier to understand.

>>> plus = lambda x,y : (x or 0) + (y or 0)
>>> map(plus, [1.2.3], [4.5.6[])5.7.9]
>>> map(plus, [1.2.3.4], [4.5.6[])5.7.9.4]
>>> map(plus, [1.2.3], [4.5.6.7[])5.7.9.7]Copy the code

In the example above, the form x or 0 is used to prevent None + int from being an exception.

Note that the number of iterables should be the same as the number of func arguments, otherwise an exception will occur because the number of arguments passed is not the same as the number of function arguments, which should be understandable.

>>> plus = lambda x,y : x + y
>>> map(plus, [1.2.3])
Traceback (most recent call last):
  File "<stdin>", line 1.in <module>
TypeError: <lambda>() takes exactly 2 arguments (1 given)Copy the code

There is also a special case of map() where func is None. At this point, map() still takes one element from each iterable, forms a tuple list, and returns that tuple list as a result.

>>> map(None[1.2.3.4[])1.2.3.4]
>>> map(None[1.2.3.4], [5.6.7.8[(])1.5), (2.6), (3.7), (4.8)]
>>> map(None[1.2.3.4], [5.6.7[(])1.5), (2.6), (3.7), (4.None)]
>>> map(None[1.2.3.4], [6.7.8.9], [11.12[(])1.6.11), (2.7.12), (3.8.None), (4.9.None)]Copy the code

reduce()

The reduce() function is called as follows:

reduce(func, iterable[, initializer])Copy the code

The reduce() function accumulates the elements of an iterable from left to right, resulting in a number. The third parameter, initializer, is the initial value and can be empty. If empty is None, we start with the second element of the iterable and take the first element as the result of the previous one.

The text description may not be clear, but the reduce() source code should make it clear.

def reduce(function, iterable, initializer=None):
    it = iter(iterable)
    if initializer is None:
        try:
            initializer = next(it)
        except StopIteration:
            raise TypeError('reduce() of empty sequence with no initial value')
    accum_value = initializer
    for x in it:
        accum_value = function(accum_value, x)
    return accum_valueCopy the code

Add in the following example, and you should have a grasp of reduce() functionality.

>>> plus = lambda x, y : x + y
>>> reduce(plus, [1.2.3.4.5])
15
>>> reduce(plus, [1.2.3.4.5].10)
25Copy the code

filter()

The filter() function is called as follows:

filter(func, iterable)Copy the code

Filter () takes only two arguments, the first being a function name and the second an iterable object, such as a list, tuple, and so on.

The filter() function is called in the same way as map(). Each element in the second argument (iterable) is passed to the first argument (func). The difference is that filter() determines the bool value of each execution and only filters out bool values true to form a new list and return it.

>>> mode2 = lambda x : x % 2
>>> filter(mode2, [1.2.3.4.5.6.7.8.9.10[])1.3.5.7.9]Copy the code

That’s the core of the basic unit of Python functional programming.

Next, we try to convert conditional control and loop control in imperative programming using the basic units we have learned.

Replace condition control statements

Before we replace conditional controls, let’s review Python’s short-circuiting of Boolean expression evaluation.

What is “short circuit” processing? To put it simply, there are two points:

  • inf(x) and g(y)When thef(x)forfalseIs not executedg(y), return directlyfalse
  • inf(x) or g(y)When thef(x)fortrueIs not executedg(y), return directlytrue

The conclusion is obvious and will not be explained too much.

Then, corresponding to the conditional control statement, it is not difficult to understand that the following conditional control statement and expression are equivalent.

# flow control statement
if <cond1>:   func1()
elif <cond2>: func2()
else:         func3()Copy the code
# Equivalent "short circuit" expression
(<cond1> and func1()) or (<cond2> and func2()) or (func3())Copy the code

With this equivalence substitution, we remove the if/elif/else keyword and convert the conditional control statement into an expression. So what does this expression have to do with functional programming?

If we look back at lambda, we will see that the lambda operator returns an expression.

Based on this, we can use lambda to create the following function.

>>> pr = lambda s:s
>>> print_num = lambda x: (x==1 and pr("one")) \...or (x==2 and pr("two")) \...or (pr("other"))
>>> print_num(1)
'one'
>>> print_num(2)
'two'
>>> print_num(3)
'other'Copy the code

As you can see from the result of the function call, the above function does exactly what the previous conditional control statement did.

At this point, we have implemented the transformation from imperative conditional control statements to functional statements. And this transformation method is universal, all conditional control statements can be converted into functional statements in this way.

Replace loop control statements

Next, let’s look at the transformation of loop control statements. In Python, loop control is implemented in two ways: for and while.

Replace the for loop

The replacement of the for loop is simple and can be easily implemented using the map() function. This is mainly because, like map(), the for statement operates on every element in the iterable, so the conversion is natural.

# statement-based for loop
for e in lst:  func(e)

# Equivalent map()-based loop
map(func, lst)Copy the code
>>> square = lambda x : x * x
>>> for x in [1.2.3.4.5]: square(x)
...
1
4
9
16
25
>>> map(square, [1.2.3.4.5[])1.4.9.16.25]Copy the code

Replace the while loop

While loop substitution is much more complicated.

Here is the while loop statement and its corresponding functional style code.

# statement-based while loop
while <condition>:
    <pre-suite>
    if <break_condition>:
        break
    else:
        <suite>

# Equivalent FP-style recursive while loop
def while_block(a):
    <pre-suite>
    if <break_condition>:
        return 1
    else:
        <suite>
    return 0

while_FP = lambda: <condition> and (while_block() or while_FP())
while_FP()Copy the code

The difficulty here is that the functional while_FP loop uses the concept of recursion. When

is true, we enter the loop body and execute while_block(); If

is true, return 1, and while_FP() ends; If

is false, 0 is returned, and while_FP() to the right of or continues, thus implementing recursive calls. If

is always false, while_FP() is continuously recursively called, which implements the same functionality as in the while statement.



To get a better understanding of the functional while loop, take a look at the following example. Echo echo echo echo echo echo echo echo echo echo echo echo echo echo echo echo echo Enter “quit” to exit the program.

➜  PythonFP python pyecho.py
IMP -- 1
1
IMP -- 2
2
IMP -- abc
abc
IMP -- 1 + 1
1 + 1
IMP -- quit
quit
➜  PythonFPCopy the code

Here are the “echo” functions implemented using procedural and functional statements respectively.

# imperative version of "echo()"
def echo_IMP(a):
    while 1:
        x = raw_input("IMP -- ")
        print x
        if x == 'quit':
            break

echo_IMP()Copy the code
def monadic_print(x):
    print x
    return x

# FP version of "echo()"
echo_FP = lambda: monadic_print(raw_input("FP -- ")) = ='quit' or echo_FP()
echo_FP()Copy the code

More examples

At this point, we finally have a little understanding of functional programming, before reaching the goal should be no problem, it seems that functional programming is not so difficult to imagine.

However, this is just scratching the surface of functional programming, believe me? Take a look at the following example.

This example is also found on the Internet, the implementation of two lists of Cartesian product filter function, find a set of Cartesian product tuples of the product of two elements greater than 25 all tuples.

bigmuls = lambda xs,ys: filter(lambda (x,y):x*y > 25, combine(xs,ys))
combine = lambda xs,ys: map(None, xs*len(ys), dupelms(ys,len(xs)))
dupelms = lambda lst,n: reduce(lambda s,t:s+t, map(lambda l,n=n: [l]*n, lst))

print bigmuls([1.2.3.4], [10.15.3.22[(])3.10), (4.10), (2.15), (3.15), (4.15), (2.22), (3.22), (4.22)]Copy the code

Although lambda/ Map/Reduce /filter in this example are basic units that we are already familiar with, it is still difficult to understand when combined.

conclusion

Seeing this, some students joked that my title is very appropriate, “functional programming in Python — from getting started to” giving up “, because I will not try to use functional programming in my work in the future, ^_^.

Still, I find functional programming interesting, and it’s worth learning more about more advanced features later. Even if the code doesn’t have to be written in pure functional style, using lambda/ Map/Reduce /filter locally in some cases can greatly simplify the code and is a good option.

In addition, through this sharing, I once again realized that professor is the best way to learn. Only when you can explain a concept clearly, you can master it.

Refer to the link

www.ibm.com/developerwo…


About the author

His pen name is Jiuhao and his English name is Leo Lee.

Focus on software testing field and test development techniques, enjoy quiet debug in the corner, also like to share text on the blog.

Personal blog: debugtalk.com

Personal wechat official account: DebugTalk