We'd better start with the hardest question: "What exactly is functional programming (FP)?" "One answer might say that FP is what you do when you're programming with Lisp, Scheme, Haskell, ML, OCAML, clean, Mercury, Erlang (or some other) language." This is a sound answer, but it is not a precise explanation of the problem. Unfortunately, even functional programmers themselves have a hard time understanding what FP really is. The story of "elephant" is used to describe the situation as appropriate. You can also safely compare FP with command programming (using, for example, the operations performed in C, Pascal, C + +, Java, Perl, Awk, TCL, and most other languages, at least to a large extent).
Personally, I'll sketch functional programming as having at least one of the following characteristics. A functional language makes these things simple and makes other things difficult or impossible:
- The function is the first Class (object). That is, every operation that can be done with the data can be done using the function itself (for example, passing a function to another function).
- Use recursion as the primary control structure. In some languages, no other "looping" constructs exist.
- Focus on listing list processing (for example, name Lisp). Lists are often used in conjunction with the recursion of the child list to replace the loop.
- The "pure" function language can avoid side effects. This is not included in the most common pattern in the command language, specifying the first, and then assigning another value to the same variable to keep track of the program state.
- FP discourages or does not allow statements at all, instead using expression evaluation (in other words, functions plus arguments). In a very pure case, a program is an expression (plus a supported definition).
- FP is concerned with what is calculated rather than how it is calculated.
- Many FP take advantage of the "higher" function (in other words, functions operate on some functions, and these functions operate on other functions).
The advocates of functional programming believe that all of these features lead to faster development of shorter and less error code. Moreover, senior theorists in the fields of computer science, logic and mathematics have found that the formal performance of functional languages and programs is much easier than command languages and programs.
inherent Python function capabilities
Python has most of the FP features listed above since Python 1.0. But for most Python features, they are rendered in a very mixed language. In large part because of the OOP features of Python, you can use the parts you want to use and ignore the rest (until you need it later). Using Python 2.0, a list of connotations adds some great "syntactic whitewash". Although the list content does not add any new capabilities, they make many old abilities look a lot better.
The basic elements of FP in Python are function map (), reduce () and filter (), and operator Lambda. In Python 1.x, the Apply () function is also handy for applying the return value of a function directly to another function. Python 2.0 provides an improved syntax for this purpose. It may be surprising, but few of these functions (and basic operators) are nearly enough to write any Python program; In particular, all flow control statements (if, elif, else, assert, try, except, finally, for, break , continue, while, Def) can be handled in a functional style using only FP functions and operators. While virtually eliminating all flow control commands in a program may be useful only for adding "chaotic Python" competition (as with code that looks very much like Lisp), it is worthwhile to understand how FP uses functions and recursion to represent flow control.
eliminate flow control statements
The first thing to consider when we perform the elimination of the connection is the fact that Python "shorted out" the Boolean expression. This provides the If/elif/else block of the expression version (assuming that each block calls a function, which is always possible). Here are the specific methods:
Listing 1. "Short-circuit" condition call in Python
# Normal statement-based Flow control
if
<cond1>: func1 ()
elif
<cond2>: Func2 ()
Else
: func3 ()
# equivalent "Short circuit" expression
(<cond1>
and
func1 ())
or
(<cond2>
and
Func2 ())
or
(func3 ())
# Example ' short circuit ' expression
>>> x = 3
>>>
defpr
(s):
return
s
>>> (X==1
and
PR (
' one '))
or
(x==2
and
PR (
' two '))
or (
PR
(' other '))
' other '
>>> x = 2
>>> (x==1
and
PR (
' one '))
or
(x==2
and
PR (
' two '))
or
(pr (
' other '))
' two '
The conditional invocation of an expression version seems to be just a place trick, but it's even more interesting if we notice that the lambda operator must return an expression. Because--as shown earlier--the expression can contain a conditional block by short-circuiting, so the lambda expression is very common in the expression criteria return value. Build on our example:
Listing 2. Short Lambda in Python
>>> PR =
Lambda
s:s
>>> namenum =
lambda
x: (x==1
and
PR (
"one") \
....
or
(x==2
and
PR (
"two")) \ ...
.
or
(pr (
"other"))
>>> namenum (1)
' one '
>>> namenum (2)
' two '
>>> Namenum (3)
' other '
function as the first class object
The above example already shows the first class position of a function in Python, but in a very subtle way. When you create a function object using a lambda operation, we have something completely routine. Because of this, we can bind the object to the name "PR" and "Namenum", using the same method and binding the number 23 or string "spam" to these names exactly the same way. But as we can use the number 23 without having to bind it to any name (in other words, like a function argument), we can use a function object created with a lambda without binding it to any name. A function is just another value that we perform some action on in Python.
The primary action we perform on the first class of objects is to pass them to the FP built-in function map (), reduce (), and filter (). Each of these functions accepts the function object as its first argument.
Map () performs the function passed for each corresponding item in the specified list, and returns a list of results.
Reduce () performs the function passed on each successive item, returning the internal accumulation of the final result, for example, reduce (lambda n,m:n*m, Range (1,10)) means "factorial of 10" (in other words, multiplying each item by the first multiplication).
Filter () uses the passed function to "evaluate" each item in the list, and then returns a list of the items that are screened and passed the function test.
We also often pass function objects to our own custom functions, but they are usually equivalent to the combination of the above built-in functions.
By combining these three FP built-in functions, you can perform an astonishing series of "flow" operations (without using statements and using only expressions).
function loops in Python
The replacement loop is as simple as replacing a conditional block. For can be directly converted to map (). For our condition to execute, we need to simplify the statement block into a single function call (we are gradually approaching the usual practice):
Listing 3. Functions in Python ' for ' Loop
For
e
in
lst:func (e)
# statement-based Loop
map (func,lst)
# map ()-based Loop
In addition, there is a similar technique for the function method of continuous program flow. That is, command programming usually involves close to "doing this, then doing that, and then doing something else." "Such a statement. Map () Let's just do this:
Listing 4. function continuous operation in Python
# let ' s create a execution utility function
do_it =
Lambda
f:f ()
# Let F1, F2, F3 (etc) is functions tha T perform actions
map (Do_it, [f1,f2,f3])
# Map ()-based action sequence
Typically, our entire main program can be a map () expression and a series of functions that need to be performed to complete a program. Another handy feature of the first class of functions is that they can be placed in a list.
While transformations are slightly more complex, they can still be done directly:
Listing 5. The function ' while ' Loop in Python
# statement-based while loops
while
<cond>:
<pre-suite>
if
<break_condition : Break
Else
:
<suite>
# Fp-style recursive while Loopp
Defwhile_block
():
<pre-suite>
if
<break_condition>:
return
1
else
:
<suite>
return
0
while_fp =
Lambda
: (<cond>
and
While_ Block ())
or
while_fp ()
while_fp ()
The while transformation still requires the While_block () function, which itself contains the statement, not just an expression. But we need to do a further elimination of the function (for example, short-circuit the if/else in the template). In addition, because the loop body (by design) cannot change any of the variable values, <cond> is difficult to use in a generic test, such as while myvar==7 (then the entire content will be modified in While_block ()). One way to add more useful conditions is to have While_block () return a more interesting value and then compare the return value to the termination condition. It is necessary to look at the specific examples of these elimination statements:
Listing 6. The function ' echo ' Loop in Python
# imperative version of "Echo ()"
defecho_imp
():
while
1:
x = raw_input (
"IMP--")
if
x = =
' quit ': Break
Else
print
x
echo_imp ()
# utility function for ' identity With Side-effect "
defmonadic_print
(x):
print
x
return
x
# FP version of" Echo () "
echo_fp =
Lambda
: Monadic_print (raw_input (
" FP--")) = =
' quit '
or
echo_fp ()
ECHO_FP ()
What we have done is to try to represent a small program involving I/O, loops, and conditional statements as a pure expression with recursion (actually, if necessary, as a function object that can be passed to any other place). We do still use the utility function Monadic_print (), but this function is completely generic and can be reused in every function program expression we create later (it's a one-time cost). Note that any expression that contains an Monadic_print (x) value evaluates to the same result as if it contains only X. FP (especially Haskell) has a "single body" for functions that "have side effects in the process without performing any action."
Eliminate side effects
After the work of removing the perfect, meaningful statement without replacing the obscure, nested expression, a natural question is: "Why?!" "All my descriptions of FP are done using Python. But the most important feature-and perhaps the most useful feature in a particular case-is that it eliminates side effects (or at least some diversion to some particular area, such as a single one). The vast majority of bugs--and the problems that motivate programmers to turn to debugging--occur because variables contain unexpected values during program execution. A function program simply does not assign a value to a variable, thus avoiding this particular problem.
Let's look at a fairly common command code. Its purpose is to print a list of several pairs of numbers with a Chuyang product greater than 25. The numbers that make up the pairs are themselves selected from the other two lists. This is similar to what programmers actually do in their program segments. The following commands are used to achieve this goal:
Listing 7. Command Python code for "Printing Mahayana product"
# Nested Loop procedural style for finding the big products
xs = (1,2,3,4)
ys = (10,15,3,22)
bigmuls = []
# ... . More Stuff
... For
x
in
xs:
with
y
in
ys:
# ... more stuff
... If
x*y >:
bigmuls.append ((x,y))
# ... more stuff ...
# ... more stuff
... Print
Bigmuls
The project is so small that nothing can go wrong. But our purpose may be embedded in code that implements many other purposes at the same time. Those parts of the annotation with "more stuff" are where side effects can cause errors to occur. In any of these places, variable xs, ys, Bigmuls, X, y are likely to get unexpected values in the assumed abridged code. Also, after this piece of code is executed, all variables may have some values that the code may or may not need later. It is clear that this type of error can be prevented by using the encapsulation of a function/instance form and considerations about the scope. Also, you can always del them after the variables have been executed. In practice, however, these types of errors are common.
The objective's functional approach completely eliminates these side-effects errors. The following is a possible piece of code:
Listing 8. Function Python code for "Printing Mahayana product"
Bigmuls =
Lambda
xs,ys:filter (
Lambda
(x,y): X*y >, 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))
In the example, we bind the anonymous (lambda) function object to the name, but this is not necessarily necessary. We can only nest definitions. This is done for readability purposes, but also because combine () is a very good utility function (which produces a list of all element pairs from both input lists) that is readily available. The subsequent dupelms () is primarily just a way to help combine () play a role. Even though the example of this function is longer than the command example, the new code in Bigmuls () may itself be less than the number of code in the command version, once the utility functions are considered reusable.
The real advantage of this function example is that there is absolutely no variable to change any of these values. There are no possible unintended side effects in later code (and not in older code). Obviously, it has no side effects and does not guarantee that the code is correct, but even so, this is an advantage. Note, however, that Python (unlike many function languages) does not prevent the bigmuls, combine, and dupelms of names from being rebind. If combine () begins to have other meaning in later parts of the program, all efforts will be undone. You can gradually create a Singleton class to contain this type of immutable bindings (such as s.bigmuls, for example), but this column does not cover this section.
A particularly noteworthy problem is that our specific purpose is to customize the new features in Python 2. The best (and functional) technique is neither the command example provided above nor the function instance, but the following:
Listing 9. List of "Bigmuls" Python code
Print
[(x,y)
for
x
in
(1,2,3,4)
for
y
in
(10,15,3,22)
if
x*y > 25]
Concluding remarks
I have described the method used to replace each Python flow control construct with a function equivalent (eliminating side effects in the process). An effective conversion of a particular program will bring some additional considerations, but we already know that the built-in functions are routine and complete. In a later column, we'll look at some of the more advanced functional programming techniques, and hopefully explore more of the pros and cons of functional style.