If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Tim Peters, Zen of Python
Before we talk about programming paradigms, we must first define what a program is: a set of instructions in a language that can be translated into machine code and executed by a computer. As long as we don't make any algorithmic mistakes and our instructions don't require unnecessary computation, it doesn't really matter how we construct these programs for the computer to execute. However, since programs are (so far) written by humans, we need to structure our code and keep it in an understandable form so it can be maintained and extended by humans. As programs have become more complex, three basic programming paradigms have emerged: Procedural, Object-Oriented, and Functional Programming. These are ways of thinking about solving problems and structuring program code. Some languages focus on one paradigm (such as Haskell for functional programming). Python, which we use here, is suitable for all three, so it is called a multi-paradigm programming language. What these paradigms share is that they modularize code, but they use different concepts to do so. This similarity is not surprising, because any large program will contain code that needs to be used multiple times. Writing that code repeatedly would be redundant, while it is more elegant to write a generic version once and call it from different places.
Before introducing the paradigms, we should distinguish two types of programming: imperative and declarative. Imperative programming means we give the computer precise step-by-step instructions to execute, which is ultimately what we have been learning so far. The code explicitly states how the instructions should run. Declarative programming, in contrast, focuses more on what should happen rather than exactly how. Imperative programming is closer to how computers operate, so it's easier for the machine to translate. Procedural and object-oriented programming are imperative. Declarative programming is less common and is more abstract.
Procedural programming is an imperative paradigm where code is organized into procedures (subroutines). In Python, procedures are often implemented as functions. For example, Jupyter Notebooks commonly employ procedural programming for data analysis. Procedural programming is more intuitive for beginners, but purely procedural code can be harder to reuse across different projects.
# Managing a Flower Store: Procedural Approach
# Store flowers
flowers = {}
def add_flower(flower_type, quantity):
if flower_type in flowers:
flowers[flower_type] += quantity
else:
flowers[flower_type] = quantity
def get_quantity(flower_type):
return flowers.get(flower_type, 0)
# Adding flowers
add_flower("Roses", 10)
add_flower("Lilies", 15)
# Getting quantity of a flower type
print("Roses available:", get_quantity("Roses"))
Procedural programming focuses on writing a series of instructions (procedures) that act on data. In this
florist example, we keep a global dictionary flowers
and define functions like
add_flower
and get_quantity
to operate on it. This
approach clarifies how tasks are carried out but can become unwieldy for large, complex systems.
Object-Oriented Programming (OOP) is based on the concept of "objects," which contain both data and methods for manipulating that data. OOP organizes code into modular parts. If we imagine a program as an organism, different tasks are handled by different organs. For instance, the heart pumps blood, but the liver doesn't need to know the details of that process. This is analogous to the basic ideas of OOP. The first object-oriented language, Simula, was developed in the 1960s to simulate real-world systems.
# Managing a Flower Store: Object-Oriented Approach
class FlowerStore:
def __init__(self):
self.flowers = {}
def add_flower(self, flower_type, quantity):
if flower_type in self.flowers:
self.flowers[flower_type] += quantity
else:
self.flowers[flower_type] = quantity
def get_quantity(self, flower_type):
return self.flowers.get(flower_type, 0)
# Creating a FlowerStore instance
flower_store = FlowerStore()
# Adding flowers
flower_store.add_flower("Roses", 10)
flower_store.add_flower("Lilies", 15)
# Getting quantity of Roses
print("Roses available:", flower_store.get_quantity("Roses"))
In OOP, the florist software is modeled by a FlowerStore
class that encapsulates
the inventory in self.flowers
along with methods like
add_flower
and get_quantity
. This encapsulation
allows for more complex, real-world models and encourages code reuse and scalability.
Functional programming follows the declarative approach and emphasizes pure functions—functions that rely only on their inputs and produce outputs, with no side effects. A pure function does not affect any external variable, ensuring it consistently yields the same result for a given input. This approach can significantly improve predictability and enable straightforward testing or parallelization.
# Managing a Flower Store: Functional Approach
def add_flower(flowers, flower_type, quantity):
updated_flowers = flowers.copy()
updated_flowers[flower_type] = updated_flowers.get(flower_type, 0) + quantity
return updated_flowers
def get_quantity(flowers, flower_type):
return flowers.get(flower_type, 0)
# Initial empty flower collection
flowers = {}
# Adding flowers in an immutable way
flowers = add_flower(flowers, "Roses", 10)
flowers = add_flower(flowers, "Lilies", 15)
# Getting quantity of Roses
print("Roses available:", get_quantity(flowers, "Roses"))
In this functional style, data is not mutated directly; rather, new data structures are derived from old
ones using pure functions. This florist example uses add_flower
and
get_quantity
functions that take a
flowers
dictionary, process it, and return a new one. This pattern avoids
hidden state changes, simplifying reasoning and concurrency.
Different paradigms can be used to implement the same functionality. AI can assist in choosing a suitable paradigm, experimenting with different approaches, and coding in a particular style. Instead of asking broadly, "How do I structure my program?", you might say, "Show me how to build an inventory system in Python using procedural, object-oriented, and functional programming," which yields comparative implementations.
Example Prompt:Resulting AI-generated code:
class Book:
def __init__(self, title, author, year):
self.title = title
self.author = author
self.year = year
def __str__(self):
return f"{self.title} by {self.author} ({self.year})"
class Library:
def __init__(self):
self.books = []
def add_book(self, book):
self.books.append(book)
def get_books(self):
return self.books
# Creating book instances
book1 = Book("1984", "George Orwell", 1949)
book2 = Book("To Kill a Mockingbird", "Harper Lee", 1960)
# Creating a Library instance
library = Library()
# Adding books to the library
library.add_book(book1)
library.add_book(book2)
# Getting all books in the library
for book in library.get_books():
print(book)