During my sophomore year of college at Carnegie Mellon, I got a job in the psychology department as an assistant programmer for a professor working on neural nets. The language being used was LISP, and the first order of business was to learn it. I even got paid while I was learning, which I thought was pretty amazing at the time!
I would say working with LISP over the next several years had a huge impact on my life as a programmer — moreso than working with any other language. So I’d like to devote today’s post to the wonderful world of LISP….
LISP (which stands for LISt Processing) is an example of a functional programming language. I talked a little about procedural programming languages earlier, so today I’ll describe some functional aspects of LISP. Of course there is no substitute for just diving in and learning it yourself….
Why did I spend so many years programming in LISP? I worked a few hours a week as a math tutor, so my interest in teaching was already blossoming. In the Spring of my sophomore year, my roommate heard about a summer program for talented math and science students, and suggested I look into it. Computer science was one of the courses, and the language being taught was — you guessed it — LISP. I could hardly believe it!
So I spent ten summers teaching LISP at the Pennsylvania Governor’s School for the Sciences. I started as a teaching assistant, but eventually became the instructor for the lab course and director of student projects in computer science when I was in graduate school at CMU.
The reason LISP was the language taught, incidentally, was because the students were a particularly bright bunch — only the top two students from each county in the state were accepted. Many of them already had programming experience — but BASIC and PASCAL were the popular languages at the time for high school students. It was thought that LISP would level the playing field, as it were. I can’t recall that any student ever had experience with LISP before.
What makes LISP so different? Certainly its structure — everything is a list. Even function calls! So to add two numbers, you’d write
(plus 3 4).
The first element of this list is the function name, then the arguments follow later.
How do you distinguish lists from function calls? Consider the function call
(append ‘(1 2 3) ‘(4 5 6))
This does what you’d expect — concatenates two lists together. The single quote indicates the list represents a pure data structure; you aren’t applying the function “1” with arguments “2” and “3.” The quote suspends evaluation of the list.
So data structures and function calls look the same. But here’s what’s neat. You can write commands like
(eval ‘(plus 3 4)).
The function eval evaluates the list as a function call. So this is equivalent to (plus 3 4). Now think about this for a moment. It’s like taking a string like “3+4” in a language like C and saying “interpret this string as an arithmetic expression.” Nope, can’t do it. You’d need to write a parser to do that — and writing a parser isn’t a simple undertaking.
This means you can build function calls as lists and evaluate them, as in
(eval (cons ‘plus ‘(3 4))),
where cons is the constructor function which inserts an element into the front of the list. (And as you read through the examples, you’ll understand the joke that LISP stands for “Lots of Idiotic Stupid Parentheses.”)
One reason this is even possible is that LISP is not a typed language. In other words, what the ‘plus means depends on how it’s used — it has no predefined type. Of course if you use it the wrong way, you’ll get an error — or perhaps not. LISP will try its best to evaluate what you give it, and may find a way, but it may not be what you intended….
So there are lots of predicates in LISP, like listp, which tells you if something is a list or not. For example, (listp ‘(1 2 3)) would be true, while (listp 2) would be false. Some languages are so heavily typed that all variables must be given a type when they’re declared.
I think the ability to construct function calls and evaluate them is one of the major differences between a functional programming language like LISP and other languages. It might not seem such a big deal at first, but really understanding this idea has definitely impacted the way I think about programming.
Another aspect of the course I TAed which made an impact was the avoidance of loops and variable assignments. Yes, there are loops and variable assignments in LISP, but the style of the course was to avoid them — in other words, it got students (and me!) to think outside the procedural programming box.
This style of programming is not inherent in functional programming, but it was so significant in my learning, it deserves a few words.
The natural alternative to loops is recursion. You can write recursive functions in any programming language, but when it’s your only option, you often have to get creative. So I had to think recursively about everything, not just arithmetic operations, such as in recursively defining the factorial or the Fibonacci numbers. I got pretty good at recursion.
So much so, that when I submitted a multi-procedure program in one of my CS courses at CMU — where I implemented every loop recursively — I was penalized. Why? Because in most languages, recursion has more overhead and is less efficient. Moreover, there is usually a limit imposed to the depth of recursive calls, so in some languages, you can only use recursion up to a point. There are pros and cons to using recursion.
Avoiding variable assignments forces you to think about function parameters. If I wanted to add the sum of the squares of the numbers from 1 to n recursively and avoiding variable assignments, here is what I would do in Python:
So you would have to call sumsquares with an additional argument, as in
This essentially initializes the result to 0 before it’s incremented.
Wow, I didn’t know I had so much to say about LISP! So stay tuned for the next installment of On Coding, where I continue to describe how working with LISP expanded my programming mind in so many different directions….