An Introduction to the Art of Computer Programming Using Python in the Age of Generative AI

XV. Functional Programming

Introduction to Functional Programming

Functional programming is a programming paradigm that treats computation as the evaluation of mathematical functions and avoids changing states or mutable data. It emphasizes the use of functions, in contrast to the procedural and object-oriented programming paradigms. In Python, we can apply functional programming principles to write clearer, more concise, and more predictable code.

Pure Functions

Pure functions are the foundation of functional programming. The output of a pure function is determined solely by its input values, with no observable side effects. This means that a pure function does not change any external state or depend on any external state that might change. As a result, pure functions are predictable and easier to test.


def add(a, b):
    return a + b

result = add(3, 4)
print(result)
        

In the example above, the add function is pure because it always produces the same output for the same input and does not affect any external state.

Lambda Functions

Lambda functions, also called anonymous functions, are integral to functional programming in Python. They let you create small, unnamed functions inline, which is especially handy for constructs like map, filter, and reduce that often take functions as arguments. The syntax for a lambda function is:


lambda arguments: expression
        

Suppose you want to filter odd numbers in a list. A lambda function for this might look like:


numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

odd_numbers = filter(lambda x: x % 2 != 0, numbers)
print(list(odd_numbers))
        

Higher-Order Functions

Higher-order functions either take one or more functions as arguments or return a function as a result. They offer more abstract and flexible code. Python's built-in map and filter functions are prime examples of higher-order functions.


numbers = [1, 2, 3, 4, 5]
squared_numbers = map(lambda x: x ** 2, numbers)
print(list(squared_numbers))

even_numbers = filter(lambda x: x % 2 == 0, numbers)
print(list(even_numbers))
        

First-Class Functions

In Python, functions are first-class citizens, meaning they can be assigned to variables, passed as arguments, and returned by other functions. This property facilitates higher-order functions and supports functional programming patterns.


def square(x):
    return x * x

def cube(x):
    return x * x * x

def my_map(func, arg_list):
    result = []
    for i in arg_list:
        result.append(func(i))
    return result

squares = my_map(square, [1, 2, 3, 4, 5])
print(squares)

cubes = my_map(cube, [1, 2, 3, 4, 5])
print(cubes)
        

Immutability

Immutability is another cornerstone of functional programming, meaning data is not modified after creation. Instead of mutating data, new data structures are formed. This promotes predictable code with fewer hidden side effects, thus reducing bugs.


# Using tuple instead of list to ensure immutability
numbers = (0, 1, 2, 3, 4)

# Functions that operate on immutable data return new data structures
def add_one_to_each(numbers):
    return tuple(x + 1 for x in numbers)

new_numbers = add_one_to_each(numbers)
print(new_numbers)
print(numbers)
        

Prompting AI for Implementing Functional Programming

Be clear about the functionality you need when consulting AI for functional programming. Instead of asking, "How do I use functional programming?", specify your intent: "Help me write Python functions that adhere to functional principles, such as pure functions and higher-order functions." Provide requirements like "I need a pure function to add two numbers and a higher-order function to filter out even numbers from a list." Request examples, such as "Can you show me how to use map and filter on a list of integers?"

Example Prompt:
Write a functional program for a Morse code translator, including functions to encode and decode messages.

Resulting AI-generated code:


MORSE_CODE_DICT = {
    'A': '.-', 'B': '-...', 'C': '-.-.', 'D': '-..', 'E': '.', 'F': '..-.',
    'G': '--.', 'H': '....', 'I': '..', 'J': '.---', 'K': '-.-', 'L': '.-..',
    'M': '--', 'N': '-.', 'O': '---', 'P': '.--.', 'Q': '--.-', 'R': '.-.',
    'S': '...', 'T': '-', 'U': '..-', 'V': '...-', 'W': '.--', 'X': '-..-',
    'Y': '-.--', 'Z': '--..',
    '0': '-----', '1': '.----', '2': '..---', '3': '...--', '4': '....-',
    '5': '.....', '6': '-....', '7': '--...', '8': '---..', '9': '----.',
    ',': '--..--', '.': '.-.-.-', '?': '..--..', '/': '-..-.', '-': '-....-',
    '(': '-.--.', ')': '-.--.-', ' ': '/'
}

def encode_message(message):
    return ' '.join(MORSE_CODE_DICT.get(char.upper(), '') for char in message)

def decode_message(morse_code):
    reverse_dict = {value: key for key, value in MORSE_CODE_DICT.items()}
    return ''.join(reverse_dict.get(code, '') for code in morse_code.split(' '))

# Encoding a message to Morse code
message = "HELLO WORLD"
encoded_message = encode_message(message)
print(f"Encoded: {encoded_message}")

# Decoding a Morse code message
morse_code = ".... . .-.. .-.. --- / .-- --- .-. .-.. -.."
decoded_message = decode_message(morse_code)
print(f"Decoded: {decoded_message}")
Back to Home