This article has participated in the activity of “New person creation Ceremony”, and started the road of digging gold creation together

Some of the rough-and-tumble elements of learning Functional programming in Python. The main examples come from the last few references, and the author builds examples based on the reference materials, adding considerable personal observations.

Use recursion instead of loop

It’s almost the classic idea of functional programming.

# remove multiples of 2,3 from 1-100
lst = []
for i in range(1.101) :if i % 2! =0 and i % 3! =0:
        lst.append(i)

lst_fp = [i for i in range(1.101) if i % 2! =0 and i % 3! =0]
Copy the code

It’s not interesting to give such a simple example. In short, the key is to design a good recursion instead of loop completion when loops are complex.

Design recursively to solve the problem

The following gives a square matrix based fast power solution to the NTH term of the Fibonacci sequence procedures, is Haskell language, but should understand.

- square power quickly. The hs - a tentative only second-order square my_product: : Num a = > [[a]] - > [[a]] - > [[a]] my_product a b = [[sum [a !! i !! k * b !! k !! j | k <- [0.1.]] | j <- [0.1.]] | i <- [0.1.]]

{-
self_product :: Num a => [[a]] -> [[a]]
self_product a = [[sum [a !! i !! k * a !! k !! j | k <- [0.1.]] | j <- [0.1.]] | i <- [0.1.[] -} -- test speed:1e6 0.04.1e7 0.13.1e8 2.06.1e9 26.24Self_product :: Num a => [[a]] -> [[a]] self_product = \a -> my_product a a But it doesn't. In any case, function parameter values are determined only once, and before reference. Although Haskell's lazy evaluation-argument calculation evaluates the parameter value when it is referenced (rather than at the beginning), it only evaluates once, so it is not slow. -- Speed measurement:1e6 0.02.1e7 0.16.1e8 1.99.1e9 26.24You may be running out of memory. {-fast_exp :: (Num a, Integralint) => [[a]] -> int -> [[a]]
fast_exp _ 0 = [[1.0], [0.1]]
fast_exp a num = if num `mod` 2= =0 then self_product $ fast_exp a $ halfnum
                                     else my_product a $ self_product $ fast_exp a $ halfnum
                                     where halfnum = floor $ fromIntegral num / 2-} -- Halfnum is mainly a compromise of the type system. Read it carefully and learn how to write it - you learned how to use $by writing this function: it is used instead of parentheses to change the order of evaluation, but $cannot be abused like (). It's better to figure out the order of evaluation and then add $in place, rather than trying to use $as a separator. fast_exp :: (Num a, Integralint) => [[a]] -> int -> [[a]]
fast_exp a num | num == 0 = [[1.0], [0.1]]
               | num `mod` 2= =0 = self_product $ fast_exp a $ halfnum
               | otherwise = my_product a $ self_product $ fast_exp a $ halfnum
               where halfnum = floor $ fromIntegral num / 2Let me optimize the way I write it. The first row doesn't really need to be optimized this way, except for the second and third rowsifMore clearly (not). fibo :: Integralint= >int -> int
fibo num = fast_exp fibo_base num !! 0!!!!!0 where fibo_base = [[1.1], [1.0[] -- Application of square matrix fast power: Solving Fibonacci my_zero :: Integralint= >int -> int
my_zero a = if a == 0 then 0 else 1-- Used to measure speedCopy the code

Replace the for loop with list derivation (list parsing)

You know how to write.

# remove multiples of 2,3 from 1-100
lst = []
for i in range(1.101) :if i % 2! =0 and i % 3! =0:
        lst.append(i)

# List Comprehension
lst_fp = [i for i in range(1.101) if i % 2! =0 and i % 3! =0]
Copy the code
[(i, j) for i in range(2) for j in range(2)] # [(0, 0), (0, 1), (1, 0), (1, 1)]
[(i, j) for i, j in zip(range(2), range(2)))# [(0, 0), (1, 1)]
Copy the code

Point of view: Solving problems through unified list processing, such as recursion of sublists, is a very general idea

Based on the list derivation, recursion is designed to solve the problem

Combined with list derivation, design recursion to solve the problem. Typical example: Quicksort

def quick_sort(lst) :
    if lst == []: return []
    head, tail = lst[0], lst[1:]
    return quick_sort([elem for elem in tail if elem < head]) \
         + [head]\
         + quick_sort([elem for elem in tail if elem >= head])
Copy the code

Replace process control with and or non-logic

if-else

# an implementation of a simple judgment logic
def foo(num) :
    if num == 1: return "One"
    elif num == 2: return "Two"
    else: return "Other"
    
# Functional
def foo_fp(num) :
    self_return = lambda s: s
    return (num == 1 and "One") \
    or (num == 2 and "Two") \
    or "Other"
    
# More Functional
def foo_fp_more(num) :
    self_return = lambda s: s
    return (num == 1 and self_return("One")),or (num == 2 and self_return("Two")),or self_return("Other")
Copy the code

If the conditional statement is incomplete, it can be either or None.

while

The paradigm of FP while is as follows:

# statement-based while loop
while <cond>:
    <pre-suite>
    if <break_condition>: break
    else: <suite>
 
# FP-style recursive while loop
def while_block() :
    <pre-suite>
    if <break_condition>: return 1
    else: <suite>
    return 0
 
while_FP = lambda: (<cond> and while_block()) or while_FP()
while_FP()
Copy the code

The essence of while FP is to do things through the side effects of a function, and to dictate whether the behavior continues or terminates through the return value of the function. This has the advantage of limiting the effects of side effects to the function.

Here’s an example based on the echo() function:

welcome_str = "input something (input \"q\" to exit):"
# imperative version of "echo()"
def echo_input() :
    while 1:
        x = input(welcome_str)
        if x == 'q': break
        else: print(x)
echo_input()
 
# utility function for "identity with side-effect"
def monadic_print(x) :
    print(x)
    return x
 
# FP version of "echo()"
echo_FP = lambda: monadic_print(input(welcome_str)) == 'q' or echo_FP()
echo_FP()
Copy the code

The disadvantage of while FP is that it does not pass values very well through the return value of the function. Let’s take an example of this shortcoming:

# remove multiples of 2,3 from 1 to 100, and keep only the first 10 digits
lst = []
i = 1
while len(lst) < 10:
    if i % 2! =0 and i % 3! =0:
        lst.append(i)
    if i >= 100: break
    else: i += 1

# FP
lst_fp = []
i = 1
def while_block() :
    if i % 2! =0 and i % 3! =0:
        lst.append(i)
    if i >= 100: return 1
    else: i += 1
    return 0

while_fp = lambda: (len(lst_fp) < 10 and while_block()) or while_fp()
while_fp()
Copy the code

The ABOVE FP code shows the idea, but it doesn’t work. Lst_fp and I can only run if they are defined as globally modifiable.

Since both while_block() and while_FP return a value of 0/1, changes to variable values can only be made:

  • Executes by declaring a variable as a global variable. (It is better to follow the while FP framework above, but not recommended.)
  • The value is still passed through the return value of the function, in which case you need to make the code a little more complicated. (Recommended, though complex, is a pure FP implementation.)

Higher-order functions

Introduction to higher order functions

The idea of higher-order functions is that functions can be arguments and functions can be return values. The idea of higher-order functions is most apparent in Haskell: because everything can be represented by a function, anything that is strange can be represented by a function with similar structure and function, and then returned. This part is very basic, wait for the mood to fill in.

Three paradigms of higher-order functions: Map Reduce Filter

The Map Reduce Filter functions represent the three programming patterns (or paradigms) in FP. Using these three functions flexibly can accomplish a large part of the tasks of functional programming. Here is a simple implementation of these three functions (based on FP) :

def map(f, lst) :
    return [f(x) for x in lst]

The map is actually more complex and should allow for multiple iterable parameters, but it doesn't matter
Copy the code
def reduce(f, lst) :
    assert len(lst) > 1
    if len(lst) == 2:
        return f(lst[0], lst[1])
    head, tail = lst[0], lst[1:]
    return f(head, reduce(f, tail))

# is also a very basic implementation of Reduce
Copy the code
def filter(f, lst) :
    return [x for x in lst if f(x)]
Copy the code

Ideas:

  • The idea of functions as parameters, the idea of higher-order functions
  • Solve problems through unified list processing

Practical application of higher order functions

The most useful thing about higher-order functions is that they don’t have to return a simple value or structure, but can return something messy. Here’s an example: Python: decorator used to display the execution time of a function

monad

Monad: Actually nothing, just a side effect in the process of executing the function body. Benefits of Monad: The effects of side effects are confined to functions, making it easy to locate bugs and debug.

Monad style print

Use monadic_print() as an example:

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

> > =

What thing in Python most resembles >>= in Haskell?

# Normal
lst = xx.yy()
lst.sort()
# > > +
lst = xx.yy().sort()
Copy the code

The code is segmented and wrapped in functions

Block code and encapsulate it with functions: this is a code style. The following is an example:

# imperative version
## step1
import numpy as np
x = np.array(range(1.5))
y = x + 2
z1 = y ** 2
z2 = np.sin(y)
## step2
import matplotlib.pyplot as plt
plt.plot(x, z1)
plt.plot(x, z2)
plt.show()

# FP
## step1
def fun1() :
    import numpy as np
    x = np.array(range(1.5))
    y = x + 2
    z1 = y ** 2
    z2 = np.sin(y)
    return x, z1, z2 # only these three are used later
x, z1, z2 = fun1()
## step2
def fun2() :
    import matplotlib.pyplot as plt
    plt.plot(x, z1)
    plt.plot(x, z2)
    plt.show()
fun2()
Copy the code

Benefits:

  • Easy to segment debugging (comment out the execution of the sentence, you can not run this paragraph)
  • You can specify the scope of variables and imported modules/packages
  • Easy to locate errors: to locate obvious errors within a function
    • Prevent error transmission: Write a Sanity check on each piece of code to make sure the phase is running correctly

The underlying idea: get things done by evaluating expressions (focusing on what to evaluate) rather than constantly assigning (focusing on how to evaluate).

Further reading

  • This article was originally based on the 1 extension, with a lot of ideas and examples coming from this blog post, and some errata in it.
  • This paper has many ideas and examples from 2
  • If you want to know why the real Monad is used in Haskell for &, use 3 as a starting point

  1. Functional programming in Python↩
  2. Haskell Functional programming introduction zhang Song ↩
  3. Understand Monad through Python’s list derivation↩