Introduction to Programming Paradigms

January 30, 2021

/images/intro-to-programming-paradigms.jpg

Prerequisites

Some basic understanding of primitives and control flow. Familiar with if-else, while, for, etc.

Additional Resources / Reading List

https://hackr.io/blog/programming-paradigms - Good introduction. Can skip the section on logical programming, not very common in modern languages.

https://repl.it/ - Programming sandbox environment. Pick from any language and start playing with language features.

Head First Object-Oriented Analysis and Design: A Brain Friendly Guide to OOA&D by Brett McLaughin - Very fun and approachable book on understanding object oriented programming. Not matter how old, it is still relevant.

What is programming paradigm and why is it important?

Software development and programming is mostly an exercise in thinking and expressing our thoughts into code. One common saying is that "good code should read like a story". And just like a story, code can have different type of code structure for readers to make sense of what the code is doing. This is especially important for code that is co-developed by a team, where team members need to quickly understand what is going on in order to make changes.

Most programming languages support a mix of programming paradigms.

In the following we will attempt to get the same result using different programming paradigms.

Procedural

As the name suggests, it is written in a way how you want the computer to carry out the procedure. It is code that literally hand-holds the computer through what you want it to do. It is also known as imperative programming.

list = ['apple', 'pear', 'orange']
index = 0

while index < list.length  do
	if list[index] == 'apple'
		put 'found'
	end
end

Core characteristics

  • Variables declared for the sake of managing state within program
  • Direct manipulation of primitives (e.g. accessing array indexes)
  • Statements that let you "jump" to other parts of the code

Pros

  • Code is explicit. No surprises
  • Usually very performant and there is nothing in the way of the actual instructions
  • Easy to understand

Cons

  • Code can grow very large and unmaintainable due to the sheer number of operations
  • Not easy to reason around if operations are very complex, like nested loops and deep conditional control flows
  • There is a tendency of passing around primitive data types, also making is hard to track what changed

Use cases

You can't really escape procedural programming. After all, a computer is supposed to receive a set of instructions to do anything useful. However, the bulk of your thought process should not be thinking in procedures.

Functional

Functional programming has its roots from mathematics and relies heavily on using specific expressions to return values.

list = ['apple', 'pear', 'orange']

# This defines a function that calls itself
def find_apple(array)
	if array.empty?
		false
	elsif array[0] == 'apple'
		puts 'found'	
	else
		find_apple(list[1..array.length])
	end
end
# call the function
find_apple(list)

# this is another way, using 'reduce'
list.reduce(false) { |item| 
	if item == 'apple'
		puts 'found'
	else
		false
	end
}

Core characteristics

  • Recursion: functions that call itself.
  • Functions that have specific purposes (e.g. map , reduce).
  • Emphasis on pure functions (strict adherence to only have functions that purely return a value and not do anything else).
  • Allows composition of small functions into larger functions.
  • Extensive type system.

Pros

  • Code is very logical and 'smart' if you know how it works.
  • Very predictable outcomes, because of pure functions.
  • Immutability. Once a variable is assigned, it cannot be changed (or mutated). This property is surprisingly useful when designing large scale and complicated software.

Cons

  • Needs deep knowledge in functional programming to fully utilize its strengths for large and complex code. Not recommended for beginners to deep dive into functional programming.

Usage

Simple functional programming principles can help you express your intent clearer and reduce surprises in your code. For example, when writing functions, separate the pure effects from impure effects. Example:

# --------------
# Impure example
def upcase_and_print(string)
	string.upcase!  # the ! changes the original 
	puts string   # < writing to terminal is a side-effect
end
text = "hello"
upcase_and_print(text) # ==> "HELLO"
puts text # ==> "HELLO"  -> this changed text by surprise

# ------------------------
# Separate pure and impure
def upcase(string)
	return string.upcase  # this doens't change the original 
end
def print(string)
	puts string
end
text = "hello"
new_text = upcase(text)
print(new_text) # ==> "HELLO"
puts text # ==> "hello"  --> this doesn't change original by surprise

Object Oriented Programming

As the name suggests, object oriented programming (OOP) is built around modelling things in the real world. Modelling software against real world objects is a natural (and correct) thing to do, and most people are able to pick up the basics quickly. It is also a fact that the invention of OOP paved the way for large and complex software to be built for the modern world.

class Fruit
	attr_reader :name # this line makes name publicly accessible

	def initialize(name)
# the '@' makes the variable specific to only this instance
# You can have many instances of Fruit, and this allows each to have
# different names that they maintain internally
		@name = name 
	end
end

class Basket
	def initialize(items)
		@items = items
	end
	
	def find_item(name) # this is known as a 'method'
		if @items.find { |item| item.name == name }
			puts 'found'
		end
	end
end

list = [Fruit.new('apple'), Fruit.new('pear'), Fruit.new('orange')]
basket = Basket.new(list)
basket.find_fruit('apple')  # ==> 'found'

Core characteristics

  • Encapsulation: The focus on packaging data within classes where they belong. A related concept is information hiding. Hiding unnecessary data helps to present a clear operating model to other objects that want to use it.
  • Inheritance: The idea that some objects are specialised versions of a more generic version. (e.g. A cat is a specialised version of a mammal)
  • Composition: The idea that objects can be composed of other objects. (e.g. A car has many wheels. A car has many headlights)
  • Message passing: The idea that program is executed through objects sending each other messages.

Pros

  • Easy to reason and structure when used properly.
  • OOP language features are well supported among most modern programming languages. Very translatable skill set between languages.

Cons

  • The community has a spectrum of people who have all sorts of opinions about OOP. It can sometimes be hard to have consensus on what OOP approach is best to solve a problem.
  • Easy to start, but difficult to master. Each language has slightly different idea of doing the same thing (e.g. Java calls it interface, Ruby calls it module, Swift calls it protocol, Python calls it mixin, C++ calls it abstract class. They kinda mean the same thing of sharing functions between classes, but also different in a significant way that affects your code structure).

Usage

OOP should be the first thing that all new engineers learn. It is a common skill among engineers and most projects use OOP, so it is easy to slip into a project if needed. It also helps to promote critical thinking around where's the best places to keep data and separate responsibility between classes.

Conclusion

There is more than 1 way to get the same result. What you choose depends on who and what you are working with. There are even more paradigms that are not discussed here like declarative (SQL), logical (Prolog) and more. Feel free to go down the rabbit hole at your own time.

Where to go from here?

I highly recommend trying out writing a couple of lines of code in OOP to get a feel of how it works. A basic exercise:

Foreign purchases

Create a class called Money that holds information about amount and currency.

Create a class called LineItem that holds information about transaction name and spending.

Spending should be an instance of Money.

Line item should have a method called print that outputs the value in a specific format.

spending = Money.new(100.00, 'SGD')
line_item = LineItem.new('clothes', spending)
line_item.print # You should see 'clothes SGD 100.00'

Find similarities and differences

Fire up your web browser and search for object oriented programming for some programming languages you have heard of (e.g. search object oriented programming python)

Look for similarities and differences in the way how each language

  • declares object classes
  • initialization
  • declaring instance variable and methods

Popular languages are more similar than different. This exercise helps you to gain confidence that programming structure is more important than language specific syntax.

Like my content? Subscribe to receive tips on software engineering.
© Alvin Ng