Functional Programming

Functional programming is a programming style where functions are treated as first-class citizens. This means functions can be assigned to variables, passed as arguments, and returned from other functions. Functional programming focuses on writing clean and concise code that avoids changing state or mutating data.

Key Features of Functional Programming

  1. First-Class Functions:
    • Functions can be treated like variables.
    • They can be passed as arguments and returned from other functions.
  2. Pure Functions:
    • A function is "pure" if it doesn’t modify external state and always produces the same output for the same input.
  3. Immutability:
    • Data remains unchanged. Instead of modifying data, new copies are created.
  4. Higher-Order Functions:
    • Functions that can take other functions as arguments or return functions as results.
  5. Use of Lambda Functions:
    • Anonymous, inline functions.
  6. Built-in Functional Tools:
    • Python provides built-in functions like map(), filter(), and reduce() for functional programming.

Examples of Functional Programming in Python

1. First-Class Functions

Python
Copy
def square(x):
    return x * x

def apply_function(func, value):
    return func(value)

result = apply_function(square, 5)
print(result)  # Output: 25

2. Pure Functions

Python
Copy
def add_numbers(a, b):
    return a + b  # Pure function, no external state is modified

print(add_numbers(3, 4))  # Output: 7

3. Higher-Order Functions

A higher-order function takes another function as input.

Python
Copy
def double(x):
    return x * 2

def apply_to_list(func, numbers):
    return [func(n) for n in numbers]

result = apply_to_list(double, [1, 2, 3, 4])
print(result)  # Output: [2, 4, 6, 8]

4. Lambda Functions

Lambda functions are small anonymous functions defined using the lambda keyword.

Python
Copy
# Regular function
def add(a, b):
    return a + b

# Lambda function (same functionality)
add = lambda a, b: a + b

print(add(3, 5))  # Output: 8

5. Using map(), filter(), and reduce()

1. map(): Applies a function to all items in an iterable.

Python
Copy
numbers = [1, 2, 3, 4]
squares = map(lambda x: x * x, numbers)
print(list(squares))  # Output: [1, 4, 9, 16]

2. filter(): Filters items in an iterable based on a condition.

Python
Copy
numbers = [1, 2, 3, 4]
even_numbers = filter(lambda x: x % 2 == 0, numbers)
print(list(even_numbers))  # Output: [2, 4]

3. reduce(): Reduces an iterable to a single value (from functools module).

Python
Copy
from functools import reduce

numbers = [1, 2, 3, 4]
total = reduce(lambda x, y: x + y, numbers)
print(total)  # Output: 10

Benefits of Functional Programming

  • Readable and concise code: Focuses on "what" to do rather than "how" to do it.
  • Easier to debug and test: Pure functions are predictable.
  • No side effects: Immutability reduces bugs caused by data mutations.

When to Use Functional Programming?

Functional programming is ideal for:

  • Data processing pipelines.
  • Code that prioritizes immutability and pure functions.
  • Scenarios where reusable and composable functions are required.

Closures

  • A closure is a function that remembers the variables from its enclosing scope even after the scope has finished execution.
  • Used to encapsulate logic and maintain state.

Example:

Python
Copy
def outer_function(message):
    def inner_function():
        print(message)  # Remembers 'message' from outer_function
    return inner_function

closure = outer_function("Hello, World!")
closure()  # Output: Hello, World!

Decorators

  • A decorator is a function that takes another function as input and extends its behavior without modifying the original function.
  • Used for logging, authentication, performance monitoring, etc.

Example:

Python
Copy
def decorator_function(original_function):
    def wrapper_function():
        print("Wrapper: Before the function call")
        original_function()
        print("Wrapper: After the function call")
    return wrapper_function

@decorator_function
def say_hello():
    print("Hello!")

say_hello()
# Output:
# Wrapper: Before the function call
# Hello!
# Wrapper: After the function call

Generators

  • A generator is a special type of function that produces items one at a time using the yield keyword.
  • It does not store the entire result in memory, making it memory-efficient.

Example:

Python
Copy
def generate_numbers():
    for i in range(5):
        yield i  # Yield one number at a time

gen = generate_numbers()
for num in gen:
    print(num)
# Output: 0, 1, 2, 3, 4

yield

  • The yield keyword is used to produce a value in a generator function and pause the function’s execution.
  • The function can resume execution where it left off.

Example:

Python
Copy
def countdown(n):
    while n > 0:
        yield n
        n -= 1

for num in countdown(3):
    print(num)
# Output: 3, 2, 1

itertools Module for Advanced Iterators

  • The itertools module provides advanced iterator tools to handle complex data-processing tasks efficiently.
  • It includes functions for infinite iteration, permutations, combinations, and more.

Common Functions in itertools:

1. count(): Generates an infinite sequence of numbers.

Python
Copy
from itertools import count
for i in count(5):
    if i > 10:
        break
    print(i)  # Output: 5, 6, 7, 8, 9, 10

2. cycle(): Cycles through an iterable indefinitely.

Python
Copy
from itertools import cycle
for i, item in enumerate(cycle("AB")):
    if i > 3:
        break
    print(item)  # Output: A, B, A, B

3. chain(): Combines multiple iterables into one.

Python
Copy
from itertools import chain
for item in chain([1, 2], ['a', 'b']):
    print(item)  # Output: 1, 2, a, b

4. combinations(): Generates all possible combinations of a given length.

Python
Copy
from itertools import combinations
for combo in combinations([1, 2, 3], 2):
    print(combo)  # Output: (1, 2), (1, 3), (2, 3)

5. product(): Generates a cartesian product of input iterables.

Python
Copy
from itertools import product
for prod in product([1, 2], ['a', 'b']):
    print(prod)  # Output: (1, 'a'), (1, 'b'), (2, 'a'), (2, 'b')