Although Python is in fact not a purely functional programming language, it is itself a multilingual language and gives you enough freedom to use functional programming. Functional style has a variety of theoretical and practical benefits (you can find this list in Python's documentation):
- form can be verified
- Modular Nature
- Combination of
- Easy to debug and test
Although this list has been described clearly enough, I still like Michael O.church's description of the benefits of functional programming in his article, "Very few functional programs decay (functional programs rarely rot)". I talked about the use of functional methods in Python in "functional programming with Python" during the Pycon UA No. 2012 session. I also mentioned that when you try to write readable and maintainable functional code in Python, you'll quickly find a lot of problems.
The Fn.py class Library was born to deal with these problems. Although it is not possible to solve all the problems, it is a "battery" for developers who want to get the most value from functional programming, even if it is in the command-style dominant program. So, what does it all have?
Scala-style lambda definition
The syntax for creating lambda functions in Python is very lengthy, to compare:
Python
Map (Lambda x:x*2, [1,2,3])
Scala
Copy Code code as follows:
Clojure
Copy Code code as follows:
Haskell
Copy Code code as follows:
Inspired by Scala, fn.py provides a special _ object to simplify the lambda syntax.
From FN Import _
Assert (_ + _) (5) =
assert List (Map (_ * 2, Range (5)) = = [0,2,4,6,8]
assert list (filte R (_ < 10, [9,10,11]) = = [9]
In addition, there are many scenarios where you can use _: all arithmetic operations, attribute parsing, method invocation, and slicing algorithms. If you're not sure what your function will do, you can print the results:
From FN Import _
Print (_ + 2) # (x1) => (x1 + 2) "
Print (_ + _ * _) #" (X1, x2, x3) => (x1 + (x2 * x3)) "
Flow (stream) and the declaration of an infinite sequence
A Scala-style lazy evaluation (lazy-evaluated) stream. The basic idea is to take the value on demand for each new element and share the calculated element values in all the iterations that are created. The Stream object supports the << operator, which represents the push of new elements as needed.
The processing of an infinite sequence by an inert value stream is a powerful abstraction. Let's take a look at how to compute a Fibonacci sequence in a functional programming language.
Haskell
Copy Code code as follows:
fibs = 0:1: Zipwith (+) fibs (tail fibs)
Clojure
Copy Code code as follows:
(Def fib (lazy-cat [0 1] (map + fib (rest fib)))
Scala
Copy Code code as follows:
def fibs:stream[int] =
0 #:: 1 #:: Fibs.zip (Fibs.tail). Map{case (a,b) => A + b}
Now you can use the same way in Python:
From the FN import Stream
from the Fn.iters import take, drop, map from
operator import add
f = Stream ()
fib = f & lt;< [0, 1] << map (add, F, drop (1, f))
assert list (take, fib) = = [0,1,1,2,3,5,8,13,21,34]
assert fib[ = = = 6765
assert list (fib[30:35]) = = [832040,1346269,2178309,3524578,5702887]
Trampoline (trampolines) modifier
Fn.recur.tco is a temporary solution that does not require large stack space allocations to handle TCO. Let's start with a recursive factorial calculation example:
def fact (n):
If n = 0:return 1 return
N * Fact (N-1)
This can work, but the implementation is very bad. Why, then? It consumes a lot of storage space because it recursively saves the previous computed value to work out the final result. If you perform this function on a large n value (exceeding the value of Sys.getrecursionlimit ()), CPython will fail in this way:
>>> Import sys
>>> fact (Sys.getrecursionlimit () * 2)
... many many lines of stacktrace ...
Runtimeerror:maximum recursion depth exceeded
This is also a good thing, at least it avoids a serious error in your code.
How do we optimize this program? The answer is simple, just change the function to use tail recursion:
def fact (N, acc=1):
if n = = 0:return ACC return
fact (n-1, Acc*n)
Why is this way better? Because you do not need to keep the previous value to calculate the final result. You can view more of the content of the tail recursive call optimization on Wikipedia. But...... The Python interpreter executes this function in the same way as the previous function, and the result is that you don't get any optimizations.
Fn.recur.tco provides you with a mechanism that allows you to use a "trampoline" approach to achieve a certain tail-recursive optimization. The same approach is also used in languages such as Clojure, where the main idea is to convert a function call sequence into a while loop.
From FN import recur
@recur. TCO
def fact (N, acc=1):
if n = = 0:return False, acc return
True, (N-1, AC C*n)
@recur. TCO is a modifier that turns your function execution into a while loop and verifies its output:
- (False, result) on behalf of the finished run
- (True, args, Kwargs) means we want to continue calling the function and passing different arguments
- (func, args, Kwargs) to toggle the function to be executed in the while loop
Error handling in functional style
If you have a request class, you can get the corresponding value according to the parameter name passed in. To have the return value formatted as all uppercase, Non-null, and to remove the string of trailing spaces, you need to write this:
Class Request (Dict):
def parameter (self, name): Return
self.get (name, None)
r = Request (testing= "Fixed", Empty= "")
param = r.parameter ("testing")
if Param is None:
fixed = ""
else:
param = Param.strip () C10/>if len (param) = = 0:
fixed = ""
else:
fixed = Param.upper ()
Well, it looks a little odd. Use Fn.monad.Option to modify your code, which represents an optional value, and each option instance can represent a full or empty (this is also inspired by the option in Scala). It provides a convenient way for you to write long sequences of operations, and removes many If/else statement blocks.
From operator import Methodcaller from
fn.monad import optionable
class Request (dict):
@optionable
def parameter (self, name): Return
self.get (name, None)
r = Request (testing= "fixed", empty= "")
Fixed = R.parameter ("testing")
. Map (Methodcaller ("Strip"))
. Filter (len)
. Map (Methodcaller ("Upper"
) . Get_or ("")
Fn.monad.Option.or_call is a convenient method that allows you to make multiple calls to try to complete the calculation. For example, you have a request class that has several optional attributes such as Type,mimetype and URLs, and you need to use at least one attribute value to parse its "request type":
From Fn.monad import Option
request = Dict (url= "Face.png", mimetype= "PNG")
TP = Option \
. From_value ( Request.get ("type", None) \ # check ' type ' key
Or_call (from_mimetype, request) \ # or. Check "MimeType" key< C18/>.or_call (from_extension, request) \ # or ... get "url" and check extension
. get_or ("application/undefined")
The rest of the matter?
I only describe a small part of the class library, and you can also find and use the following features:
- 22 additional Itertools code snippets to extend the functionality of the built-in module
- Unifying the use of Python 2 and Python 3 iterators (iterator) (such as Range,map and Filtter, and so on) is useful when using a cross-version class library
- It provides a simple syntax for functional combination and partial function application.
- Additional operators are provided to use higher-order functions (apply,flip, etc.)
Work in progress
Since the release of this class library on GitHub, I have received many revisers views, comments and suggestions from the community, as well as patches and fixes. I'm also continuing to enhance existing functionality and provide new features. The recent road map includes the following elements:
- Add more operators for use with iterable objects (such as FOLDL,FOLDR)
- More monad, such as Fn.monad.Either, to handle error logging
- Provides c-accelerator for most module
- To simplify Lambda Arg1:lambda arg2: ... The generator of the curry function provided in the form
- More documentation, more tests, more sample code