Programming Paradigms

What are programming paradigms?

What are programming paradigms?

High level concept for structure & implementation of
computer programs and programming languages.

back to basics

Ben Eater’s 8bit Computer

Instructions of the 8bit computer

microcode

assembled program

👉 imperative programming

a sequence of steps (like a cookbook)

ASM on ARM & OSX

.global _start 
.align 4

_start: 
    mov x5, #3      // initialize counter with 3
loop:
    bl print_hello
    sub x5, x5, #1  // subtract 1 from counter
    cmp x5, #0      // check if counter is 0
    bne loop        // else, go to loop
    b exit          // go to exit

print_hello:
    mov x16, #4     // syscall 4 == write
    mov x0, #0      // 0 == stdout
    adr x1, hello
    mov x2, #13 
    svc #0x80       // perform syscall
    ret 

exit:
    mov x16, #1     // syscall 1 == exit
    mov x0, #0      // exit code 0
    svc #0x80       // perform syscall

hello: .ascii "Hello World\n"

Structured Programming

go to statement considered harmful (1968)

👉 structured programming

reduces freedom of control flow
use if, for, while etc… instead of jumps

(FORTRAN (1955) was unstructured…)

Procedural programming

Use procedures that can be called to modularize code.

Some definitions overlap between procedural and structured programming.

structured & procedural

def print_hello():
    print("Hello World")

for _ in range(3):
    print_hello()
Hello World
Hello World
Hello World

Object Oriented Programming

Discovered in 1966 with the creation of Simula-67 language.

The idea of polymorphism removes the need for function pointers.

I’m sorry that I long ago coined the term “objects” for this topic because it gets many people to focus on the lesser idea. The big idea is “messaging”.

Alan Kay
coined the term “Object Oriented”
by Marcin Wichary, CC BY 2.0

Functional Programming

Appeared in 1957, with the Lisp programming language, the first functional language.

Functional programming removes assignment 😱.

👉 no side-effects

