You should use DEFVAR or DEFPARAMETER to create global variables rather than SETF. Use DEFVAR when you want subsequent re-evaluations to leave the current value alone, and DEFPARAMETER when you want them to take effect. In this case, you want DEFPARAMETER.
When you find yourself using variables to represent tables of data, it's time to introduce some sort of data structure, the advantage being that data structures can be iterated over when they contain items that can all be treated in the same way.
- Code: Select all
;; All orders will be created with this function, so I can change
;; the data structure I use for an order any time I like. I can add
;; optional and keyword parameters to it without disturbing the
;; legacy interface.
(defun order (item-name quantity item-price)
(list item-name quantity item-price))
;; All legal operations on orders are defined here; if I change
;; the data structure I can simply update these functions
;; and legacy code will continue to work properly.
(defun name (order) (first order))
(defun quantity (order) (second order))
(defun price-per (order) (third order))
(defun subtotal (order) (* (quantity order) (price-per order)))
;; Since all orders have the same interface, I can simply iterate
;; when I want to deal with a lot of them.
(defun total-bill (bill)
(loop for o in bill summing (subtotal o)))
Whenever you find yourself using a programming language as an excessively verbose calculator, it's time to take a step back and factor something out. In this case, I saw a whole bunch of orders, so I devised an interface of five functions to create and get data from orders, and used that interface along with some list operations (one of CL's built-in interfaces) to write TOTAL-BILL in a high-level way. When you get your interface right, you can program just by telling the computer what you want, viz. the total of a list of orders:
- Code: Select all
;; Altogether, I've written one more line of code than you did--but if
;; I want to add more items to a bill, I just tack them onto this list
;; (which would probably be generated programmatically or read from a
;; database anyway).
(total-bill (list (order 'bolts 7 2.99)
(order 'nuts 7 1.19)
(order 'screws 20 0.79)))
If I wanted to be obsessive, I could have defined a bill type as well, but since a list is a list, unlike an order which stores several different types of data (name, quantity, price), I didn't see the advantage. If you expected to be typing up a lot of bills by hand, a macro implementing a less verbose syntax than (list (order 'thing &c... would be nice to have. You could also use DEFSTRUCT or DEFCLASS to create your data types, or make the list-based order type official with DEFTYPE.