Principles of Programming
A2 Level — Unit 3: Programming & System Development
Programming Paradigms
A programming paradigm is a fundamental style or approach to programming that dictates how problems are structured, broken down, and solved. Different paradigms suit different types of problem, and understanding when to apply each one is essential at A2 level.
Procedural Programming
Procedural programming organises code as a sequence of instructions grouped into procedures (also called subroutines or functions). The program executes statements in order, calling procedures as needed.
Procedural programming is a paradigm based on the concept of procedure calls, where programs are built from one or more procedures (sequences of instructions) that operate on data.
Key features:
- Programs follow a top-down, step-by-step approach
- Code is organised into reusable procedures/functions
- Data and functions are kept separate
- Uses sequence, selection, and iteration as control structures
- Variables may be local or global in scope
# Procedural approach: calculating average of marks
def get_marks():
marks = []
for i in range(5):
mark = int(input("Enter mark: "))
marks.append(mark)
return marks
def calculate_average(marks):
total = sum(marks)
return total / len(marks)
def display_result(average):
print(f"The average mark is: {average:.1f}")
# Main program flow
marks = get_marks()
avg = calculate_average(marks)
display_result(avg)
' Procedural approach: calculating average of marks
Sub Main()
Dim marks(4) As Integer
GetMarks(marks)
Dim avg As Double = CalculateAverage(marks)
DisplayResult(avg)
End Sub
Sub GetMarks(ByRef marks() As Integer)
For i As Integer = 0 To 4
Console.Write("Enter mark: ")
marks(i) = CInt(Console.ReadLine())
Next
End Sub
Function CalculateAverage(marks() As Integer) As Double
Dim total As Integer = 0
For Each mark As Integer In marks
total += mark
Next
Return total / marks.Length
End Function
Sub DisplayResult(average As Double)
Console.WriteLine($"The average mark is: {average:F1}")
End Sub
Advantages:
- Simple and straightforward to understand
- Easy to trace the flow of execution
- Well suited for small to medium-sized programs
- Good for scripting and automation tasks
When to use:
- Simple sequential tasks such as batch processing or scripts
- Mathematical or scientific calculations
- Problems where data structures are straightforward
- When the development team is more familiar with procedural approaches
Object-Oriented Programming (OOP)
Object-oriented programming organises code around objects that combine data (attributes) and behaviour (methods) together. Programs are built by defining classes and creating objects that interact with one another.
Key features:
- Encapsulation of data and methods within objects
- Inheritance allows code reuse through class hierarchies
- Polymorphism enables a single interface for different underlying forms
- Abstraction hides complex details behind simple interfaces
Advantages:
- Models real-world entities naturally
- Promotes code reuse through inheritance
- Easier to maintain and modify large codebases
- Encapsulation protects data integrity
- Supports modular development by teams
When to use:
- Large, complex systems such as enterprise applications
- GUI-based applications and games
- Projects requiring long-term maintenance
- Simulations that model real-world entities
- When multiple developers work on different components
Functional Programming
Functional programming treats computation as the evaluation of mathematical functions. It avoids changing state and mutable data, relying instead on pure functions and immutable values.
Functional programming is a paradigm where programs are constructed by applying and composing pure functions – functions that always produce the same output for the same input and have no side effects.
Key features:
- First-class functions (functions can be passed as arguments and returned as values)
- Pure functions with no side effects
- Immutable data (values cannot be changed once created)
- Recursion used instead of iterative loops
- Higher-order functions such as
map,filter, andreduce
# Functional approach: filtering and transforming a list
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# Use map, filter, and lambda (higher-order functions)
evens = list(filter(lambda x: x % 2 == 0, numbers))
squared_evens = list(map(lambda x: x ** 2, evens))
print(squared_evens) # Output: [4, 16, 36, 64, 100]
# Using recursion instead of a loop
def factorial(n):
if n <= 1:
return 1
return n * factorial(n - 1)
print(factorial(5)) # Output: 120
' Functional-style approach in VB.NET using LINQ and lambda
Imports System.Linq
Module FunctionalExample
Sub Main()
Dim numbers() As Integer = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
' Filter and map using LINQ with lambda expressions
Dim squaredEvens = numbers.
Where(Function(x) x Mod 2 = 0).
Select(Function(x) x * x).
ToList()
For Each value In squaredEvens
Console.Write(value & " ") ' Output: 4 16 36 64 100
Next
End Sub
' Recursive factorial
Function Factorial(n As Integer) As Integer
If n <= 1 Then Return 1
Return n * Factorial(n - 1)
End Function
End Module
Advantages:
- Easier to test and debug because pure functions have predictable outputs
- Well suited to parallel and concurrent processing (no shared mutable state)
- Produces concise, mathematically elegant code
- Fewer bugs related to unintended side effects
When to use:
- Data processing and transformation pipelines
- Concurrent or parallel programming
- Mathematical and statistical computation
- Problems that can be expressed as transformations on data
Declarative / Logic Programming
Declarative programming describes what the program should accomplish rather than how to accomplish it. Logic programming (e.g. Prolog) is a subset where programs are expressed as a set of facts and rules, and the system uses logical inference to derive answers.
Declarative programming specifies the desired result without explicitly listing the step-by-step commands needed to achieve it. Logic programming uses facts and rules to answer queries through logical inference.
Key features:
- Programs state facts and rules rather than procedures
- A query engine derives solutions automatically
- Backtracking is used to explore possible solutions
- No explicit control flow (the runtime determines execution order)
Example in Prolog (logic programming):
% Facts
parent(tom, bob).
parent(tom, liz).
parent(bob, ann).
parent(bob, pat).
% Rule
grandparent(X, Z) :- parent(X, Y), parent(Y, Z).
% Query: Who are Tom's grandchildren?
?- grandparent(tom, Who).
% Result: Who = ann ; Who = pat
Advantages:
- Very concise for problems involving relationships and rules
- The programmer focuses on the problem domain, not the algorithm
- Well suited to artificial intelligence and expert systems
- Easy to add new facts and rules without restructuring code
When to use:
- Database queries (SQL is declarative)
- Artificial intelligence and expert systems
- Natural language processing
- Scheduling and constraint-satisfaction problems
- Rule-based systems
Paradigm Comparison
| Feature | Procedural | Object-Oriented | Functional | Declarative/Logic |
|---|---|---|---|---|
| Focus | Steps/procedures | Objects and interactions | Functions and transformations | Facts and rules |
| Data handling | Separate from functions | Encapsulated in objects | Immutable values | Facts in a knowledge base |
| Code reuse | Procedures/functions | Inheritance and polymorphism | Higher-order functions | Reusable rules |
| Best for | Simple scripts, calculations | Large systems, GUIs | Data processing, concurrency | AI, databases, expert systems |
| Example languages | C, Pascal, early BASIC | Java, Python, C#, VB.NET | Haskell, Erlang, Lisp | Prolog, SQL |
Exam questions often ask you to justify which paradigm is most appropriate for a given scenario. Always link your answer to the specific features of the paradigm (e.g. “OOP is appropriate because the system models real-world entities such as customers and orders, which naturally map to objects with attributes and methods”).
Object-Oriented Programming Concepts
OOP is built on several core concepts. You must be able to define each concept, explain its purpose, and provide code examples.
Classes and Objects
A class is a blueprint or template that defines the attributes (data) and methods (behaviour) that objects of that type will have. An object is a specific instance of a class, with its own values for the attributes defined by the class.
class Dog:
def __init__(self, name, breed, age):
self.__name = name # private attribute
self.__breed = breed # private attribute
self.__age = age # private attribute
def bark(self):
return f"{self.__name} says Woof!"
def get_name(self):
return self.__name
def get_info(self):
return f"{self.__name} is a {self.__age}-year-old {self.__breed}"
# Creating objects (instances of the Dog class)
dog1 = Dog("Rex", "German Shepherd", 5)
dog2 = Dog("Bella", "Labrador", 3)
print(dog1.bark()) # Rex says Woof!
print(dog2.get_info()) # Bella is a 3-year-old Labrador
Public Class Dog
Private _name As String
Private _breed As String
Private _age As Integer
Public Sub New(name As String, breed As String, age As Integer)
_name = name
_breed = breed
_age = age
End Sub
Public Function Bark() As String
Return $"{_name} says Woof!"
End Function
Public Function GetName() As String
Return _name
End Function
Public Function GetInfo() As String
Return $"{_name} is a {_age}-year-old {_breed}"
End Function
End Class
' Creating objects
Dim dog1 As New Dog("Rex", "German Shepherd", 5)
Dim dog2 As New Dog("Bella", "Labrador", 3)
Console.WriteLine(dog1.Bark()) ' Rex says Woof!
Console.WriteLine(dog2.GetInfo()) ' Bella is a 3-year-old Labrador
Methods and Attributes
Attributes (also called properties or fields) are the data items stored within an object. Methods are the functions defined within a class that operate on the object’s attributes and define its behaviour.
Methods typically fall into several categories:
| Method Type | Purpose | Example |
|---|---|---|
| Constructor | Initialises a new object | __init__ in Python, Sub New in VB.NET |
| Accessor (getter) | Returns the value of a private attribute | get_name() |
| Mutator (setter) | Changes the value of a private attribute | set_name(new_name) |
| General method | Performs an action or calculation | bark(), calculate_area() |
Encapsulation
Encapsulation is the bundling of data (attributes) and the methods that operate on that data into a single unit (class), while restricting direct access to the internal state. External code interacts with the object only through its public methods.
Encapsulation is achieved by making attributes private and providing public getter and setter methods to control access. This protects the data from being set to invalid values.
class BankAccount:
def __init__(self, account_holder, balance=0):
self.__account_holder = account_holder
self.__balance = balance # private attribute
def deposit(self, amount):
if amount > 0:
self.__balance += amount
return True
return False
def withdraw(self, amount):
if 0 < amount <= self.__balance:
self.__balance -= amount
return True
return False
def get_balance(self):
return self.__balance
account = BankAccount("Alice", 1000)
account.deposit(500)
account.withdraw(200)
print(account.get_balance()) # 1300
# account.__balance = -999 # This would NOT work (name mangling)
Public Class BankAccount
Private _accountHolder As String
Private _balance As Decimal
Public Sub New(accountHolder As String, Optional balance As Decimal = 0)
_accountHolder = accountHolder
_balance = balance
End Sub
Public Function Deposit(amount As Decimal) As Boolean
If amount > 0 Then
_balance += amount
Return True
End If
Return False
End Function
Public Function Withdraw(amount As Decimal) As Boolean
If amount > 0 AndAlso amount <= _balance Then
_balance -= amount
Return True
End If
Return False
End Function
Public ReadOnly Property Balance As Decimal
Get
Return _balance
End Get
End Property
End Class
Dim account As New BankAccount("Alice", 1000)
account.Deposit(500)
account.Withdraw(200)
Console.WriteLine(account.Balance) ' 1300
Inheritance
Inheritance allows a new class (subclass or child class) to be based on an existing class (superclass or parent class), inheriting its attributes and methods. The subclass can add new attributes/methods or override existing ones.
class Animal:
def __init__(self, name, species):
self.__name = name
self.__species = species
def get_name(self):
return self.__name
def speak(self):
return "..."
class Dog(Animal): # Dog inherits from Animal
def __init__(self, name, breed):
super().__init__(name, "Dog")
self.__breed = breed
def speak(self): # overriding the parent method
return "Woof!"
def get_breed(self): # new method specific to Dog
return self.__breed
class Cat(Animal): # Cat also inherits from Animal
def speak(self): # overriding the parent method
return "Meow!"
dog = Dog("Rex", "Labrador")
cat = Cat("Whiskers", "Cat")
print(f"{dog.get_name()} says {dog.speak()}") # Rex says Woof!
print(f"{cat.get_name()} says {cat.speak()}") # Whiskers says Meow!
Public Class Animal
Private _name As String
Private _species As String
Public Sub New(name As String, species As String)
_name = name
_species = species
End Sub
Public Function GetName() As String
Return _name
End Function
Public Overridable Function Speak() As String
Return "..."
End Function
End Class
Public Class Dog
Inherits Animal
Private _breed As String
Public Sub New(name As String, breed As String)
MyBase.New(name, "Dog")
_breed = breed
End Sub
Public Overrides Function Speak() As String
Return "Woof!"
End Function
Public Function GetBreed() As String
Return _breed
End Function
End Class
Public Class Cat
Inherits Animal
Public Sub New(name As String)
MyBase.New(name, "Cat")
End Sub
Public Overrides Function Speak() As String
Return "Meow!"
End Function
End Class
Dim dog As New Dog("Rex", "Labrador")
Dim cat As New Cat("Whiskers")
Console.WriteLine($"{dog.GetName()} says {dog.Speak()}") ' Rex says Woof!
Console.WriteLine($"{cat.GetName()} says {cat.Speak()}") ' Whiskers says Meow!
Polymorphism
Polymorphism (meaning “many forms”) allows objects of different classes to be treated through the same interface. A method call on a variable can produce different behaviour depending on the actual type of the object at runtime.
In the inheritance example above, both Dog and Cat override the speak() method. We can treat them polymorphically:
# Polymorphism in action
animals = [Dog("Rex", "Labrador"), Cat("Whiskers", "Cat")]
for animal in animals:
# The same method call produces different output
print(f"{animal.get_name()} says {animal.speak()}")
# Output:
# Rex says Woof!
# Whiskers says Meow!
' Polymorphism in action
Dim animals As New List(Of Animal)
animals.Add(New Dog("Rex", "Labrador"))
animals.Add(New Cat("Whiskers"))
For Each animal As Animal In animals
' The same method call produces different output
Console.WriteLine($"{animal.GetName()} says {animal.Speak()}")
Next
' Output:
' Rex says Woof!
' Whiskers says Meow!
Abstraction
Abstraction is the process of hiding complex implementation details and exposing only the essential features to the user. In OOP, abstract classes and interfaces define what an object can do without specifying how it does it.
Abstraction allows programmers to work with high-level concepts. For example, a Shape abstract class might define that all shapes must have a calculate_area() method, but each specific shape implements the calculation differently.
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def calculate_area(self):
pass
@abstractmethod
def describe(self):
pass
class Circle(Shape):
def __init__(self, radius):
self.__radius = radius
def calculate_area(self):
return 3.14159 * self.__radius ** 2
def describe(self):
return f"Circle with radius {self.__radius}"
class Rectangle(Shape):
def __init__(self, width, height):
self.__width = width
self.__height = height
def calculate_area(self):
return self.__width * self.__height
def describe(self):
return f"Rectangle {self.__width} x {self.__height}"
shapes = [Circle(5), Rectangle(4, 6)]
for shape in shapes:
print(f"{shape.describe()}: area = {shape.calculate_area()}")
Public MustInherit Class Shape
Public MustOverride Function CalculateArea() As Double
Public MustOverride Function Describe() As String
End Class
Public Class Circle
Inherits Shape
Private _radius As Double
Public Sub New(radius As Double)
_radius = radius
End Sub
Public Overrides Function CalculateArea() As Double
Return 3.14159 * _radius ^ 2
End Function
Public Overrides Function Describe() As String
Return $"Circle with radius {_radius}"
End Function
End Class
Public Class Rectangle
Inherits Shape
Private _width As Double
Private _height As Double
Public Sub New(width As Double, height As Double)
_width = width
_height = height
End Sub
Public Overrides Function CalculateArea() As Double
Return _width * _height
End Function
Public Overrides Function Describe() As String
Return $"Rectangle {_width} x {_height}"
End Function
End Class
Dim shapes As New List(Of Shape)
shapes.Add(New Circle(5))
shapes.Add(New Rectangle(4, 6))
For Each shape As Shape In shapes
Console.WriteLine($"{shape.Describe()}: area = {shape.CalculateArea()}")
Next
A common exam question asks you to distinguish between encapsulation and abstraction. Encapsulation is about hiding data behind private access and controlling it through public methods. Abstraction is about hiding complexity so that users interact with a simplified interface without needing to know the internal workings.
Standardisation of Computer Languages
Why Standards Matter
Standardisation ensures that programming languages behave consistently across different compilers, interpreters, and platforms. Standards are set by recognised bodies including:
| Organisation | Full Name | Role |
|---|---|---|
| ANSI | American National Standards Institute | Sets US standards, including for C and SQL |
| ISO | International Organization for Standardization | Sets international standards for many languages (C, C++, SQL, etc.) |
| W3C | World Wide Web Consortium | Sets standards for web technologies (HTML, CSS, XML, etc.) |
| IEEE | Institute of Electrical and Electronics Engineers | Sets standards for floating-point arithmetic, networking, etc. |
| ECMA | European Computer Manufacturers Association | Standards for JavaScript (ECMAScript), C#, etc. |
Benefits of standardisation:
- Portability – programs written in a standard-compliant language can be compiled and run on different platforms without modification
- Interoperability – different software systems can work together when they follow the same standards
- Consistency – programmers can move between tools and platforms with confidence that the language behaves the same way
- Education – teaching and learning are simplified when there is one agreed version of a language
- Longevity – standardised code is more likely to remain usable as technology changes
Difficulties in Agreeing Standards
Despite the clear benefits, reaching agreement on language standards is challenging:
- Competing interests – different companies and organisations may favour features that benefit their own products or platforms
- Pace of change – technology evolves faster than standards committees can agree on specifications; by the time a standard is published it may already be outdated
- Legacy compatibility – new standards must remain backward-compatible with existing code, which constrains innovation
- Complexity – modern languages are extremely complex, and specifying every detail precisely is a huge task (the C++ standard is over 1,500 pages)
- Vendor extensions – companies often add proprietary extensions to languages, which fragments the ecosystem (e.g. Microsoft’s extensions to C++)
- International differences – representatives from different countries may have different priorities and requirements
When asked about difficulties in agreeing standards, focus on the tension between innovation and stability. Companies want to innovate quickly with new features, but standards require slow, careful deliberation to ensure reliability and broad agreement.
Ambiguities in Natural Language
Natural languages (such as English or Welsh) are inherently ambiguous. A single sentence can have multiple valid interpretations. This is in stark contrast to programming languages, which must be completely unambiguous.
Lexical Ambiguity
Lexical ambiguity occurs when a single word has more than one meaning. The correct meaning depends on context.
Examples:
- “I went to the bank.” – a financial institution or the side of a river?
- “She could not bear the weight.” – to tolerate, or a large animal?
- “The bat flew across the garden.” – a flying mammal or a cricket bat?
- “He left his keys on the left side.” – departed/remaining, or a direction?
Syntactic Ambiguity
Syntactic ambiguity occurs when the grammatical structure of a sentence allows more than one interpretation, even though each individual word is clear.
Examples:
- “I saw the man with the telescope.” – Did I use a telescope to see him, or did he have a telescope?
- “Flying planes can be dangerous.” – The act of flying planes, or planes that are flying?
- “The teacher said on Monday he would set a test.” – Did the teacher make the statement on Monday, or is the test on Monday?
- “Visiting relatives can be boring.” – The act of visiting, or relatives who are visiting?
Semantic Ambiguity
Semantic ambiguity occurs when a sentence is grammatically clear but can still be interpreted in different ways due to the meaning of the words in context.
Examples:
- “The chicken is ready to eat.” – The chicken is cooked and ready to be eaten, or the chicken (animal) is hungry and ready to eat food?
- “Every student read a book.” – Did they all read the same book, or did each read a different one?
- “Time flies like an arrow.” – Time passes quickly, or there is a species of flies called “time flies” that are fond of arrows?
Why This Matters for Programming
These types of ambiguity illustrate why natural language cannot be used directly as a programming language. A computer must execute instructions with absolute precision and cannot resolve ambiguous statements. This motivates the need for formal, unambiguous syntax in programming languages, which is defined using tools such as syntax diagrams and BNF notation.
Need for Unambiguous Syntax
Syntax is the set of rules that defines the valid structure of statements in a programming language. Programming language syntax must be completely unambiguous so that every valid program has exactly one interpretation.
Programming languages require unambiguous syntax because:
- Compilers and interpreters must parse source code into a single, definitive structure – if a statement could be interpreted in two ways, the machine would not know which one the programmer intended
- Correctness – ambiguous syntax could lead to programs producing unexpected results
- Error detection – clear syntax rules allow compilers to identify and report errors precisely
- Consistency – all programmers and all tools agree on what a given piece of code means
Two formal methods are used to define unambiguous syntax: syntax diagrams and Backus-Naur Form (BNF).
Syntax Diagrams
A syntax diagram (also called a railroad diagram) is a graphical representation of the syntax rules of a language. It uses lines, arrows, and labelled shapes to show the valid sequences of symbols that form a construct.
How to Read Syntax Diagrams
Syntax diagrams use the following conventions:
| Element | Meaning |
|---|---|
| Rounded box / oval | Terminal symbol – an actual character or keyword that appears in the code (e.g. if, =, ;) |
| Rectangular box | Non-terminal symbol – a named construct defined by another diagram (e.g. <expression>, <identifier>) |
| Arrows / lines | Show the direction of reading; follow the arrows from left to right |
| Branching paths | Indicate choices (alternatives) |
| Loops (paths going backward) | Indicate repetition |
Example: Integer Literal
An integer literal consists of an optional sign (+ or -) followed by one or more digits:
+---+
+--->| + |---+
| +---+ |
| v
---->+----------->+----> [digit] ---+--->
| ^ |
| +---+ | |
+--->| - |---+ |
+---+ |
|
+---------------------+
|
v
+----> [digit] ---+---->
^ |
+-----------------+
This diagram shows that an integer may optionally start with + or -, must have at least one digit, and may have additional digits.
Example: If Statement
An if statement requires the keyword if, followed by a condition, then a block, with an optional else clause:
----> [if] ----> [condition] ----> [block] ---+---->
|
| +------+
+--->| else |----> [block] ----->
+------+
Backus-Naur Form (BNF)
Backus-Naur Form (BNF) is a formal notation for describing the syntax of a language using a set of production rules. Each rule defines how a non-terminal symbol can be replaced by a sequence of terminal and/or non-terminal symbols.
BNF Notation
| Symbol | Meaning |
|---|---|
<name> |
Non-terminal symbol (a construct to be defined) |
::= |
“Is defined as” |
\| |
“Or” (separates alternatives) |
| Terminal text | Actual characters/keywords that appear in the code (sometimes in quotes) |
BNF Examples
Defining a digit:
<digit> ::= 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
Defining a letter:
<letter> ::= a | b | c | ... | z | A | B | C | ... | Z
Defining an identifier (a name starting with a letter, followed by zero or more letters or digits):
<identifier> ::= <letter> | <identifier> <letter> | <identifier> <digit>
This is a recursive definition: an identifier is either a single letter, or an existing identifier followed by another letter or digit. This allows identifiers of any length.
Defining an integer:
<digit> ::= 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
<sign> ::= + | -
<unsigned_integer> ::= <digit> | <unsigned_integer> <digit>
<integer> ::= <unsigned_integer> | <sign> <unsigned_integer>
Defining a simple assignment statement:
<assignment> ::= <identifier> = <expression>
<expression> ::= <identifier> | <integer> | <expression> <operator> <expression>
<operator> ::= + | - | * | /
Defining an if statement:
<if_statement> ::= if <condition> then <block>
| if <condition> then <block> else <block>
Relationship Between Syntax Diagrams and BNF
Syntax diagrams and BNF are two different ways of expressing exactly the same information:
| Syntax Diagram Feature | BNF Equivalent |
|---|---|
| Sequence of boxes connected by arrows | Symbols listed in order on the right of ::= |
| Branching paths (choices) | The \| (or) operator |
| Loops (backward arrows) | Recursive rules (a non-terminal refers to itself) |
| Rounded box (terminal) | Terminal text in the rule |
| Rectangular box (non-terminal) | <name> in angle brackets |
Any construct that can be represented as a syntax diagram can also be expressed in BNF, and vice versa. BNF is more compact and suited to formal specification, while syntax diagrams are more visual and easier for humans to follow.
In the exam you may be asked to read a BNF rule and determine whether a given string is valid, or to write a BNF rule for a simple construct. Practice by working through examples: take a rule, pick a string, and trace through the rule to see if it can be derived. Remember that recursive rules are what allow repetition in BNF.