COMP2100/2500 in 2008

Lecture 27: Comparison of Programming Paradigms

Summary

A discussion of object-oriented, procedural, scripting and other classes of programming languages; what they're good for, what they're not...

Outline

  1. Procedural programming

  2. Modular programming

  3. Object-oriented programming

  4. Comparison between procedural and object-oriented languages.

  5. Generic programming

  6. Aspect Oriented programming

  7. Scripting

  8. Functional programming (in Haskell)


Introduction

In this course we have used languages of three fundamentally different types:

There are other, radically different, programming paradigms.


Procedural programming

Decide which procedures you want;
use the best algorithms you can find.

In this paradigm the focus is on processing — the algorithm needed for performing the desired computation. Languages support it by providing facilities for passing arguments to functions and returning values from functions. The necessity to distinguish between the functions results in a primitive type system etc.

The need to construct larger programs, reusable components, and so on (the needs of constructing larger scale software) lead to primitive forms of modules (separate files, libraries, packages).

Procedural Languages Include:

Characteristic features of Procedural languages:


C Programming Example

Header: counter.h
typedef int counter;

/* reset to zero */
extern void 
reset(counter *c);

/* count another one */
extern void 
increment (counter *c);
Implementation: counter.c
#include "counter.h"
/* reset to zero */
void reset(counter *c) {
  *c = 0;
}
/* count another one */
void increment(counter *c) {
  *c = *c + 1;
}

Modular programming

Decide which modules you want;
partition the program so that
data is hidden within modules.

This is the data hiding paradigm. In addition to qualities of procedural languages, it groups together procedures with related data (one step away from the class concept). Techniques for designing procedures now have modularization constraint.

Like procedural definitions, data definition capability should also be available.

Decide which types you want;
provide a full set of operations
for each user-defined type.

When interface and implementation are decoupled we get the Data Abstraction.

Modular Programming Languages include:

Important feature: separate compilation. This is partly a language issue (separate headers and bodies of procedures, for example) but it is mainly an issue of taking advantage of a particular language implementation. This is of great practical importance to the programmer, and a challenge to the program designer and compiler writer. Separate compilation motivates the creation and use of build tools like make and Ant.


Object Oriented languages

The module definition is rigid, cannot be adopted to a changeable surrounding (rest of the program which uses it). This flexibility (commonality) arises if to replace an abstract module with a class hierarchy.

Decide which classes you want;
provide a full set of operations for each class;
make commonality explicit via inheritance.

Object Oriented Languages Include:

Characteristic Features of OO languages:


Java and Eiffel: Differences within a Paradigm

class

   COUNTER

creation
   reset

feature {ALL}

   number: INTEGER
		 -- number counted

   increment is
		 -- count another one
	  do
		 number := number + 1
	  end

   reset is
		 -- reset to zero
	  do
		 number := 0
	  end

end -- class COUNTER
public class Counter {

  private int num;

  public Counter() {
	num = 0;
  }

  /** number counted */
  public int number() {
	return num;
  }

  /** count another one */
  public void increment() {
	num++;
  }

  public void reset() {
	num = 0;
  }
}

Java and Eiffel: Feature Comparison

Major feature comparison:

Feature Eiffel Java
Multiple Inheritance + -
Generic Types + +
Design by Contract + -
Overloading - +

Java makes a distinction between the concepts of subtyping (using the implements keyword) and inheritance (using the extends keyword). A class can be a subtype of another class, without inheriting its implementation. Eiffel doesn't have this distinction.

Minor feature comparison:


Object Oriented and Procedural: Feature Comparison

Feature Object Oriented Procedural
Abstraction through subtyping + -
Reuse through Inheritance + -
Encapsulation of data + -*
Automatic Memory Management +** -

*: is supported by modular languages
**: not supported by all OO languages

But the news is not all good for OO languages:


Object-oriented languages are not completely replacing procedural

Procedural languages remain the more appropriate choice in application areas where:

For example:


Generic Programming

Decide which algorithms you want;
parametrize them so they work for a
variety of types and data structures.

