Functional Programming with Python

What is functional programming?
Functional programming languages are specially designed to handle symbolic computation and list processing applications. Functional programming is based on mathematical functions. Some of the popular functional programming languages include: Lisp, Python, Erlang, Haskell, Clojure, etc.
Recently, the use of functional programming has been on the rise. Thus, many traditionally imperative languages like Java and Python have started to support functional programming techniques. In this article I will be introducing some of these techniques to you with assuming you have basic understanding of functional programming. if you are not familiar with basic function programming, check this article on function programming.
First-Class Functions
Python functions are first class objects. Thus, we can assign them to variables, pass them as arguments to other functions, store them in other data structures such as dictionaries and use them as return values for other functions.
In the example below, we are assigning function to a variable. This assignment doesn’t call the function. It takes the function object referenced by shout and creates a second name pointing to it, Tell.
def Show(Text): return Text.upper()print(Show("Hello World"))Tell = Showprint(Tell("Hello World"))OUTPUT
HELLO WORLD
HELLO WORLD
Since functions are objects, we can assign the function Show
to any variable and then call that variable to refer to the function. For example, we can assign it to the variable Tell.
Functions inside data structures
Same as other objects, Functions can also be stored inside Data Structures. For example, we can create a dictionary with mapping of int
to func.
This is useful for when the int
is a shorthand for a procedure to be performed.
#INITIALIZING DICTIONARY
dict = {
0: func1,
1: func2
}
x = input() #GET INTEGER VALUE FROM USER
dict[x]() #CALL THE FUNCTION RETURNED BY DICTIONARY ACCESS
Functions as arguments and return values
A function can take multiple arguments, these arguments can be objects, variables(of same or different data types) and functions.
Functions can also serve as arguments and return values of other functions. The functions that either accept or return functions are known as higher-order functions and are an important part of functional programming.
Higher-order functions are extremely powerful. As explained deeply on Eloquent JavaScript:
Consider an example.
Let’s say we want to iterate over a list of items and print them sequentially. We can easily build an iterate
function:
def iterate(list_of_items):
for item in list_of_items:
print(item)
This might seem cool, but this is just one level of abstraction. What if we want to do something different (instead of print) when we iterate over the list?
That’s where higher-order functions come in. We can create a function iterate_custom
that takes in both a list to iterate and a function to be applied on each item:
def iterate_custom(list_of_items, custom_func):
for item in list_of_items:
custom_func(item)
Although this might seem trivial, it is immensely powerful.
We’ve moved up a layer of abstraction and made our code more reusable. Now, we can call the function not only when we want to print a list, but also do anything with the list that involves sequential iteration.
Functions can also be returned to make things simpler. Just like how we stored functions in a dict
, we can also use a function as control flow to decide the appropriate function. For example:
def add(x, y):
return x + ydef sub(x, y):
return x - ydef mult(x, y):
return x * ydef calculator(opcode):
if opcode == 1:
return add
elif opcode == 2:
return sub
else:
return mult my_calc = calculator(2) #MY CALC IS A SUBSTRACOTR
my_calc(5, 4) #RETURNS 5 - 4 = 1
my_calc = calculator(9) #MY CALC IS A MULTIPLLIER
my_calc(5, 4) #returns 5 x 4 = 20.
Nested functions
Functions can be defined inside of other functions as well and these are aptly called inner functions. These are especially useful to make helper functions — small, reusable functions that support the main function as a sub-module.
Helper functions are handy when a problem requires a specific function definition (argument type or order) but it is easier to solve the problem without following the convention. A good example is from this lecture slide.
Let’s say you want to define a fibonacci function, fib(n)
, which takes a single argument, n
, and we have to return the nth
fibonacci number.
A possible way of defining such a function is with the use of a helper function that tracks the previous two terms of the fibonacci sequence (since a fibonacci number is simply the sum of the previous two fibonacci numbers).
def fib(n):
def fib_helper(fk1, fk, k):
if n == k:
return fk
else:
return fib_helper(fk, fk1+fk, k+1) if n <= 1:
return n
else:
return fib_helper(0, 1, 1)
Moving the calculation from function body to function argument is incredibly powerful because it reduces redundant calculation that may occur in a recursive approach.
Lambda Expressions (Single Expression Functions)
How can we define a function without giving it any name? We can apply lambda function to define short and on -liner functions for example shown below.
add = lambda x, y: x + y
add(1, 2) #RETURNS 2
The behavior of this add
is exactly the same as the one defined using the traditional def
keyword earlier.
Note that lambda functions must be one-liners and must not contain a return statement written by the programmer.
In-fact, they always have an implicit return statement (in the above example, it would say return x + y
but we omit explicit return statements from lambda functions).
The lambda function is much more powerful and concise because we can also construct anonymous functions — functions without a name:
(lambda x, y: x * y)(9, 10) #RETURNS 90
This is a handy method for whenever we need a function only once and need not use it later. For example, when filling up a dictionary:
import collections
pre_fill = collections.defaultdict(lambda: (0, 0))
#all dictionary keys and values are set to 0
We can now look at Map, Filter, and Reduce to appreciate lambda even more.
Map, Filter, and Reduce
1. Map
map
is a function that takes a set of inputs and transforms them into another set based on a function specified. This is same as the iterate_custom
function . For example:
def substract_1(x): return x - 1
scores = [10, 9, 8, 7, 6, 5]
new_scores = list(map(substract_1, scores))
#NEW SCORES ARE NOW [9, 8, 7, 6, 5, 4]
print(new_scores)
In Python 3, the map
function returns a map
object that can be typecast to a list
for us to make use of it. Now, instead of defining the multiply_by_four
function explicitly, we can define a lambda
expression:
new_scores = list(map(lambda x: X-4, scores))
2. Filter
filter
, as the name suggests, is a function that helps “filter” out unwanted items. For example, we may want to filter out all the odd numbers from scores
. We can do so using filter
:
even_scores = list(filter(lambda x: (x % 2 == 0), scores))
#even_scores = [6, 8]
Since the function supplied to filter
decides the acceptance of an item on a case-by-case basis, the function must return a bool
value (as seen in the lambda function above) and must be unary (take one input parameter).
3. Reduce
reduce
is a function to “summarize” or to get a “big-picture” of a set of data. For example, if we want to calculate the sum of all scores, we can use reduce
:
sum_scores = reduce((lambda x, y: x + y), scores)
#sum_scores = 32
This is much simpler than writing a loop. Note that the function supplied to reduce
requires two params: one represents the current item being inspected and one is the cumulative result of the operation being applied.
Note that the above just gets you started, although thoroughly, with functional programming in Python.
Moreover, You can practice and explore these concepts on this hacker-rank module.
Thank you for Reading and consider clap if you find it useful.