Clicky

Lisp Programming Language

This section was labeled under programming.

Tutorial on Lisp.

The Lisp programming language s a family of programming languages with a long history and a distinctive, fully parenthesized prefix notation. Originally specified in 1958, Lisp is the second-oldest high-level programming language still in common use. Only Fortran is older, by one year. Lisp has changed since its early days, and many dialects have existed over its history. Today, the best-known general-purpose Lisp dialects are Common Lisp, Scheme, Racket and Clojure.

Choosing Implementation

The first thing you have to do is to choose a Lisp implementation. This may seem like a strange thing to have to do for folks used to languages such as Perl, Python, Visual Basic (VB), C#, and Java. The difference between Common Lisp and these languages is that Common Lisp is defined by its standard—there is neither a single implementation controlled by a benevolent dictator, as with Perl and Python, nor a canonical implementation controlled by a single company, as with VB, C#, and Java. Anyone who wants to read the standard and implement the language is free to do so. Furthermore, changes to the standard have to be made in accordance with a process controlled by the standards body American National Standards Institute (ANSI). That process is designed to keep any one entity, such as a single vendor, from being able to arbitrarily change the standard. Thus, the Common Lisp standard is a contract between any Common Lisp vendor and Common Lisp programmers. The contract tells you that if you write a program that uses the features of the language the way they’re described in the standard, you can count on your program behaving the same in any conforming implementation.

Hello World

You can just pass “Hello World” (with quotes) to the Lisp REPEL and you will get your first Hello World in Lisp.

This works because strings, like numbers, have a literal syntax that’s understood by the Lisp reader and are self-evaluating objects: Lisp reads the double-quoted string and instantiates a string object in memory that, when evaluated, evaluates to itself and is then printed in the same literal syntax. The quotation marks aren’t part of the string object in memory—they’re just the syntax that tells the reader to read a string. The printer puts them back on when it prints the string because it tries to print objects in the same syntax the reader understands.

However, this may not really qualify as a “hello, world” program. It’s more like the “hello, world” value.

You can take a step toward a real program by writing some code that as a side effect prints the string “hello, world” to standard output. Common Lisp provides a couple ways to emit output, but the most flexible is the FORMAT function. FORMAT takes a variable number of arguments, but the only two required arguments are the place to send the output and a string. You’ll see in the next chapter how the string can contain embedded directives that allow you to interpolate subsequent arguments into the string, à la printf or Python’s string-%. As long as the string doesn’t contain an \~, it will be emitted as-is. If you pass t as its first argument, it sends its output to standard output. So a FORMAT expression that will print “hello, world” looks like this:

CL-USER> (format t "hello, world")
hello, world
NIL

However, it’s still arguable whether you’ve yet written a true “program.” But you’re getting there. And you’re seeing the bottom-up style of programming supported by the REPL: you can experiment with different approaches and build a solution from parts you’ve already tested. Now that you have a simple expression that does what you want, you just need to package it in a function. Functions are one of the basic program building blocks in Lisp and can be defined with a DEFUN expression such as this:

CL-USER> (defun hello-world () (format t "hello, world"))
HELLO-WORLD

At one level, this expression, like all the others you’ve seen, is just another expression to be read, evaluated, and printed by the REPL. The return value in this case is the name of the function you just defined and we will discuss later why was it uppercased.

Why Lisp?

Why learn Lisp? Because it lets you do things that you can’t do in other languages. If you just wanted to write a function to return the sum of the numbers less than n> say, it would look much the same in Lisp and C:

(defun sum (n)
   (let ((s 0))
       (dotimes (i n s)
            (incf s i))))
int aum(int n){
    int i , s = 0;
    for ( i = 0; i < n; i++)
        s += i ;
    return(s);
}

If you only need to do such simple things, it doesn’t really matter which language you use. Suppose instead you want to write a function that takes a number n, and returns a function that adds n to its argument:

(defun addn (n)
    #'(lambda (x)
     (+ x n)))