Generic Programming is a programming method that is based on finding the most abstract representation of efficient algorithms without compromising its efficiency. (Ideologically close to mathematical phenomenon that an abstract set of axioms, eg, for geometry, can have multiple realisations resulting in different mathematical theories). Generic programming assumes that there are fundamental computational laws that govern the behaviour of software components and that it's possible to design interoperable modules based on such laws. These laws can also guide the software design.

Generic programming emphasizes the difference between types (primitive and user defined — modules, ADT and classes) and data structures (collections, like stacks or lists). Just as types are generalised in ADT and classes, the data structures are generalised as containers. A container (container class) is a class holding a collection (a set in the general mathematical sense) of elements of some type. (This can be defined using a language feature known as templates, or generics).

Algorithms can be parametrised by containers (e.g. algorithms that will sort, copy or search each concrete type of container without presuming is it a list or an array); such algorithms are known as generic algorithms, and the style of programming is known as generic programming.

C++, Ada, and Java allow for efficient implementation of this paradigm. In Java Java 1.5 Generics and Ada the feature is type safe, and does not require unsafe type-casting, but each generic class is implemented as a single class. In C++ the implementation may require a new class to be generated at compile time. More specialised generic programming languages are yet to be developed (although Alexander Stepanov promised this ten years ago). The presence of templates feature in a language (C++, Eiffel, Java) does not guarantee that the generic programming can be effectively realised using the language syntax. When implemented (eg, as C++'s Standard Template Library), it allows k generic algorithms which operate on j data structures built from i different data types to be presented as only (j+k) software components (instead of i*j*k), which can be as useful as ordinary API, and their performance will not be compromised by their generality.

One should spend at least a semester on learning the generic programming. See the Java Generics Tutorial .


Aspect Oriented Programming

This is a relatively new paradigm.

OO, being a very useful concept, has limitations. Often requirements do not decompose into behaviour which can be captured by a single entity — class. Global constraints, behavioural features which permeate the entire system, distinguishing and segregating different behavioural properties (concerns), application of domain specific solutions — these and other problems cannot be effectively addressed by OOP.

AOP is based on the idea that computer systems are better programmed by separately specifying the various concerns (here, a goal, concept, property, or area of interest) of a system and some description of their relationships, and then using the mechanism (which can be implemented as the language feature, or through code-generating tools) of weaving (or, composing) those aspects together in a coherent program. Aspects can be security, persistence, synchronization, transaction, caching/buffering, and so on. OOP strives to find commonality between classes and reorganise them into a hierarchy. AOP seeks to organise the scattered concerns as first-level programming concepts (like classes are in OOP), and remove them horizontally from the module/object structure.

One implementation of AOP — AspectJ programming language. As a language it contains two specific constructs allowing it to realise the AOP tasks: Implementation of concerns — mapping individual requirements into code so that a compiler can translate it into executable code (this can be emulated by standard languages, like Java and C++; but it will defeat the aspired goal of AOP to improve the technology etc)), and Weaving rules specification — how to compose independently implemented concerns to form the final system (has no prototype feature, should be added as extension, or otherwise).

For implementing the first goal AspectJ introduced the constructs of aspect (which look like a class). To implement the second goal, the construct pointcut is used.

I am very new to AOP and AspectJ. First impression, this is a very large and far-reaching solution of the problem for which the Visitor DP was used. Interested? There are already several books on the subject. You can start by checking this tutorial. Much more stuff is available on Eclipse's AspectJ web site. One should spend at least a semester on learning the AO programming.


Scripting

Scripting languages include:

Characteristic features of scripting languages:


When Would You Use a Scripting Language?

When the task

Scripting languages (particularly Perl) are now used extensively to generate dynamic web pages.


Other Programming Paradigms

There are other, radically different, ways to program:


Introduction to Functional Programming in Haskell

Programming languages such as C/C++/Java/Python are called imperative programming languages because they consist of sequences of actions. The programmer tells the computer how to perform a task, step-by-step. Functional programming languages work differently. Rather than performing actions in a sequence, they evaluate expressions.

Data-types are defined separately from the functions that manipulate them. A generic stack type is defined as follows:

data Stack t = Empty | Push t (Stack t)

