AutoLISP: The Complete Guide — Chapter 13 / 16

Symbols and Lambda Functions

This chapter covers more advanced concepts that showcase the richness and elegance of LISP. Symbols and lambda functions will allow you to write more expressive and powerful code. These concepts are less common in everyday AutoLISP scripts, but understanding them will give you a much deeper mastery of the language.

Symbols

In AutoLISP, a symbol is a name that references a value. When you write (setq radius 42), radius is a symbol that is bound to the value 42. Think of it as a label on a box that contains the value 42.

Symbol radius bound to the value 42

When AutoLISP encounters a symbol, it evaluates it automatically — meaning it replaces it with its value:

(setq radius 42)
radius          ; → 42 (the symbol is evaluated)

Reminder: in AutoCAD's command line, you must prefix the symbol with ! to get its value (e.g., !radius). In a .lsp program or in VS Code's debug console, this prefix is not needed.

But what happens if you want to refer to the symbol itself, without evaluating it? That's where quote comes in.

Quoting with quote and '

The quote function (or its shorthand ') prevents the evaluation of an expression:

(quote radius)  ; → radius (the symbol itself, not its value)
'radius         ; → radius (identical, shorthand syntax)

Compare:

(setq radius 42)
radius          ; → 42 (evaluation: returns the value)
'radius         ; → radius (quoting: returns the symbol)

Difference between evaluation and quoting

This is exactly the mechanism you used to create literal lists:

'(1 2 3)       ; → (1 2 3) — the list is not evaluated as a function call
(list 1 2 3)   ; → (1 2 3) — same result, but via a function call

Without the apostrophe, (1 2 3) would be interpreted as a call to the "function" 1, which would cause an error.

The distinction between quote and list is important in practice. With quote, the entire content is frozen — no variables are evaluated. With list, the arguments are evaluated before the list is built. Consider this example of a filter for ssget:

;; The layer name is known in advance: quote is sufficient
(ssget "X" '((8 . "DIMENSION")))

;; The layer name is in a variable: you must use list
;; so that the variable is evaluated
(setq layer-name (getstring "\nLayer name: "))
(ssget "X" (list (cons 8 layer-name)))

With '((8 . layer-name)), AutoLISP would literally search for a layer named "LAYER-NAME" instead of using the value entered by the user.

eval: forcing evaluation

eval is the opposite of quote. It evaluates an expression:

(setq expression '(+ 2 3))
expression          ; → (+ 2 3) — it's a list, not a result
(eval expression)   ; → 5 — the list is evaluated as code

This is a remarkable capability: in LISP, code and data share the same structure (lists). You can therefore build code dynamically and execute it with eval.

;; Build an expression dynamically
(setq operator '+)
(setq data '(10 20 30))
(eval (cons operator data))  ; → 60 — evaluates (+ 10 20 30)

set vs setq

You already know setq. The set function is its "unquoted" version:

(setq x 10)      ; Assigns 10 to the symbol x
(set 'x 10)      ; Identical: assigns 10 to the symbol x

setq is a shorthand for set quote — it automatically quotes the first argument. But set is useful when the variable name is itself dynamic:

(setq variable-name 'my-value)
(set variable-name 42)
my-value  ; → 42

In practice, you will almost always use setq. But knowing that set exists helps you understand the internal workings of the language.

Lambda functions

A lambda function is an anonymous function — a function without a name. It is defined using the lambda keyword:

(lambda (x) (* x x))

This expression creates a function that takes an argument x and returns . But since it has no name, how do you use it?

Calling a lambda directly

You can call it immediately by placing it in the first position of an expression:

((lambda (x) (* x x)) 5)  ; → 25

This is equivalent to:

(defun square (x) (* x x))
(square 5)  ; → 25

But the lambda version doesn't need a name — it is created and used in a single step.

Storing a lambda in a variable

You can also assign a lambda to a variable:

(setq square (lambda (x) (* x x)))

However, be aware that in AutoLISP, to call a function stored in a variable, you will need to use apply (which we will see next).

Higher-order functions

A higher-order function is a function that takes another function as an argument. This is one of the most powerful concepts in functional programming, and AutoLISP provides several of them.

mapcar: applying a function to each element

mapcar applies a function to each element of one or more lists and returns a list of the results:

(mapcar '1+ '(1 2 3 4 5))
; → (2 3 4 5 6) — adds 1 to each element

mapcar applying 1+

With a lambda function:

(mapcar '(lambda (x) (* x x)) '(1 2 3 4 5))
; → (1 4 9 16 25) — computes the square of each element

mapcar with multiple lists (elements are taken in parallel):

(mapcar '+ '(1 2 3) '(10 20 30))
; → (11 22 33) — adds elements pairwise

apply: applying a function to a list of arguments

apply takes a function and a list, and calls the function with the list elements as arguments:

(apply '+ '(1 2 3 4 5))     ; → 15 — equivalent to (+ 1 2 3 4 5)
(apply 'max '(3 7 1 9 2))   ; → 9 — equivalent to (max 3 7 1 9 2)
(apply 'strcat '("Hel" "lo"))  ; → "Hello"

This is very useful when you have a list of values and want to pass them as individual arguments to a function.

vl-sort: sorting a list

vl-sort sorts a list using a comparison function:

(vl-sort '(3 1 4 1 5 9 2 6) '<)
; → (1 2 3 4 5 6 9) — ascending order (duplicates are removed)

(vl-sort '(3 1 4 1 5 9 2 6) '>)
; → (9 6 5 4 3 2 1) — descending order (duplicates are removed)

Warning: vl-sort can remove duplicates from the list. The official documentation states: "Duplicate elements may be eliminated from the list". This behavior is not guaranteed — it depends on the internal implementation — but in practice, duplicates are always removed. If you need to preserve duplicates, sort the indices of the list instead and reconstruct it afterward.

With a lambda for custom sorting:

;; Sort strings by length
(vl-sort '("cat" "hippopotamus" "rat" "dog")
  '(lambda (a b) (< (strlen a) (strlen b)))
)
; → ("rat" "cat" "dog" "hippopotamus")

vl-remove-if and vl-remove-if-not: filtering

;; Keep only even numbers
(vl-remove-if-not
  '(lambda (x) (= (rem x 2) 0))
  '(1 2 3 4 5 6 7 8 9 10)
)
; → (2 4 6 8 10)

;; Remove negative numbers
(vl-remove-if
  '(lambda (x) (< x 0))
  '(-3 5 -1 8 -7 2)
)
; → (5 8 2)

Practical example: transforming points

Imagine we have a list of 2D points and we want to move them by 100 units in X and 50 in Y:

(setq points '((0 0) (10 20) (30 40) (50 60)))

(setq translated-points
  (mapcar
    '(lambda (point)
      (list
        (+ (car point) 100)
        (+ (cadr point) 50)
      )
    )
    points
  )
)
; → ((100 50) (110 70) (130 90) (150 110))

Original and translated points

Let's compare with a classic imperative approach:

;; Loop version
(setq translated-points '())
(foreach point points
  (setq translated-points
    (append translated-points
      (list
        (list
          (+ (car point) 100)
          (+ (cadr point) 50)
        )
      )
    )
  )
)

The mapcar version is more concise and more readable once you are familiar with these concepts.

Summary

Concept Syntax Description
Quoting 'x or (quote x) Prevents evaluation
Evaluation (eval expr) Forces evaluation
Lambda (lambda (args) body) Anonymous function
mapcar (mapcar 'fn lst) Applies a function to each element
apply (apply 'fn lst) Calls a function with a list of arguments
vl-sort (vl-sort lst 'fn) Sorts a list
vl-remove-if (vl-remove-if 'fn lst) Filters out elements
vl-remove-if-not (vl-remove-if-not 'fn lst) Keeps matching elements

In the next chapter, we will see how to attach custom data to entities and link entities together through their handles.


Helping hand Need an AutoCAD (AutoLISP, ObjectARX, .NET, VBA) development? Contact me for a free quote.