What does addn look like in C? You just can’t write it. You might be wondering, when does one ever want to do things like this? Programming languages teach you not to want what they cannot provide. You have to think in a language to write programs in it, and it’s hard to want something you can’t describe. When I first started writing programs—in Basic—I didn’t miss recursion, because I didn’t know there was such a thing. I thought in Basic. I could only conceive of iterative algorithms, so why should I miss recursion?

Lisp is designed to be extensible: it lets you define new operators yourself. This is possible because the Lisp language is made out of the same functions and macros as your own programs. So it’s no more difficult to extend Lisp than to write a program in it. In fact, it’s so easy (and so useful) that extending the language is standard practice. As you’re writing your program down toward the language, you build the language up toward your program. You work bottom-up, as well as top-down.

As programming environments grow in power, and languages become more abstract, the Lisp style of programming is gradually replacing the old plan-and-implement model.

In the old model, bugs are never supposed to happen. Thorough specifications, painstakingly worked out in advance, are supposed to ensure that programs work perfectly. Sounds good in theory. Unfortunately, the specifications are both written and implemented by humans. The result, in practice, is that the plan-and-implement method does not work very well.

Planning is a necessary evil. It is a response to risk: the more dangerous an undertaking, the more important it is to plan ahead. Powerful tools decrease risk, and so decrease the need for planning. The design of your program can then benefit from what is probably the most useful source of information available: the experience of implementing it.

Lisp style has been evolving in this direction since the 1960s. You can write prototypes so quickly in Lisp that you can go through several iterations of design and implementation before you would, in the old model, have even finished writing out the specifications. You don’t have to worry so much about design flaws, because you discover them a lot sooner. Nor do you have to worry so much about bugs. When you program in a functional style, bugs can only have a local effect. When you use a very abstract language, some bugs (e.g. dangling pointers) are no longer possible, and what remain are easy to find, because your programs are so much shorter. And when you have an interactive environment, you can correct bugs instantly, instead of enduring a long cycle of editing, compiling, and testing.

Lisp style has been evolving in this direction since the 1960s. You can write prototypes so quickly in Lisp that you can go through several iterations of design and implementation before you would, in the old model, have even finished writing out the specifications. You don’t have to worry so much about design flaws, because you discover them a lot sooner. Nor do you have to worry so much about bugs. When you program in a functional style, bugs can only have a local effect. When you use a very abstract language, some bugs (e.g. dangling pointers) are no longer possible, and what remain are easy to find, because your programs are so much shorter. And when you have an interactive environment, you can correct bugs instantly, instead of enduring a long cycle of editing, compiling, and testing.

Lisp Specifications

“Okay I’m convinced”

Evaluation

In Lisp, + is a function, and an expression like (+ 2 3) is a function call. When Lisp evaluates a function call, it does so in two steps:

  1. First the arguments are evaluated, from left to right. In this case, each argument evaluates to itself, so the values of the arguments are 2 and 3, respectively.
  2. The values of the arguments are passed to the function named by the operator. In this case, it is the + function, which returns 5.

Not all the operators in Common Lisp are functions, but most are. And function calls are always evaluated this way. The arguments are evaluated left-to-right, and their values are passed to the function, which returns the value of the expression as a whole. This is called the evaluation rule for Common Lisp.

One operator that doesn’t follow the Common Lisp evaluation rule is quote. The quote operator is a special operator, meaning that it has a distinct evaluation rule of its own. And the rule is: do nothing. The quote operator takes a single argument, and just returns it verbatim:

(quote (+ 4 5))

For convenience, Common Lisp defines ’ as an abbreviation for quote. You can get the effect of calling quote by affixing a ’ to the front of any expression:

`(+ 4 5)

It is much more common to use the abbreviation than to write out the whole quote expression.

Lisp provides the quote as a way of protecting expressions from evaluation. The next section will explain how such protection can be useful.

Symbols do not (usually) evaluate to themselves, so if you want to refer to a symbol, you should quote it, as above.

Lists are represented as zero or more elements enclosed in parentheses. The elements can be of any type, including lists. You have to quote lists, or Lisp would take them for function calls:

Data

Lisp offers all the data types we find in most other languages, along with several others that we don’t. One data type we have used already is the integer, which is written as a series of digits: 256. Another data type Lisp has in common with most other languages is the string, which is represented as a series of characters surrounded by double-quotes: “ora et labora”. Integers and strings both evaluate to themselves.

Lists are represented as zero or more elements enclosed in parentheses. The elements can be of any type, including lists. You have to quote lists, or Lisp would take them for function calls:

`(pray sunrise)

