The goals of this lab are two-fold. First, it introduces you to CLOS, Common Lisp's way of doing object-oriented programming. Second, it gives more experience with using local variables: as a way to simulate some of the features of OOP in Common Lisp.
Early in the semester, I claimed that writing new languages was easy to do in Common Lisp, at least as compared to other programming languages. You have already encountered data-driven programming, a technique useful for implementing small language extensions, that is well-supported in Lisp. You have also learned a bit about macros, which let you create your own syntax on top of Common Lisp's syntax.
When some folks decided that they would like to do object-oriented programming but that they didn't want to give up programming in Lisp, they did the natural thing: they implemented ways to do OO in Lisp. For a few years, several competing sets of OO macros and functions were in use. A couple of these even added to the theory of object-oriented programming. Most were based on the idea of a closure, a local data environment in which one can create functions with global scope.
Around 1990, one of the later proposals -- the Common Lisp Object System, or CLOS -- was accepted as the future OO standard for Lisp. It differs from traditional OOP languages in that methods are created independent of classes, but methods can specialize on instances of a particular class. Nowadays, you can implement your own OO system if your task is small, but all large OO programming in Common Lisp is done in CLOS. Graham shows how to implement a different OO language, based on the more traditional message-passing model, in Chapter 17.
By the way, the development of CLOS as a standard OO implementation within Common Lisp is a good example of why Lisp will never die, despite being among the oldest of programming languages. Lisp's incredible flexibility allows us to add new features, new languages, and even new programming styles to Lisp whenever we desire. In this way, Lisp is like a living organism; it can absorb new ideas as they prove themselves useful to programmers.
CMU/CL implements a standard version of CLOS.
Prior to doing the in-lab activity, be sure that you have done the following:
Submit by the end of the day Monday, October 22, an e-mail message that contains a transcript of your load and evaluations. Use the built-in function dribble or a Lisp/Emacs buffer to capture your session as a text file.
For example:
> (setf a1 (make-instance 'bank-account :balance 5000.00 :name "Fred")) > (account-name a1) "Fred" > (account-balance a1) 5000.0 > (deposit a1 100) 5100.0 > (account-balance a1) 5100.0 > (withdraw a1 1000) 4100.0 > (account-balance a1) 4100.0
> (setf a2 (make-instance 'limited-account :balance 5000.00 :name "Fred" :limit 100.00)) > (account-name a2) "Fred" > (withdraw a2 20.00) 4980.0 > (withdraw a2 200.00) OVER-LIMIT
In order to solve this exercise, you will need a way to invoke the withdraw method inherited from limited-account's superclass. You do this by invoking call-next-method.
(send object message( arg* ))
The message corresponds to a method whose first argument is the object and the remainder of whose arguments are the message arguments.
For example, following up on the previous example:
> (send a2 withdraw( 20.00 )) 4960.0 > (send a2 withdraw( 200.00 )) OVER-LIMIT
Show how we can use a closure to simulate an object by implementing the bank account class as a closure over a balance variable.
Submit an e-mail message containing only a single, gcl-loadable file containing your solutions to the above exercises by Wednesday, October 24, at 4:00 PM. If you forget what "gcl-loadable" means, please review the instructions in Laboratory Exercise 2..
By that same time, submit a print-out of the same file, stapled, to me in person or to the department office.
Describe how to use the CLOS features presented in Section 11.7 to add an audited-account class to our hierarchy of bank accounts. An audited-account maintains an audit trail for all operations executed on an account and their results. The features discussed in Section 11.7 allow you to define this class without replicating any of the code defined in our existing classes!
Send me your description via e-mail by 4:00 PM on Wednesday. I'll give extra credit for working code!
Graham is clearly a functional programmer at heart. As a result, his view of other programming styles is sometimes a little biased. For instance, he claims that OOP is prone to creating "spaghetti code". Of course, I can write spaghetti code in OOP, but I can also do so using the combination of macros, higher-order functions, and functions that use special variables. (Isn't that just a fancy name for a global variable??) Perhaps Graham is influenced by the complexity of OOP supported by CLOS. Multiple inheritance and the combination rules discussed in Sections 11.7-11.8 are indeed prone to overly complex code. Used properly, though, they are more powerful than anything Java or C++ or Ada95 give us!
That said, though, I hope now that you understand what he meant when he said earlier in the book that Lisp "transcends" object-oriented programming. Lisp contains features that allow you to implement any sort of OOP that you want to do. For that matter, Lisp transcends every style of programming, since it contains features that allow you to implement any sort of programming style that you want to use -- including procedural programming, because Lisp supports side effects with expressions such as setf and do.