Functional Python

An Introduction in Code

#Basics

Intro to Functional Python

Principles, belonging to Functional Programming:
- No side effects (e.g. changing object states). Some claim, only mutating global variables are side effects. Yet, mutating any data is to be avoided in functional programming.
- Working with Higher Order Functions and Pure Functions.
Pure Functions are functions, that don't cause side effects. Both Pure Functions and Higher Order Functions are a result of the idea to make everything revolve around expressions, instead of statements.

Pure Functions

# for x, y will be returned (f(x)=x^2)
def f_x(x):
  return x * x

print(f_x(2)) // 4
print(f_x(3)) // 9 
Like functions in maths, Pure Functions return what you expect. They can also receive parameters, but do not access unpredictable things like time.time().
For every same input it returns the same output (determinism).

Higher-Order Functions

def higherOrder(secondFunc):
  return secondFunc

def f():
  return "f-function"

higherOrder(f)  # <function f at ...>
higherOrder(f)()  # f-function
As higher-order-function we call a function that receives a functions as a parameter, and returns one.

#Lambdas in Python

Lambdas in Python

Lambdas are a type of functions in Python.
Often, they are also called anonymous functions, because they are not visible publicly since they have no name.
In Python, lambdas can be created with the lambda keyword. If you want to, you can store them in a variable nevertheless.

Lambda vs. normal function

f1 = lambda x, y : x * y

def f2(x, y):
    return x * y
Both functions do the same. While the normal function wrapps its parameters with brackets, the lambda does not. Also, you can see that a lambda always returns something.

Why Lambdas?

You might ask, why to use them at all?
First, since lambdas are not globally visible, they are awesome helper functions to pass into high-order functions. Using map is a great example. Another benefit of lambdas is their inability to perform side effects - since their body is a return-statement, you can only use a single expression. Side effect can lead to bugs, since mutating state is made possible.

#Map in Python

What is map?

Map is a function in Python, which we can use to iterate over a list. But not only iterating is possible - map is all about iterating and transforming data, without mutating the original object.
Not mutating data? This is what we want in functional Python.

Using map in Python

numbers = [1, 2, 3]
doubledNumbers = list(map(lambda x: x * 2, numbers))

print(doubledNumbers)
# [2, 4, 6]
In this example, we iterate over a list of numbers and double each number. The function list turns our map object into a list.
To transform the data, we use a lambda function.

Mapping with two lists

Xs = [1, 2, 3, 4, 5, 6]
Ys = [2, 4, 6, 8, 10, 12]

results = list(map(lambda x, y: x + y, Xs, Ys))

print(results)
# [3, 6, 9, 12, 15, 18]
By adding another list as argument and providing another parameter for the lambda function, we can transform data from both lists.

#Filter & Reduce in Python

Filter & Reduce in Python

Filter and Reduce belong to the same category of tools in functional Python as Map.
Both help to transform data from a list, without mutating the original list.
Filter filters data from the list, Reduce reduces our list to a single value.

Filter

numbers = [1, 2, 3, 4]
even = list(filter(lambda x: x % 2 == 0, numbers))
    
print(even)
# [2, 4]
Filter works like this: For each x that leads to the return value of lambda being true, the x is copied to the resulting, filtered array.

Reduce

from functools import reduce

numbers = [1, 2, 3, 4]
sum = reduce(lambda x, y: x+y, numbers)

print(sum)  # 10
x and y can be imagined like the previous value and the current value. The previous value is the sum of all numbers in the list so far, added up.

#Immutability in Python

What is immutability?

Writing immutable objects is another goal in functional python. This means, having object we can't mutate.
Not being able to mutate them is important to avoid side effects.
By default, primitive types like int, float, bool and strings and tuples are immutable. This doesn't mean, we can't reassign them, which is a problem.

Mutable vs. reassignable - primitive types

num = 10 
num = 5
# num is 5 now, even though the int is immutable
Immutablilty means the object isn't mutable once it is created. This doesn't mean, the variable can't be reassigned. Through reassigning, the num = 5 is stored in a different place in the memory - therefore, it's another object, not a mutation. Yet, in functional Python, this is a problem.

Avoiding reassigning

from typing import Final

num: Final = 10
num = 5
In Python, a variable can be declared as final. This doesn't prohibit reassigning but your editor should throw a warning.

Mutable objects

nums = [1, 2, 3]
print(id(nums))  # 140703355859008
    
nums[0] = 10
print(id(nums))  # 140703355859008
    
print(nums)  # 10, 2, 3
Since the place in the memory stays the same, we have the same object - but its values changed. This is mutability par excellence.