Notice that one quote protects a whole expression, including expressions within it. You can build lists by calling list. Since list is a function, its arguments are evaluated. Here we see a call to + within a call to list:

(list `my `(+ 2 3))

We are now in a position to appreciate one of the most remarkable features of Lisp. Lisp programs are expressed as lists. If the arguments of flexibility and elegance did not convince you that Lisp notation is a valuable tool, this point should. It means that Lisp programs can generate Lisp code. Lisp programmers can (and often do) write programs to write their programs for them.

List Operations

The function cons builds lists. If its second argument is a list, it returns a new list with the first argument added to the front:

(cons ' a ' (b c d))

We can build up lists by consing new elements onto an empty list. The list function that we saw in the previous section is just a more convenient way of consing several things onto nil.

The primitive functions for extracting the elements of lists are car and cdr. The car of a list is the first element, and the cdr is everything after the first element:

(car `(ab c))
(cdr `(ab c))

The parentheses in the cdr are for that it is probably a list, since it is everything but the first element, but in car it is only a one element that’s why there is not parentheses.

You can use combinations of car and cdr to reach any element of a list. If you want to get the third element, you could say:

(car (cdr (cdr ' ( a b c d ) ) ) )

However, you can do the same thing more easily by calling third :

( second '( a b c d))

There are also first and second, you can guess what is their functionality.

Truth

In Common Lisp, the symbol t is the default representation for truth. Like nil, t evaluates to itself. The function listp returns true if its argument is a list:

(listp `(a b c))

A function whose return value is intended to be interpreted as truth or falsity is called a predicate. Common Lisp predicates often have names that end with p.

The simplest conditional in Common Lisp is if. It usually takes three arguments: a test expression, a then expression, and an else expression. The test expression is evaluated. If it returns true, the then expression is evaluated and its value is returned. If the test expression returns false, the else expression is evaluated and its value is returned:

(if (listp '(abc))
    (+ 1 2)
    (+ 5 6))

Like quote, if is a special operator. It could not possibly be implemented as a function, because the arguments in a function call are always evaluated, and the whole point of if is that only one of the last two arguments is evaluated. The last argument to if is optional. If you omit it, it defaults to nil.

There are and and or functions, you probably can tell what do they do.

Basic Functions

You can define new functions with defun. It usually takes three or more arguments: a name, a list of parameters, and one or more expressions that will make up the body of the function. Here is how we might define third:

(defun our-third (x)
    (car (cdr (cdr x ))))

Let’s try to defun member which tells you if whether an element is a member of function:

(defun somemeeber (obj lst)
  (if (null lst) nil
      (if (eql (car lst) obj) t
          (somemeeber obj (cdr lst)))))

The difference between this implementation (along many other things in the internal implementation) is that instead of evaluating the rest of the list the that member does it, I evaluate the truth value t.

(somemeeber 3 '(1 2 3 4))

I/O

The most general output function in Common Lisp is f ormat. It takes two or more arguments: the first indicates where the output is to be printed, the second is a string template, and the remaining arguments are usually objects whose printed representations are to be inserted into the template. Here is a typical example:

(format t "~A plus ~A equals ~A. ~%" 2 3 ( + 2 3))

Notice that two things get displayed here. The first line is displayed by format. The second line is the value returned by the call to format, displayed in the usual way by the toplevel. Ordinarily a function like format is not called directly from the toplevel, but used within programs, so the return value is never seen.

The first argument to format, t, indicates that the output is to be sent to the default place. Ordinarily this will be the toplevel. The second argument is a string that serves as a template for output. Within this string, each ~A indicates a position to be filled, and the ~% indicates a newline. The positions are filled by the values of the remaining arguments, in order.

The standard function for input is read. When given no arguments, it reads from the default place, which will usually be the toplevel. Here is a function that prompts the user for input, and returns whatever is entered:

(defun askem (string)
  (format t ""A" string)
    (read))

Variables

One of the most frequently used operators in Common Lisp is let , which allows you to introduce new local variables.

A let expression has two parts. First comes a list of instructions for creating variables, each of the form (variable expression). Each variable will initially be set to the value of the corresponding expression.

(defun ask-for-number ()
  (format t "Enter a number")
  (let ((n (read)))
    (if (numberp n)
        n
        (ask-for-number))))

You can create a global variable by giving a symbol and a value to defparameter:

Such a variable will then be accessible everywhere, except in expressions that create a new local variable with the same name. To avoid the possibility of this happening by accident, it’s conventional to give global variables names that begin and end with asterisks. The name of the variable we just created would be pronounced “star-glob-star”.

You can also define global constants, by calling def constant:

(defconstant limit (+ *glob* 1))

There is no’need to give constants distinctive names, because it will cause an error if anyone uses the same name for a variable. If you want to check whether some symbol is the name of a global variable or constant, use boundp:

(defconstant *glob* 43)
(boundp '*glob*)

Assignment

In Common Lisp the most general assignment operator is setf. We can use it to do assignments to either kind of variable:

(setf *glob* 90)
(defun read-number-and-prefix-with-message ()
  (let ((n (ask-for-number)))
    (setf n (+ n 1))
    (format t "The new number is ~A" n)))

Iteration

When we want to do something repeatedly, it is sometimes more natural to use iteration than recursion. A typical case for iteration is to generate some sort of table. This function

(defun show-squares (start end)
  (do ((i start (+ i 1)))
      ((> i end) 'done)
    (format t "~A ~A~%" i (* i i))))
(show-squares 2 4)

The do macro is the fundamental iteration operator in Common Lisp. Like let , do can create variables, and the first argument is a list of variable specifications. Each element of this list can be of the form

{variable initial update)

where variable is a symbol, and initial and update are expressions. Initially each variable will be set to the value of the corresponding initial, on each iteration it will be set to the value of the corresponding update. The do in show-squares creates just one variable, i . On the first iteration i will be set to the value of start , and on successive iterations its value will be incremented by one.

The remaining arguments to do comprise the body of the loop. They will be evaluated, in order, on each iteration. On each iteration the variables are updated, then the termination test is evaluated, and then (if the test failed) the body is evaluated.

(defun show-squares (i end)
 (if (> i end)
  'done
  (progn
     (format t M~A ~A~°/,n i (* i i ) )
   (show-squares (+ i 1) e n d))))

Common Lisp has simpler iteration operators for special cases. To iterate through the elements of a list, for example, you would be more likely to use dolist . Observe the following functions:

(defun our-length-without-do (lst)
  (if (null lst)
      0
      (+ (our-length-without-do (cdr lst)) 1)))

Functions as Objects

In Lisp, functions are regular objects, like symbols or strings or lists. If we give the name of a function to function, it will return the associated object. Like quote, function is a special operator, so we don’t have to quote the argument:

(function +)

You can replace the + with any other operator or function like read.

This strange-looking return value is the way a function might be displayed in a typical Common Lisp implementation.

Until now we have only dealt with objects that look the same when Lisp displays them as when we typed them in. This convention does not apply to functions. Internally, a built-in function like + is likely to be a segment of machine language code. A Common Lisp implementation may choose whatever external representation it likes.

Just as we can use ' as an abbreviation for quote, we can use #' as an abbreviation for function:

(apply #'+ l) ==  (apply (function +) l)

#' (aka function) can be used in front of (lambda …) (we will talk about lambda soon) but it’s redundant there, so the only place where it’s really meaningful is in front of a symbol, as in #'car. In ELisp, #'car and 'car are almost completely equivalent, so one of the main purpose is simply to document the intention (i.e. to indicate to whoever reads this code that you intend to use this symbol as a function). Yet there are a few circumstances, where the difference is more significant:

  • The byte-compiler takes advantage of this documented intention and when you write #'car it will check whether car exists as a function, and if it doesn’t find it, it will emit a warning, just like it would if you had a call to that function.
  • Inside cl-flet and cl-labels, only #'f can refer to the locally defined function f, because 'f will still refer to the global symbol f (and whichever function might be stored in its symbol-function slot). E.g.

    (cl-flet ((car (x y) (+ x y)))
      (list #'car 'car))
    =>
    ((closure nil (x y) (+ x y)) car)
    

    (read: Get in the habit of using sharp quote)

Like any other kind of object, we can pass functions as arguments. One function that takes a function as an argument is apply. It takes a function and a list of arguments for it, and returns the result of applying the function to the arguments:

(apply #'+ ' ( 1 2 3))

The function f u n c a l l does the same thing but does not need the arguments to be packaged in a list:

(funcall #'+ 1 2 3)

The defun macro creates a function and gives it a name. But functions don’t have to have names, and we don’t need def un to define them. Like most other kinds of Lisp objects, we can refer to functions literally.

To refer literally to an integer, we use a series of digits; to refer literally to a function, we use what’s called a lambda expression. A lambda expression is a list containing the symbol lambda, followed by a list of parameters, followed by a body of zero or more expressions.

Here is a lambda expression representing a function that takes two numbers and returns their sum:

(lambda (x y)
  (+ x y))

A lambda expression can be considered as the name of a function. Like an ordinary function name, a lambda expression can be the first element of a function call, for example:

((lambda (x) (+ x 100)) 2)

Types

Lisp has an unusually flexible approach to types. In many languages, variables are what have types, and you can’t use a variable without specifying its type. In Common Lisp, values have types, not variables. You could imagine that every object had a label attached to it, identifying its type. This approach is called manifest typing. You don’t have to declare the types of variables, because any variable can hold objects of any type.

(read more about manifest typing)

We will study types in details later.

Lists

In Lisp, lists are one of the fundamental data structures in Lisp. In the earliest dialects they were the only data structure: the name “Lisp” originally stood for “LISt Processor.” But Lisp has long since outgrown this acronym. Common Lisp is a general-purpose programming language with a wide variety of data structures.

Conses

We previously introduced cons, car, and cdr, the primitive list manipulation functions, what cons really does is combine two objects into a two-part objects into a two-part object called cons. Conceptually, a cons is a pair of pointers; the first one is the car and the second is the cdr.

Conses provide a convenient representation for pairs of any type. The two halves of a cons can point to any kind of object, including conses. It is by taking advantage of the latter possibility that we use conses to build lists. One does not tend to think of lists as pairs, but they can be defined that way. Any nonempty list can be considered as a pair of the first element and the rest of the list.

Lisp lists are the embodiment of this idea. We use one half of the cons to point to the first element of the list, and the other to point to the rest of the list (which is either another cons or nil). The convention in Lisp has always been to use the car for the first element and the cdr for the rest of the list. So now car is synonymous with the first element of a list, and cdr with the rest. Lists are not a distinct kind of object, but conses linked together in this way.

The last two lists we made both have three elements; it just happens that the second element of z is also a list. Such a list is called a nested list, while a list like y that doesn’t contain other lists as elements is called a flat list.

The function consp returns true if its argument is a cons. So l i s t p could be defined:

(defun our-listp (x)
   (or (null x) (consp x)))

Since everything that is not a cons is an atom, the predicate atom could be defined:

(defun our-atom (x) (not (consp x )))

Equality

Each time you call cons, Lisp allocates a new piece of memory with room for two pointers. So if we call cons twice with the same arguments, we get back two values that look the same, but are in fact distinct objects:

(eql (cons 'a nil) (cons 'a nil) )

equal, essentially, returns true if its arguments would print the same:

(equal (cons 'a nil) (cons 'a nil) )

Pointers

One of the secrets to understanding Lisp is to realize that variables have values in the same way that lists have elements. As conses have pointers to their elements, variables have pointers to their values.

You may have used other languages in which pointers were manipulated explicitly. In Lisp you never have to do this, because the language handles pointers for you. We’ve already seen how this happens with lists. Something

similar happens with variables. Suppose, for example, we set two variables to the same list:

(setf y (list 'a 'b 'c))
(setf x '( a b c))

What actually happens when we set y to the value of x? The location in memory associated with the variable x does not contain the list itself, but a pointer to it. When we assign the same value to y, Lisp copies the pointer, not the list. So whenever you assign one variable the value of another, the two variables will have e q l values:

(setf y (list 'a 'b 'c))
(setf x '( a b c))
(setf y x)
(eql x y)

Example: Compression

As an example, this section shows how to perform a simple form of compression on lists. This algorithm goes by the impressive name of run-length, it should work as follows:

(comprs '(a a a c))
(defun comprs (x)
  (ucompr (car x) 1 (cdr x)))


(defun ucompr (elmnt n lst)
  (if (null lst)
      (list (build elmnt n))
      (let ((next (car lst)))
        (if (eql next elmnt)
            (ucompr elmnt (+ n 1) (cdr lst))
            (cons (build elmnt n) (ucompr next 1 (cdr lst)))))))

(defun build (elmnt n)
  (if (> n 1)
      (list n elmnt)
      elmnt))

Can you write one to do uncompress?

(defun unbuild (n elmt)
  (if (zerop n)
      nil
      (cons elmt (unbuild (- n 1) elmt))))

(defun uncompress (lst)
  (if (null lst)
      nil
      (if (listp (car lst))
          (let ((f (car (car lst)))
                (s (cdr (car lst))))
            (cons (unbuild f s)
                  (uncompress (cdr lst))))
          (uncompress (cdr lst)))))

This will work, however, as you will see we are going to get some different kind of lists, lists of lists;

(uncompress (list '(3 A) '(3 B) '(2 C)))

Access

Common Lisp has additional access functions defined in terms of car and cdr. To find the element at a given position in a list we call nth,

(nth 0 ' ( a b c))

and to find the nth cdr, we call nthcdr:

(nthcdr 2 ' ( a b c))

Both nth and nthcdr are zero-indexed; that is, the elements are numbered starting at zero rather than one. In Common Lisp, whenever you use a number to refer to an element of a data structure, the numbering starts at zero. The two functions do almost the same thing; nth is equivalent to car of nthcdr.

Mapping functions

Common Lisp provides several functions for calling functions on the elements of a list. The most frequently used is mapcar, which takes a function and one or more lists, and returns the result of applying the function to elements taken from each list, until some list runs out:

(mapcar #'(lambda (x) (+ x 10))
         '(1 2 3))

The related maplist takes the same arguments, but calls the function on successive cdrs of the lists:

(maplist #'(lambda (x) x)
          '(a b c))

Trees

Conses can also be considered as binary trees, with the car representing the right subtree and the cdr the left. For example, the list

(a (b c) d)

is also the tree represented in the following figure:

2023-03-05_17-47.png

Binary trees without interior nodes are not useful for much. Common Lisp includes functions for operating on trees not because one needs trees as such, but because one needs a way to do something to a list and all the lists within it. For example, suppose we have a list like

(and (integerp x) (zerop (mod x 2)))

and we want to substitute y for x throughout. It won’t do to call substitute, which replaces elements in a sequence:

(substitute 'y 'x '(and (integerp x) (zerop (mod x 2))))

As you can see, this call is ineffective because substitute looks for sequential elements, like this:

(substitute 'y 'x '(x (integerp x) (zerop (mod x 2))))

However, there is a good replacement which is subset, which can access subtrees:

(subst 'y 'x '(and (integerp x) (zerop (mod x 2))))

(AND (INTEGERP Y) (ZEROP (MOD Y 2)))

Sequences

Another way to think of a list is as a series of objects in a particular order. In Common Lisp, sequences include both lists and vectors.

To copy part of a sequence, we use subseq. The second argument (required) is the position of the first element to be included, and the third argument (optional) is the position of the first element not to be included.

The function reverse returns a sequence with the same elements as its argument, but in the reverse order:

(reverse '(a b c))

Common Lisp has a built-in sort function called sort. It takes a sequence and a comparison function of two arguments, and returns a sequence with the same elements, sorted according to the function:

(sort '( 0 2 1 3 8 ) #'>)

You have to be careful when using sort , because it’s destructive. For efficiency reasons, sort is allowed to modify the sequence given to it as an argument. So if you don’t want your original sequence modified, pass a copy.

Dotted Pair Notation

Dotted pair notation is a general syntax for cons cells that represents the CAR and CDR explicitly. In this syntax, (a . b) stands for a cons cell whose CAR is the object a and whose CDR is the object b. Dotted pair notation is more general than list syntax because the CDR does not have to be a list. However, it is more cumbersome in cases where list syntax would work. In dotted pair notation, the list (1 2 3) is written as (1 . (2 . (3 . nil))). For nil-terminated lists, you can use either notation, but list notation is usually clearer and more convenient. When printing a list, the dotted pair notation is only used if the CDR of a cons cell is not a list.

KILL Example: Shortest Path   @write

Following code contains a program for finding the shortest path through a network. The function shortest-path takes a start node, a destination node, and a network, and returns the shortest path, if there is one.

In this example, nodes are represented as symbols, and networks are represented as assoc-lists with elements of the form

(node . neighbors)

For example:

(setf min '((a b c) (b c) (c d)))

We can visualize it like this:

Oa3OgZT.png

Garbage

Lists can be slow for several reasons. They offer sequential instead of random access, so retrieving a given element takes longer in a list than an array, for the same reason that it takes longer to find something on a tape than on a disk. Internally, conses tend to be represented as pointers, so traversing a list means traversing a series of pointers, instead of simply incrementing an index, as in an array. But these two costs can be small compared to the cost of allocating and recycling cons cells.

Automatic memory management is one of Lisp’s most valuable features. The Lisp system maintains a segment of memory called the heap. The system keeps track of unused memory in the heap and doles it out as new objects are created. The function cons, for example, returns a newly allocated cons.

Allocating memory from the heap is sometimes generically known as consing. If such memory were never freed, Lisp would run out of space for new objects and have to shut down. So the system must periodically search through the heap, looking for memory that is no longer needed. Memory that is no longer needed is called garbage, and the scavenging operation is called garbage collection, or GC.

> (setf 1st (list 'a >b }c))
(A B C)
> (setf 1st nil)
NIL

Since we have no way of reaching this list, it might as well not exist. Objects that we no longer have any way of reaching are garbage. The system can safely reuse these three cons cells.

This way of managing memory is a great convenience to the programmer. You never have to allocate or deallocate memory explicitly. And this means that you never have to deal with the bugs that come from doing so. Memory

leaks and dangling pointers are simply impossible in Lisp.

Data Structure

The preceding discussed the list, Lisp’s most versatile data structure. This chapter shows how to use Lisp’s other data structures: arrays (including vectors and strings), structures, and hash tables. They may not be as flexible as lists, but they can make access faster, and take up less space.

Arrays

In Common Lisp, you can make an array by calling make-array with a list of dimensions as the first argument. To make a 2x3 array we would say:

(make-array '(2 3))

The n before the A tells how many dimensions the array have. You can also pass a tag initial-element to set its value:

(make-array '(2 3) :initial-element 2)

We can access both vectors and arrays using aref:

(aref (make-array '(2 3) :initial-element 2) 0 0)

However, in the cause of vectors there is a fastere function which is svref

(svref (vector 'a 'b 'c) 2)

KILL Structures   @write

Functions

Understanding functions is one of the keys to understanding Lisp. Conceptually, functions are at the core of Lisp. Practically, they are one of the most useful tools at your disposal

The predicate f boundp tells whether there is a function with a given symbol as its name. If a symbol is the name of a function, symbol-function will return it:

(fboundp '+)

By setting the symbol-function of some name to a function,

(setf (symbol-function 'add2) #'(lambda (x) (+ x 2)))
(add2 3)

Tags

We’ve already seen four functions for retrieving elements of sequences: nth for lists, aref and svref for vectors, and char for strings. Common Lisp also provides a function elt that works for sequences of any kind:

(elt '(a b c) 1)

Many sequence functions take one or more keyword arguments from the standard set listed in this table:

Paramater Purpose DEAFULT
:key a function to apply to all elements identity
:test the test function for comparison eql
:from-end if true, work backwards nil
:start position at which start 0
:end position, if any, at which to stop nil

Functional Programming

Functional programming means writing programs that work by returning values, instead of by modifying things. It is the dominant paradigm in Lisp. Most built-in Lisp functions are meant to be called for the values they return, not for side-effects.

The function remove, for example, takes an object and a list and returns a new list containing everything but that object:

Why not just say that remove removes an object from a list? Because that’s not what it does. The original list is untouched afterwards:

(setf 1st ' (c a r a t ) )
(remove 'a 1st)

Why not just say that remove removes an object from a list? Because that’s not what it does. The original list is untouched afterwards.

1st

So what if you really do want to remove something from a list? In Lisp you generally do such things by passing the list as an argument to some function, and using setf with the return value. To remove all the as from a list x, we say:

(setf x (remove 'a x))

Functional programming means, essentially, avoiding setf and things like it. At first sight it may be difficult to imagine how this is even possible, let alone desirable. How can one build programs just by returning values?

It would be inconvenient to do without side-effects entirely. However, as you read further, you may be surprised to discover how few you really need. And the more side-effects you do without, the better off you’ll be.

One of the most important advantages of functional programming is that it allows interactive testing. In purely functional code, you can test each function as you write it. If it returns the values you expect, you can be confident that it is correct. The added confidence, in the aggregate, makes a huge difference. You have instant turnaround when you make changes anywhere in a program. And this instant turnaround enables a whole new style of programming, much as the telephone, as compared to letters, enabled a new style of communication.

Some Other Resources

TODO Ray-Tracing

Misc: eqal?

  • (eq x y) is true if and only if x and y are the same identical object.

    (eq 'a 'b) is false.
    (eq 'a 'a) is true.
    (eq 3 3) might be true or false, depending on the implementation.
    (eq 3 3.0) is false.
    (eq 3.0 3.0) might be true or false, depending on the implementation.
    (eq #c(3 -4) #c(3 -4))
      might be true or false, depending on the implementation.
    (eq #c(3 -4.0) #c(3 -4)) is false.
    (eq (cons 'a 'b) (cons 'a 'c)) is false.
    (eq (cons 'a 'b) (cons 'a 'b)) is false.
    (eq '(a . b) '(a . b)) might be true or false.
    (progn (setq x (cons 'a 'b)) (eq x x)) is true.
    (progn (setq x '(a . b)) (eq x x)) is true.
    (eq #\A #\A) might be true or false, depending on the implementation.
    (eq "Foo" "Foo") might be true or false.
    (eq "Foo" (copy-seq "Foo")) is false.
    (eq "FOO" "foo") is false.
    
    
  • The eql predicate is true if its arguments are eq, or if they are numbers of the same type with the same value, or if they are character objects that represent the same character.

    (eql 'a 'b) is false.
    (eql 'a 'a) is true.
    (eql 3 3) is true.
    (eql 3 3.0) is false.
    (eql 3.0 3.0) is true.
    (eql #c(3 -4) #c(3 -4)) is true.
    (eql #c(3 -4.0) #c(3 -4)) is false.
    (eql (cons 'a 'b) (cons 'a 'c)) is false.
    (eql (cons 'a 'b) (cons 'a 'b)) is false.
    (eql '(a . b) '(a . b)) might be true or false.
    (progn (setq x (cons 'a 'b)) (eql x x)) is true.
    (progn (setq x '(a . b)) (eql x x)) is true.
    (eql #\A #\A) is true.
    (eql "Foo" "Foo") might be true or false.
    (eql "Foo" (copy-seq "Foo")) is false.
    (eql "FOO" "foo") is false.
    
  • The equal predicate is true if its arguments are structurally similar (isomorphic) objects. A rough rule of thumb is that two objects are equal if and only if their printed representations are the same.

I seek refuge in God, from Satan the rejected. Generated by: Emacs 29.4 (Org mode 9.6.17). Written by: Salih Muhammed, by the date of: 2023-02-16 Thu 18:55. Last build date: 2024-07-13 Sat 07:46.