Common Lisp is more than just Strings, Symbols, Numbers and Lists… In chapter 9 of Land of Lisp, additional types are discussed.
In Common Lisp, arrays are a way of holding a very specific number of
values, and any index can be accessed in O(1) time. They look like
normal lists with a hash in front of them, like #(1 2 3). Arrays in
Common Lisp are very similar to vectors in Clojure.
You can create an array using make-array and pass in how large you
wish the array to be, or by assigning an array literal to a value.
They, unlike C arrays, can hold any number of types.
Literal:
#(1 'two :three "four" (lambda (five) five))| 1 | (quote TWO) | :THREE | four | (LAMBDA (FIVE) FIVE) |
make-array:
(make-array 5)| NIL | NIL | NIL | NIL | NIL |
You can fetch an individual element from an array using the aref
function. This function indexes from zero:
(aref #(1 'two :three "four" (lambda (five) five)) 3)four
To set a value in an array, you can setf the aref call.
Arrays can be of any size that your RAM allows, so combining that with the instant access speed makes it an incredibly handy data type.
Arrays can cause an operating system to page virtual memory to your drive and increase cache misses in your CPU if you use them for large data sets… But the performance gains of lookups makes them worth it.
A hash table in Common Lisp is very similar to an alist, though it
is ridiculously fast for accessing individual elements in
comparison. No matter the size of the list, like arrays above, you can
access and write to a hash table in O(1) time.
You can create a hash table by invoking make-hash-table:
(make-hash-table)#S(HASH-TABLE :TEST FASTHASH-EQL)
You can fetch values from the hash-table with gethash, and set it
with setf:
(let ((hash (make-hash-table)))
(setf (gethash 'dizzy hash) "Spinda")
(gethash 'dizzy hash))gethash returns two values, the first is the value returned from the
list if applicable, and the second is if the element was found or not.
Like arrays, hash tables can, however, cause your OS to start paging virtual memory to your hard drive and increase CPU cache misses. There is also the possibility of the hash having hash collisions, which slows down the hash table for the value that the collisions exits on. They can also have a variable amount of time for storing new data as the hash table attempts to allocate more memory, since it works in “chunks”. Of memory rather than allocating a small amount of data for every addition. They also are relatively inefficient for small tables because of the overhead of the hash table itself.
So, it is recommended to use this only when you are working with larger amounts of data.
In Common Lisp, structures are a data type that are used similarly to
how one would use a database table… Or how a struct would work in
C.
For example, if I wished to make a Pokemon struct, I could like:
(defstruct pokemon
name
species
ability
gender
evs
ivs
trainer-id)This would automatically create a make-<type> function to create an
instance of the struct with keyword arguments to fill in the “slots”
(What “fields” are in the Common Lisp world). So, in this case, that
would mean I now have a make-pokemon function that I could invoke
like this:
(make-pokemon :species 'meowstic :name "Unsociable" :gender :male)#S(POKEMON :NAME "Unsociable" :SPECIES MEOWSTIC :ABILITY NIL :GENDER :MALE :EVS NIL :IVS NIL :TRAINER-ID NIL)
A bunch of automatically created functions for all of the fields have
been created as well. Functions in the format
”<struct>-<field>“… So to get a Pokemon’s species, we can call the
automatically created pokemon-species function on a Pokemon:
(pokemon-name (make-pokemon :species 'geodude :name "Leg day"))Leg day
Of course, we can also use setf to set a slot on the struct:
(let ((pokemon (make-pokemon :species 'gardevoir)))
(setf (pokemon-name pokemon) "Internet")
(pokemon-name pokemon))Internet
And, of course, we can create a struct of a type by having the
literal representation in the S-Expression:
(let ((pokemon #S(POKEMON :NAME "Water Zubat" :SPECIES WINGULL)))
pokemon)#S(POKEMON :NAME "Water Zubat" :SPECIES WINGULL :ABILITY NIL :GENDER NIL :EVS NIL :IVS NIL :TRAINER-ID NIL)
As you can see, it doesn’t even have to be a complete representation
either. The omitted values are automagically filled in to create a
complete struct instance.
You can create a string stream, a string that you can continually concatenate to as if it were a stream… This is handy for debugging and is an efficient way of creating long and complex strings.
You can create one like:
(defparameter almost-java-stringbuilder (make-string-output-stream))Then you can add to the stream in any expected way:
(loop repeat 10
do (princ "Hue" almost-java-stringbuilder))And get the returned string like so:
(get-output-stream-string almost-java-stringbuilder)HueHueHueHueHueHueHueHueHueHue
This is especially handy if we are debugging something like a socket, we could replace the socket with a string stream to see what is being written or received.
It would also be trivial to write a layer to create string streams
for your sockets and to simply princ them to the real deal if you
wanted to monitor a socket.
This is why it is generally good practice to write functions that accept a stream they will write to as opposed to coding it right into the function itself. (Defaults are obviously OK)
String streams are used in macros like with-output-to-string in the
way described above… For example:
(with-output-to-string (*standard-output*)
(princ "I can ")
(princ "fly!"))I can fly!
This redirects *standard-output* to a string stream then returns it.
This is sometimes a much better way of making complex strings. It is
also far cleaner than concatenate. This is somewhat controversial
in the Lisp community though.