------------------------

Harvey Mudd College
Computer Science 131
Programming Languages
Fall Semester 1999

Lecture 01

------------------------

  • Administrivia / Structure of the Course / Grading

  • Why Are We Here?

  • The Dominant Programming Paradigms:

  • First Class Types (Values) Can Be:

  • A Program to Append Two Lists in 4 Languages

  • Some Videos of Pictorial Janus Programs Executing

    ------------------------

    Why Are We Here?

    Many of you may be asking yourself ``Why am I here?'' The easy answer for most of you is that the course is required for the major. But why is that?

    Well, this is a course about programming paradigms. So far most have you have (with the exception of a few REX assignments in CS60 and CS80) only programmed in the paradigm of imperative programmming. Imperitave programming is and always has been the dominant paradigm, and is typified by languages like Fortran, Pascal, Ada, C, C++, and many others.

    If that's all you've seen, then you may well think that that's what programming ``is''. But in fact there are many other programming paradigms (or styles) that have developed over the years.

    The other major paradigms are: Functional Programming (which is almost as old as imperative programming, dating back to 1960), Logic Programming (early 1970's), and Object-Oriented Programming (which in it's present form dates back to the mid 70's).

    A language is classified as belonging to one of these families based on the view it takes of describing the solution to a problem, as well as what kind of data structures it can easily describe and handle and how it treats that data.

    Imperative programming is, of the three, the most closely tied to the underlying hardware. Problems are solved by giving a series of commands that say what to do. The first-class data types are usually pretty limited and closely linked to the hardware. In particular, if you want to deal with complex types, in particular dynamic structures like lists and trees, you must work directly with pointers (which are really just machine addresses) and do your own memory management. Variable names are just a mnemonic way of refering to memory addresses.

    I have a friend who describes C as a really nice macro language for accessing and setting addresses. This really isn't much of an exageration. The imperative languages were a direct incremental step in the progress up from machine language, and assembly language. In my undergrad architecture course we learned to hand assemble C code to VAX assembler and it was really a pretty trivial task.

    In Functional programming the model of computation is much more abstract. Solutions are described in terms of computations that take smaller values (or the result of sub-computations) and combine them together into larger values or objects. Variable names have a very different meaning in Functional programming. They are just a conveniece for giving names to values, not memory addresses. Functional languages generally have a much richer notion of data type, and in particular do automatic memory management for dynamic structures. Modern functional languages also support the notion of higher-order programming in which functions can be freely passed as parameters.

    In Logic Programming the model of computation is not. That is, in theory, when you write a logic program you are not describing a computation, but rather a set of properties of data and relationships that hold between different data. Computation is just a side effect of proving some relationship holds. Variables have the same semantics they have in logical expressions, as quantified names that stand in for members of some set.

    Logic (not Logical) Programming and Functional Programming are often lumped together as Declarative Programming because they both share the property of describing (declaring) some formal mathematical mechanism.

    Finally, Object-Oriented programming is usually thought of as a sub-Paradigm of imperative. You still give instructions. But now, rather than thinking in terms of functions and procedures that do things to data, you think in terms of data which respond to requests to do things.

    ------------------------

    First Class Data Types

    One term that came up a couple of times in this discussion is ``First-Class Data Type''. We'll use this expresssion alot in distinguishing different languages. We'll say that a language views a data type as first class if values of that type can be:

    For instance, in C, the scalar types are first class, but most other types are second class citizens. In general, manipulating larger values requires the use of pointers.

    ------------------------

    A Program to Append Two Lists in 4 Languages

    OK, I'd like to take the rest of the class to go through one short example that I hope will clarify this notion of paradigm for you. What I've done is write a function that will take two lists and return a new list that is made of the elements of the first list followed by the elements of the second list. Note this will be a new list, the original lists will be left unchanged.

    Now I've written this program in 4 different languages, each one from a different paradigm.

    ------------------------

    A C Program to Append Two Lists

    struct node *nalloc(void)
    {
       return((struct node *) malloc(sizeof(struct node)));
    }

    struct node *copy(node *n) { node *temp = nalloc(); temp->data = n->data; return temp; }

    struct node *append(node *l1, node *l2) { node *temp = NULL;

    if (l1) { temp = copy(l1); temp->next = append(l1->next,l2); } else if (l2) { temp = copy(l2); temp->next = append(l1,l2->next); } return temp; }

    Because C has no built in notion of list, we build the list out of structures that store a data element and a pointer to another such structure. The copy function takes a pointer to a node and returns a pointer to a duplicate copy of the node. To append two lists, we check to see if the first is empty. If so, we make a a copy of the second list and return it. Otherwise, we copy the first node of the first list and attach to it the result of appending the rest of the first list and the second list.

    (Note that this program won't actually work. The node defininttion has been omitted. To make the program work we will have to supply a node definition, and, in particular, specify the type that is stored in each node's data field. At that point the program will work just for lists of that type.)

    ------------------------

    A Standard ML Program to Append Two Lists

    fun append nil l = l
      | append (h::t) l = h::(append t l);
    

    The Standard ML program stands in stark contrast to the C program. Most importantly, lists are a built-in type, so no support code is needed. This program is in two parts, each describing a single case. The first case says that if the first argument list is empty, return a copy of the second list (all parameter passing and function returns are by copy in ML). The second case says that if the list is not empty, return a list that has the first list's head as its head, and the result of appending the second list to the tail of the first list as it's tail.

    ------------------------

    A Prolog Program to Append Two Lists

    append([],K,K).
    append([X|K],L,[X|M]) :- append(K,L,M).
    

    The Prolog program looks a great deal like the ML program, but is subtly different. Here, append is a relationship that either holds or doesn't between three lists. It holds exaclty when the thrid argument is the list that results from appending the second to the first. The program describes the circumstances under which it holds. In particular, if the first list is empty and the second and third lists are the same, then it holds. In addition, if it holds for a three lists then it holds for the same three lists but with a given element added to the front of the first and third lists.

    ------------------------

    A Pictorial Janus Program to Append Two Lists

    A Pictorial Janus Program to Append Two Lists

    The Pictorial Janus is the oddest, but in fact it is much the same as the ML program. A Janus program is closed curve. Its input and output channels are represented by figures on the outside of the curve. How the program behaves is determinde by the shape of clones of itself contained inside.

    This program has two input ports on the left and an output port on the right. The action of this program is to attach a list that consist of the list attached to its upper input port appended to a copy of the list attached to its lower input port to the input port of whatever its output port is attached to.

    The program has two cases much like the ML program. The base case is the upper one. It says that if the lower input port is connected to a node that has no data attached to it (that is if the closed curve of the node has no input port on its belly) then the list attached to the top input port should be attached to the back of whatever is connected to the programs output port. The second case is for when there is data attached to the node at the bottom port. In that case the data is copied to a new node which is transfered along the output port. Whatever is attached to the rear of the input node is then moved to be connected to the programs lower input port.

    Note that the base case has no clone inside it, so when it is selected the program terminates. In contrast, the other case contains a complete clone of the overall program, so it is recursive. When it executes the program is replaced by an identical copy.

    ------------------------

    Some Videos of Pictorial Janus Programs Executing

    Below are links to Quicktime movies of several Pictorial Janus programs executing. (The machines in the HMC Academic Computing labs are configured for Netscape to run a Quicktime viewer automatically when the movies are downloaded. If you are not in an AC lab and need a viewer, click here.)

    ------------------------

    A Program to Append Two Lists

    This is the program described above in action.

    append.mov (214k)
    append.mov

    ------------------------

    A Race Between Two Append Programs

    This is a race between two copies of the append program. It shows that while append is associative (that is, append(a,append(b,c)) = append(append(a,b),c)) in a parallel setting, one version may be easier to compute than the other.

    apprace.mov (340k)
    apprace.mov

    ------------------------

    A Program to Compare the Length of Two Lists

    This program takes two lists as input and determines which (if either) is longer.

    compare.mov (95k)
    compare.mov

    ------------------------

    A Naive Program to Reverse a List

    This program reverses the list attached to its lower input port. It works by reversing the tail and then attaching the head to the end of that result. This is known as the naive reverse algorithm. While it is the most natural functional definition, it is inefficient running in O(N^2) time.

    Notice that the recursive case for the program includes not just a copy of itself, but a copy of the append program, since one of these is needed for each recursive call.

    naiverev.mov (417k)
    naiverev.mov

    ------------------------

    A Smart Program to Reverse a List

    This program reverses the list attached to its lower input port. It works by maintaining a register which it pushes the front of the input onto. Unlike the naive reverse program it does not need to call append. Hence it runs in O(n) time.

    reverse.mov (187k)
    reverse.mov

    ------------------------

    A Janus Implementation of a Primitive Memory Cell

    This program implements a primitive memory cell. Its input is a stream of read/write messages.

    memory.mov (170k)
    memory.mov

    ------------------------

    A Janus Implementation of a Priority Queue

    This program implements a priority queue. Its input is a stream of enqueue/dequeue messages. Note that the data that accompanies an enqueue message may not have reached the message yet. This is not a problem, whatever was supposed to be in the message will be forwarded to any object that subsequently tries to dequeue it.

    queue.mov (293k)
    queue.mov

    ------------------------

    This page copyright ©1999 by Joshua S. Hodas. It was built on a Macintosh. Last rebuilt on Tuesday, August 31, 1999 at 9:39:10 PM.
    http://cs.hmc.edu/~hodas/courses/cs131/lectures/lecture01.html