x = 1
x = 2
<source>:1:1: error: [GHC-29916]
    Multiple declarations of `x'
  |
2 | x = 2
  | ^

Actually, Lambda Calculus was already formulated in the 30ies.

Quicksort in Haskell

qsort [] = []
qsort (x:xs) = (qsort [x' | x' <- xs, x' < x]) ++ [x] ++ (qsort [x' | x' <- xs, x' >= x])

main = do
    print (qsort [1, 5, 3, 4, 2])
[1,2,3,4,5]

What are programming paradigmns?

  • Constrained programming habits, help you to limit mistakes
  • Programming paradigmns assist in constraining
  • Paradigmns influence both:
    • language design
    • programming habit

Usally no clean separation

Independent of language, it’s good to know about the ideas.

In many cases, you’ll want to combine ideas.

Languages are influenced by paradigmns.

It’s ok to have preferences though…

reverse history

 paradigm year
structured  1968
object oriented  1966
functional 1957

(of course there are many more)

1. Imperative vs Declarative

Imperative

How to achive it?

  • procedural
  • object oriented

Declarative

What to achieve?

  • functional
  • logic
  • reactive

In the last 15 years, most of the imperative languages gained some level of support for declarative concepts. Python supports both approaches.

Imperative programming - How to achieve it?

  • Think of it like what you do when baking a cake
Apple cake

Preheat oven to 200 degrees
Peel 1kg of apples
Cut apples into small pieces
Mix 250g butter, 250g sugar, 5 eggs, and 1 package vanilla sugar
Add 350g flour and 1 package baking powder
Mix everything
Gently fold in the apple pieces
Pour mixture in a baking dish
Bake for 30 minutes

Declarative programming - What to achieve?

  • Markup (e.g. HTML or Markdown): what should appear on a website, but not the control flow for rendering the website
  • Query: SQL states what data to fetch from a database, not how it’s done
  • Functional: e.g. ds2 = ds1.assign(tas=compute_tas())

2. Object-oriented programming (OOP)

Polymorphism

The same code can invoke different actions.

Polymorphism using classes

@dataclass
class Book:
    owner: str
    def info(self):
        print("Owner =", self.owner)

@dataclass
class Laptop:
    producer: str
    def info(self):
        print("Producer =", self.producer)

Use case

book = Book("me")
laptop = Laptop("bigcorp")
for x in (book, laptop):
    x.info()
Owner = me
Producer = bigcorp

Encapsulation & Abstraction

Back to Alan Kay:

The big idea is “messaging”.

Bundle together data and member functions inside an object, while limiting direct access to the state.

Used to hide complexity and increase code correctness.

Inheritance

Many OO languages support inheritance: a class can inherit features from a parent class

classDiagram
    class Book
    Book : +int page_count     
    Book : +int get_page_count() 

    class EBook
    EBook : +string url 
    EBook : +string get_url()

    Book <|-- EBook

Used to increase code reusability and avoid code duplication

Inheritance Example

Define a book class

class Book:
    def __init__(self, pages):
        self.page_count = pages

    def get_page_count(self):
        return self.page_count

Define an EBook class

class EBook(Book): 
    def __init__(self, pages, link):
        super().__init__(pages)
        self.url = link
    
    def get_url(self):
        return self.url

Use an EBook object

book_obj = EBook(100, "www.link-to-book.com")
book_obj.get_url()         # call function from EBook class
book_obj.get_page_count()  # call function from superclass (Book)
100

Hands-on Session!

  1. Define a class Person with a constructor which initialises name and address attributes
  2. Define a class Student that extends Person and sets a university attribute
  3. Instantiate person Alice and student Bob
  4. Print their addresses
  5. Replace self.address with self.__address. What happens?
  6. Find a way to change Alice’s address and print the new one

3. Generic programming

Algorithms with work for any (or many) types.

Revisit the power function.

4. Functional programming (FP)

Functions as first-class citizens

  • state-less group of functions organized in namespaces
  • a function receives all the information through its arguments (which can be also functions)
  • an algorithm is implemented through function composition
  • very useful for data pre-/post-processing in data pipelines in both HPC and Cloud computing

Closures

Closures allow functions to access and manipulate variables that are defined outside their scope

Closure example

def f(x):
    return lambda y: x + y 

g = f(1) # g is a closure (still a function of y)
h = g(2) 

print(h) # what is printed here?

\(h = g(2)=f(1)(2) = (\lambda y : 1 + y)(2) = 1 + 2 = 3\)

More on lambda functions.

Higher-order functions

Higher-order functions can take functions as arguments and/or return other functions.

  • very common examples include the differential operator \(d/dt\) and the usage of callback functions

Print callback example

def g():
    print("Function g")

def f(callback_fn) :
    print("Function f")
    callback_fn()

f(g)

Pure functions

Pure functions have no side effects

Pure function

def f(x):
    return x * x

Impure function

def g(x):
    print(x)

Immutability

  • Immutable data means it doesn’t change in time.
  • usually input parameters for functions are immutable

An example of how to use immutable data structures can be found here.

Map, filter, reduce

  • \(map(f,list)\) - apply function \(f\) to each element of \(list\)
items = [1, 2, 3]
incremented_items = list(map(lambda x: x+1, items))
# [2, 3, 4]
  • \(filter(p,list)\) - apply predicate \(p\) to filter elements of \(list\)
items = [1, 2, 3]
odd_items=list(filter(lambda x: x%2 == 1, items))
# [1, 3]
  • \(reduce(f, list)\) - apply function \(f\) to reduce the \(list\)
from functools import reduce
items = [1, 2, 3]
sum_items=reduce(lambda x, y : x + y, items, 0)  #  0 is initializer value  
# 6

Hands-on Session!

Write a higher-order function using map/filter/reduce which returns the product of the squares of the positive values from the input array

items=[-5, 6, -4, 7, -3, 8, -2, 8, -1, 10, 0]
f(items) = ?

Best practices

OOP vs FP

  • use OOP for complex code base with state-full behaviour which gets updated during execution
    • e.g. a simulation code where all entities share common behaviour and state, and which evolves in time
  • use FP for state-less behaviour based on functions without side-effects
    • e.g. a math library containing functions like pow, exp, etc

Documentation

Backup slides

Lambda function

A lambda function is an anonymous function defined in-place.

Lambda function

lambda x, y: x + y

It can be :

  • used as return of a function
  • passed as argument to another function
  • called immediately where it is defined

Immutability in large data objects

When using xarray.Dataset make sure your function does not change the input data

def convert_wind_from_uv_to_magnitude_and_direction(ds):
    ds["mag"] = (ds["u"]**2 + ds["v"]**2)**.5
    ds["dir"] = np.arctan2(ds["v"], ds["u"])
    del ds["u"]
    del ds["v"]
    return ds

Better alternative would be:

def convert_wind_from_uv_to_magnitude_and_direction(ds):
    return ds.assign(
        mag=(ds["u"]**2 + ds["v"]**2)**.5,
        dir=np.arctan2(ds["v"], ds["u"])
    ).drop_vars(["u", "v"])

Exercises

Using numpy instead of map/filter/reduce

Take the example from the hands-on session on functional programming using map/filter/reduce and rewrite it using idiomatic numpy. Explain how this numpy code relates to the Python implementation using map/filter/reduce. Why can numpy boost performance and why is the way that numpy is implemented so useful?

For those who don’t know numpy yet, have a look here: numpy for beginners

Next week we will randomly select some participants to present and discuss their solutions.