Chapter 9. Advanced Datatypes and Generic Programming

As you’ve seen so far, a lot can be accomplished in Lisp by using cons cells, symbols, strings, and numeric datatypes. As a very mature language, Common Lisp contains many more datatypes that move well beyond these basics. In this chapter, we will discuss the most useful of these advanced datatypes, including arrays, hash tables, and structures.

Arrays

The Common Lisp array is very similar to a list. The main advantage of using arrays is that they require only a constant amount of time to access a value at any specific location. We’ll be discussing what this means shortly.

Working with Arrays

To create a new array, use the make-array command, specifying the array’s size:

> (make-array 3)
#(NIL NIL NIL)

This creates an array of length 3. In order to indicate that the value created is not just a list, Common Lisp prepends a hash mark (#) in front of the array.

To get and set items in an array, use the aref function. For example, here’s how we get the item at index 1:

> (defparameter x (make-array 3))
#(NIL NIL NIL)
> (aref x 1)
NIL

Of course, our array is just filled with nils right now, so there’s not much worth getting. To set items in the array to more interesting values, use aref in conjunction with the setf command:

> (defparameter x (make-array 3))
#(NIL NIL NIL)
> (setf (aref x 1) 'foo)
FOO
> x
#(NIL FOO NIL)
> (aref x 1)
FOO

Although aref is usually a command used to get a value out of an array, when used in this special way indicated in the example, it lets us set a value in an array, instead. This ability to use the setf and aref commands together shows off a feature in Common Lisp: its support for generic programming. Let’s take a closer look at the setf command to learn more about how this feature works.

Using a Generic Setter

The Common Lisp language is said to support generic setters. This means that in most cases, the code for pulling a value out of a data structure (whether an array, list, string, or something else) is identical to the code for putting data into that same data structure. The setf command can be used in conjunction with functions that perform getting operations and can use the same functions to perform setting operations, instead.

We’ve already seen that aref can be used to get values out of an array, and when used with setf, it can be used for setting values in the same array. The setf command can perform this trick in a general way across most of the commands in Common Lisp that get items from a data structure. Take, for instance, the following example involving a list:

> (setf foo '(a b c))
  (A B C)
  > (second foo)
  B
 > (setf (second foo) 'z)
  Z
  > foo
  (A Z C)

As you would expect, the expression (second foo) returns B. But, when we pass (second foo) to the setf command , it somehow knows where the B came from, and it is able to treat the expression (second foo) as if it were a regular variable. Basically, the setf command asks itself the question, “Where did the item in my first argument originally come from?” In this case, the value came from the second item in the list named foo. Therefore, if we try to setf this location, the source variable, foo, is modified in response.

In fact, the first argument in setf is a special sublanguage of Common Lisp, called a generalized reference. Not every Lisp command is allowed in a generalized reference, but you can still put in some pretty complicated stuff:

> (setf foo (make-array 4))
  #(NIL NIL NIL NIL)
 > (setf (aref foo 2) '(x y z))
  (X Y Z)
  > foo
 #(NIL NIL (X Y Z) NIL)
 > (setf (car (aref foo 2)) (make-hash-table))
  #S(HASH-TABLE)
 > (setf (gethash 'zoink (car (aref foo 2))) 5)
  5
  > foo
  #(NIL NIL (#S(HASH-TABLE (ZOINK . 5)) Y Z) NIL)

This example demonstrates the true power of setf in Common Lisp. In the first use, we put the list (x y z) into an array as the third item . If we now print foo, we can see that it worked . In the second use, we replace the first item in this list inside the foo array with a hash table . Hash tables are another advanced data type we’ll be learning about shortly, in Hash Tables. It is surprisingly easy to do this with setf, because the generalized reference in the first argument to setf can be arbitrarily complicated.

Finally, we go all out and insert the value 5 into this hash table with the key of zoink . The gethash function lets you get a value out of a hash table, as we’ll see shortly. Here, with the help of setf, we are putting the number 5 into the hash table instead.

I hope you can appreciate from this example how useful setf can be when modifying complicated data structures in a program.

Another cool feature of setf is that you can expand the generalized reference syntax to support new ways of accessing values. setf is a truly generic way of modifying values, regardless of the level of nesting or the datatypes being used.

Arrays vs. Lists

You’ve now seen some basic examples of working with arrays in Lisp. However, to fully understand the benefits of arrays, we need to compare them with lists.

Almost anything that can be done with a list can also be done with an array. However, arrays are usually much faster than lists when accessing specific elements, so the difference is in performance.

For example, the array-handling function aref is very similar to a list-handling function called nth, which allows you access to an item at a specific location in a regular list without using an array. Here is an example using nth on a list:

> (nth 1 '(foo bar baz))
BAR

However, it makes sense to use the nth function only with very small lists. If, for example, list X had thousands of items in it, running the command (nth 1000 x) would be excruciatingly slow, because Lisp lists are made out of chains of cons cells. Hence, the only way Lisp can find the thousandth item in a list is to churn through the 999 preceding objects first.

In contrast, running the command (aref x 1000) on a large array accesses the thousandth item directly, without counting through the previous 999 items. This means aref will execute much more quickly on a large array than the nth command would on a large list. In fact, an aref call will happen very quickly no matter how large the array. Even if you had an array with a billion items, retrieving the last item would still happen very quickly. The only real limiting factor is your system: the amount of RAM your computer has and how capable your Lisp environment is of taking advantage of it.

image with no caption

Not only can we quickly access array values, but we can also change values at any specific location, usually faster than we can by performing the same operations on a list.

Because setting and getting specific values in a large data structure is so important, keep arrays in mind as a tool to help you get the best performance possible with your code.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.118.128.105