The four big topics
You'll have the opportunity to learn a lot of stuff in this course, and that
stuff can be grouped into about four different categories, and we can give
each one of those categories a name, just to help us keep it all organized
in our minds.
Knowledge representation and processing
The first category of stuff is named after the course itself--"knowledge
representation and processing." Here we'll be working with ways of
organizing and using information which are different from the more
traditional and linear approaches such as records, files, tables, and the
like. The organizational techniques come under the heading of "hierarchical
data structures," and they include things like lists, trees, and relational
networks. The methods for processing data stored in such structures fall
under the heading of "search techniques," and we'll see some dumb, brute-force
search methods, as well as some smarter search methods. The methods for
knowledge representation and processing that we'll cover in the course have a
long history of use in the field of artificial intelligence (AI), but over
time these same techniques have crept into other subareas of computer science,
so even if you have no interest in AI, this stuff will all still be useful to
you in the long run.
Oh, one other thing about this first category of stuff -- all these techniques
for organizing and processing information are easily tackled using recursion,
so you'll see what you might think is an overly zealous emphasis on recursive
techniques during the first few weeks. We're just trying to get you to use
the best tools for a given job.
Functional programming
The second category of stuff that we'll learn about is a programming paradigm
called "functional programming." This paradigm is a contrast to the
"procedural" (AKA "block-structured" or "imperative") programming
that you've been exposed to in CS 1501 and 1502, or any other introductory
course in Pascal, FORTRAN, C, or similar programming languages. This
paradigm also stands in contrast (although perhaps less so) to the
"object-oriented" paradigms that you are exposed to in CS 2390.
In the functional programming paradigm, we look at problems as things to be
decomposed into successively smaller sub-problems. The smallest sub-problems
are solved by small procedures which we call functions. In this context,
functions are procedures which access only the parameters passed to them
(i.e., no global variables...yipes!), and always return the same result for
the same input parameters. Thus, functions in this paradigm work like those
mathematical functions you learned about in your math classes.
The results of these small functions are then combined or synthesized by
other functions, which return those results up to other functions, and so on,
until the desired result is found. There are some nice advantages to writing
computer programs in this way:
1. With a little practice, they're pretty easy to construct.
2. They're especially easy to read.
3. They're especially easy to debug.
4. If you were really energetic, you could prove some properties, such
as correctness, about them. In other words, such programs are
mathematically "clean". (We won't have time to explore this
much, if at all, but when we move to semesters this topic will
get much more exposure.)
In addition to everything mentioned so far, pure functional programming
avoids the use of variables and assignment, and this will surely drive most
of you nuts for a while. But there are good reasons for staying away from
this, which we'll talk about later in the course. However, it's also the
case that many problems don't lend themselves to an easy solution by adhering
to the functional programming paradigm, so in the last third of the course
we'll relax some of these functional programming prohibitions.
Software design
The third category of stuff falls under the category of good software design
principles. Those of you who have been playing with computers for a number
of years may have some idea that things like program size (smaller = better)
and program speed (faster = better) are the important attributes to look for
when evaluating program quality. That was certainly true many years ago when
computers were very expensive. Today however, computers are cheap and getting
cheaper, while at the same time programmers are getting more and more
expensive. So the big question in software development is no longer just how
to best conserve computer resources, but also how to best conserve human
resources. Or, in other words, programmer efficiency is at least as
important, if not more so, than program efficiency.
But what exactly is programmer efficiency? Is it just getting the code
written as quickly as possible? That's certainly part of it, but the biggest
expense in software development is incurred in debugging, maintaining, and
revising code. Thus while we engage in the art or science of software
development, we should be striving not only for ease of design and
implementation, but also for qualities like readability, debuggability (is
that a word?), maintainability, revisability (how about that one?), and
reusability. We can do this by working toward controlling the complexity of
our programs, and we'll talk a lot about controlling software complexity in
this course. Applying principles of functional programming is one step
toward controlling software complexity.
So if you think your main concern as a programmer is "how do I save some
bytes?" or "how do I shave some cycles off my execution time?", you need to
put those ideas away until you come across some case where those issues are
important. Don't write code for the benefit of the computer; write for the
benefit of the people who build it, test it, evaluate it, debug it, adapt it,
and learn from it, and heed this quote from a couple of really smart guys:
"...a computer language is not just a way of getting a computer to
perform operations ... it is a novel formal medium for expressing
ideas about methodology. Thus, programs must be written for people to
read, and only incidentally for machines to execute."
(Abelson and Sussman)
Another really smart guy put it this way:
"The promise...of programming languages...has yet to be fulfilled.
That promise is to make plain to computers and to other programmers
the communication of the computational intentions of a programmer
or a team of programmers, throughout the long and change-plagued life
of the program. The failure of programming languages to do this
is the result of a variety of failures of some of us as researchers and
the rest of us as practitioners to take seriously the needs of
people in programming rather than the needs of the computer and the
compiler writer. To some degree, this failure can be attributed to
a failure of the design methodologies we have used to guide our
design of languages, and to a larger degree it is due to our failure
to take seriously the needs of the programmer and maintainer in
caretaking the code for a large system over its life cycle." (Gabriel)
The latter statement is a bit more ominous than the former, but the
message you should take away from all this is clear: more and more,
folks in computing are realizing that programs are written for the
benefit of people, not for the benefit of computers. If you don't
clue into this soon, you're gonna be one of the first ones against
the wall when the revolution comes. (That should be familiar to
Hitchhiker's Guide to the Galaxy fans.)
LISP
The fourth and final category of stuff you'll learn about in this course is
the Common LISP programming language. Well, everything was going along just
fine until we mentioned LISP, eh? You may have heard ugly things about LISP:
there are too many parentheses, it's only for artificial intelligence, it's
hard to learn, and so on. If you heard these myths at Tech, you probably
heard them from people who had the language jammed down their throats in one
or two weeks after having studied Pascal for two years. But these are in fact
just myths, as we hope you'll see by the end of the quarter. LISP will be the
language used in this course, and there are lots of good reasons for doing so:
1. LISP started out as a purely functional programming language, and
still retains much of that flavor. At the very least, it still encourages a
functional programming style, and that's going to help us in this course.
2. LISP is especially good for processing complex hierarchical data
structures using recursion. In fact, it is so surprisingly good that those
of you who have learned something about processing these sorts of data
structures using pointers will find it hard to believe that it could be so
easy (LISP does all the pointer management for you).
3. LISP is the second oldest general purpose programming language in use
today (FORTRAN is the oldest). As such, LISP has the benefit of lots of
people who have devoted lots of years to making it better. This shows up in
the form of some of the most sophisticated programming tools (editors,
debuggers, etc.) in existence.
4. Most programming languages are designed so that compilation is
efficient, often at the expense of things like consistency and usability.
LISP on the other hand was designed (and continues to evolve) to maintain
mathematical consistency and usability, sometimes at the expense of making
the computer work a little harder.
5. Because of its age, LISP is incredibly well documented.
6. LISP has a uniform simple syntax, and there is no distinction between
program and data. (The latter point is not important now, but it will
become more important as the course progresses.)
7. In its "normal" state, LISP is an interpreted language (or at least
it acts like one). That is, code is executed immediately, and no separate
compile stage is necessary (although compiling LISP code is both possible
and desirable). Interpreted languages are highly interactive and give
immediate feedback, making LISP an increasingly popular tool for the
rapid prototyping of complex software systems such as new language
compilers, graphics systems, and user interfaces.
8. LISP is highly extensible. In other words, it's very easy to build
new languages on top of LISP by adding functions. This feature is in large
part responsible for LISP's longevity. As new programming paradigms emerged
over time, many programming languages fell by the wayside, while LISP was
extended to accommodate the new paradigms.
9. The Common LISP standard makes LISP programs very portable.
Portability lets you migrate an application from one platform to another
without having to rewrite bunches of code. This is an important thing for
you budding software entrepreneurs to remember.
10. LISP has automatic storage management. Memory is allocated as it's
needed on the fly, and memory that is no longer needed is "garbage collected"
dynamically as well. There's seldom a need to "declare" data structures in
advance.
11. LISP also has dynamic typing, which means you no longer have to make
data type declarations. LISP does type-checking and necessary conversions at
run-time. This feature and the previous one put LISP in a category of
programming languages now called "dynamic languages." Newer dynamic
languages, such as Dylan, are generating a lot of interest in the world of
computing; LISP is sort of the granddaddy of dynamic languages.
12. Finally, LISP is the favored language for artificial intelligence
work, at least in the United States, so if you're interested in AI, this is
the language to know.
Conclusion
If you were to go back over that list of reasons why LISP is a really neat
thing, you may see a common thread: dynamic typing, automatic storage
management, extensibility, interpreted code, extensive programming tools and
the like are all computationally expensive, but they all make the programmer's
life easier. In the long run, those features and others should combine to
allow programmers to develop better software in less time, albeit possibly at
the expense of asking the computer itself to work harder. That's a trade-off,
to be sure, but it's a good one, and it's one you should be thinking about all
the time as you develop software, regardless of the particular programming
language or paradigm you happen to be using.
The key point here is this: The goal of everything you do as a computer
scientist is to get the computer to adapt to the user (and user in this
context includes you, the programmer), not vice versa. Tools are supposed to
make their users' jobs easier, not harder, and computers and their programming
languages are certainly tools. You don't want users to become slaves to the
computer; you want to make the computer do the work. That's why the computer
was invented in the first place.
Copyright 1998 by Kurt Eiselt. All rights reserved.
Last revised: September 27, 1998