Edit this page Backlinks This page is read only. You can view the source, but not change it. Ask your administrator if you think this is wrong. ====== Introduction to Programming Paradigms ====== ===== Roadmap ===== In this lecture, we will examine a few interesting notions: * programming styles * programming paradigms * programming languages and the relation between them. The key insight in this lecture is that each programming style/paradigm introduces **constraints** in how programs should be written. These constraints may serve to: * improve the legibility/usability of the code by other programmers * allow for easy improvements/modifications * limit the occurrence of bugs, and easing finding and fixing them * allow code portability * improve performance In the first part of the lecture, we introduce and discuss four programs solving the sorting problem. Each program is written in a different programming language and in a different style. The choice of style for each solution highlights most of the aspects which will be discussed in detail in the programming paradigms lecture. We then introduce the concept of //programming paradigm// and relate it to programming styles and programming languages. ===== Style 1: "Cookbook" (C) ===== <code C> #include <stdio.h> #include <stdlib.h> #define SWAP(x,y) t = x; x = y; y = t; void insertion_sort (int* v, int sz) { int i, j, t; for (i = 1; i<sz; i++){ j = i; while (j>0 && v[j-1] > v[j]){ SWAP(v[j-1],v[j]) j--; } } } int main (){ int* v = malloc(sizeof(int)*7); v[0] = 1; v[1] = 9; v[2]=4; v[3]= 15; v[4] = 2; v[5] = 6; v[6] = 0; insertion_sort(v,7); } </code> ==== Characteristics ==== * **procedural**: the program uses //procedures// (e.g. ''insertion_sort'') or macro-definitions (''SWAP''). While these may be seem like //functions//, they behave rather more like //reusable cookbooks// than functions: * ''SWAP(x,y)'' is textually replaced by ''t = x; x = y; y = t;'' * ''insertion_sort'' does not return a sorted vector, but simply modifies the array in-place * **stateful**: the actual contents of the array is changed after ''insertion_sort'' has been called. There are (at least) //two states// for the array: before being sorted, and after. The programmer needs to take this into account, since sorting ''//erases//'' the initial ordering in the array. * **type/representation-dependent**: the sorting procedure relies on the data representation: an array. For instance, sorting a linked-list would require rewriting the procedure from scratch. * **fast**: is written in C, and does not use recursive function calls, with may be costly. ===== Style 2: "Pipeline" (Haskell) ===== <code haskell> type List = [Integer] insertion_sort :: List -> List insertion_sort [] = [] insertion_sort (x:xs) = insert x (insertion_sort xs) insert :: Integer -> List -> List insert x [] = [x] insert x (y:ys) = if x > y then y:(insert x ys) else x:y:ys </code> ==== Characteristics ==== * **functional**: the implementation relies on two functions ''insertion_sort'' and ''insert'', which are very similar to functions in the mathematical sense: there receive values as parameter and return values. * **stateless**: there is no concept of //state// or //state-change//: the effect of each function call is **only** to return a value. * **modular**: the implementation is separated in two components: ''insert'' is independent from ''insertion_sort'' * **pipeline**: the logic of ''insertion_sort'' is to recursively-solve a problem of smaller size, and then use ''insert'' to assemble the solution. * **slow**: using recursive functions may be costly. * **type/representation-dependent** (similar to Style 1) ===== Style 3: "Game of life" (Prolog) ====== <code prolog> swap([X,Y|Rest], [Y,X|Rest]) :- X > Y. swap([Z|Rest], [Z|Restp]) :- swap(Rest,Restp). bubblesort(L,R) :- swap(L,Lp), !, bubblesort(Lp,R). bubblesort(L,L). </code> ==== Characteristics ==== * **backtracking**: ''bubblesort'' attempts to swap two values, and if this is possible, it continues execution. In this particular example, we see a //controlled backtracking//, via the operator ''!''(cut), which prevents exploring the entire execution tree. More details about ''!''(cut) will follow. * **stateless & stateful**: during the execution of the program, goal satisfaction can trigger variables to be bound (instantiated) to values (which can be interpreted as a side-effect), however once a variable is instantiated it cannot be modified. * **slow**: uses guided backtracking which resembles recursive function call. * **declarative**: it is very close to the bubblesort description: //bubblesort swaps two variables ''X,Y'' such that ''X<Y'', as long as such two variables exist//. * **representation/dependent** ===== Style 4: "Abstractionist" (Scala) ===== <code scala> trait Sortable[T] { def sortWith (comp:(T, T) => Boolean) : Sortable[T] // comparator } trait LinkedList [T] extends Sortable[T] { override def sortWith (comp:(T, T) => Boolean) : LinkedList[T] } case class Void[T]() extends LinkedList[T]{ override def sortWith (comp : (T, T) => Boolean) : LinkedList[T] = this } case class Cons[T] (h: T, t:LinkedList[T]) extends LinkedList[T]{ override def sortWith (comp : (T, T) => Boolean) : LinkedList[T] = { def ins (x:T, rest:LinkedList[T]) : LinkedList[T] = rest match { case Void() => Cons(x,Void[T]) case Cons(h,t) => if (comp(x,h)) Cons(x,rest) else Cons(h,ins(x,t)) } ins(h,t.sortWith(comp)) } } object Sort { def main(args: Array[String]): Unit = { val l = Cons(1,Cons(9,Cons(4,Cons(15,Cons(2,Void()))))) println(l.sortWith(_ > _)) } } </code> ==== Characteristics ==== * **type-independence**: list sorting is implemented **independently of the type** of elements contained in the list. In order to sort a list, it is sufficient to have a comparison function, here denoted as: ''comp : (T, T) => Boolean''. Type independence is technically realised here via: * **parametric polymorphism** or **genericity**: the ability to define entities (e.g. classes) parameterised with respect to a type T which is established at runtime; * **ad-hoc polymorphism** or **function overriding**: the actual implementation of ''sortWith'' is established at runtime, depending on the actual type of the object on which it is called (Void or Cons). * there is another form of type independence: ''sortWith'' is designed to work on any sortable entity, not only lists * **functional**: the actual sorting is very similar in implementation to the //pipeline style// * **slow**: due to recursivity * **stateless** ===== Programming Paradigms ===== We introduce three //de-facto programming styles//, which we call **paradigms**, by stating some key features. What is the difference between a //paradigm// and a //style// (e.g. any of the 4 above ones)? There is no formal definition for //paradigm// or //style//. However: * //paradigms// are rather related to //programming language design//. More precisely, a programming language will (preponderently) implement or adhere to a //paradigm//. However, nowadays, this no longer happens (Scala is an example). * //styles// are more specific to programmers, and may blend several aspects of a paradigm. Programming paradigms: * **imperative**: programs specify instructions which iteratively change state. It is very close/similar to the machine architecture. It is generally otherwise unconstrained. * **object-oriented**: programs consist of object definitions (classes). Programming is focused on relations between objects, and their traits (methods they implement). Apart from this enforced structure, programming is similar to the imperative style. * **functional**: programs consist of functions in the mathematical sense. Functions can only return values, no other modifications are allowed. Programs are stateless, and sometimes this is enforced by the programming language (e.g. Haskell). * **logic programming**: programs consist of rules of the form '' A => B'' (if A is true, then B must be true). The input of a program is a goal (expressed in predicate logic). Program execution attempts to satisfy a goal, e.g. it is a **proof of the goal**. In this sense, a program can be seen as a set of axioms. For this reason, Prolog - an example of a logic programming language is also called a **theorem-prover**. * **rule-based**: very similar in appearance to logic programming, but with different inference styles. ===== Do styles conform to paradims? ===== * **imperative**: The //cookbook// style is definitely **imperative**, as all its characteristics conform to it; * **object-oriented**: The //abstractionist// style is object-oriented. * **functional**: The //pipeline// style is functional. Also, the //abstractionist// style is functional. This is obvious in the actual sort implementation. * **logic-programming**: The //game-of-life// style does conform to the paradigm. ===== Do programming languages conform to paradigms? ===== * **C/C++**: ''C'' is definitely imperative. It is free of constraints, and very flexible. ''C++'' is object-oriented - it adds typing as a constraint (this statement will be discussed in more detail in the following lectures), but retains flexibility. It is possible for programmers to use a functional style in C/C++ albeit with some limitations, especially in the form of the resulting code. * **Java**: is definitely object-oriented and still preserves traces of ''imperativism'', especially with regards to primitive types (which are not objects). As with **C/C++**, a functional style is possible, but, more than C\C++ may seem forced: for instance, we cannot pass functions as parameter, however we can pass objects which implement certain methods (see, for example, the Command design pattern). * **Scala**: is object-oriented and functional at the same time. It is possible to program in an imperative style in Scala (for instance, it allows for stateful programming like function side-effects). It is also possible to write "strictly-object-oriented" programs (a la Java) as well as functional programs. Most programs will however blend the two. * **Scheme (or Lisp)**: is functional, however it is possible to program imperatively in such languages. However such programs are clumsy and rare. It is not possible to write object-oriented-style programs in these languages, as they do not support advanced typing (this will be discussed further on). * **Haskell**: is functional and **pure**. This latter term deserves a more elaborate discussion which will follow. In short, in Haskell it is not possible to program in another style but functional. Haskell also supports advanced typing (reliant on Abstract Datatypes) and ad-hoc and parametric polymorphism. * **Prolog**: is the de-facto logic programming language. It supports some restricted form functional programming (function definition). There is no means for defining user types. * **CLIPS**: is a rule-based programming language designed at NASA. Nowadays CLIPS is no longer widely-used, however elements of CLIPS design are incorporated in many domain-specific languages (i.e. languages which are deployed for solving specific kind of problems) from AI. A good example are rule-based languages used for reasoning on ontologies. ===== References ===== * [[https://www.crcpress.com/Exercises-in-Programming-Style/Lopes/p/book/9781482227376 | Exercises in programming style ]] * [[http://edge.cs.drexel.edu/regli/Classes/Lisp_papers/worse-is-better.pdf | Lisp: Good News, Bad News, How to Win Big]]