A stack is defined to be either the constant stack Empty, or the result of the constructor Push that takes an element and a stack. The following are all valid stacks:

Functions can now be defined by pattern matching on the different kinds of stack:

is_empty :: (Stack t) -> Bool
is_empty Empty    = True
is_empty (Push x s) = False
 
depth :: (Stack t) -> Int
depth Empty      = 0
depth (Push x s) = 1 + depth s

Function definitions need not be exhaustive: top and pop are not defined for empty stacks. (This avoids having to state preconditions like in Eiffel, write error-handling code or special cases...)

top :: (Stack t) -> t
top (Push x s) = x

pop :: (Stack t) -> (Stack t)
pop (Push x s) = s

Note that pop is not a procedure that modifies an existing stack, instead it is a function that makes a new stack from an old one. (We did something like this in Stack Version 3.)


Expression Trees in Haskell

data Expression = Constant Int |
                  Sum Expression Expression |
                  Product Expression Expression

value :: Expression -> Int
value (Constant n)  = n
value (Sum a b)     = (value a) + (value b)
value (Product a b) = (value a) * (value b)

toStringInfix :: Expression -> String
toStringInfix (Constant n)  = show n
toStringInfix (Sum a b)     = "(" ++ toStringInfix a ++
                              "+" ++ toStringInfix b ++ ")"
toStringInfix (Product a b) = "(" ++ toStringInfix a ++
                              "*" ++ toStringInfix b ++ ")"

toStringPostfix :: Expression -> String
toStringPostfix (Constant n)  = show n
toStringPostfix (Sum a b)     = toStringPostfix a ++ " " ++
                                toStringPostfix b ++ " +"
toStringPostfix (Product a b) = toStringPostfix a ++ " " ++
                                toStringPostfix b ++ " *"

More on Haskell: everything is a type

-- Definition of the addition function in the Curried form
add :: Num a => a -> a -> a
add n m = n + m
-- Partial application: (add 3) is a function of type Int -> Int
-- which adds 3 to its argument

-- function definition with guards
fib :: Int -> Int
fib n  
	 | n<2 = 1
	 | otherwise = fib(n-1) + fib(n-2)


-- this is a definition of the function "map"
map :: (a -> b) -> [a] -> [b]
map f (x:xs) = f x : map f xs
-- ':' is the list constructor

-- an example of usage of the map
code :: String -> [Int]
code x = map ord x

-- here ord is the function
ord :: Char -> Int

Evaluation of Functional Programming

The expression tree presented above offers equivalent power to a Visitor pattern style implementation of expression trees in OO language.

Strong features of functional programming:

Modularity is very important for successful programming. Languages which aim to improve productivity must support modular programming well. But new scope rules and mechanisms for separate compilation are not enough — modularity means more than modules. The ability to decompose a problem into parts depends directly on the ability to glue solutions together. To assist modular programming, a language must provide good glue. Functional programming languages provide two new kinds of glue — higher-order functions and lazy evaluation.


The Big Picture

No programming language is perfect. Fortunately, a programming language does not have to be perfect to be a good tool for building great systems. In fact, a general-purpose programming language cannot be perfect for all of the many tasks to which it is put. What is perfect for one task is often seriously flawed for another because perfection in one area implies specialization...
Not everything can be expressed directly using the built-in features of a language. In fact, that isn't even the ideal. Language features exist to support a variety of programming styles and techniques. Consequently, the task of learning a language should focus on mastering the native and natural styles for that language — not on the understanding of every little detail of all the language features.
In practical programming, there is little advantage in knowing the most obscure language features or for using the largest number of features. A single language feature in isolation is of little interest. Only in the context provided by techniques and by other features does the feature acquire meaning and interest.

(from B. Stroustrup's "The C++ Programming language".)

There are a variety of significantly different programming paradigms:

By the time you graduate you will have had more exposure to other language families. By that time you should be able to consider what programming paradigm best suits a given problem.

Copyright © 2006, 2008 Jim Grundy, Ian Barnes & Chris Johnson, The Australian National University
$Revision: 1.1 $ $Date: 2008/05/28 04:42:55 $ $Author: cwj $
Feedback & Queries to comp2100@cs.anu.edu.au