Here’s how to remove variables and functions from an image:
(makunbound 'variable)
(fmakunbound 'function)
So, for example:
(defvar foo 42)
(defun bar () (print "Hello world!"))
(boundp 'foo) ;; T
(fboundp 'bar) ;; T
(makunbound 'foo)
(fmakunbound 'bar)
(boundp 'foo) ;; nil
(fboundp 'bar) ;; nil
Read on to find out why I prefer the lisp approach over most other languages.
What does this look like in other languages?
In interpreted languages, like python and R there is the concept of removing or deleting a variable or function.
In python you del variable
, in R you rm(variable)
:
foo = 5
foo
Naturally results in
5
Now we delete the variable:
del foo # Delete foo
foo
# Traceback (most recent call last):
# File "<stdin>", line 1, in <module>
# NameError: name 'foo' is not defined
Why does common lisp appear to make it more complicated?
Why do we use makunbound
and fmakunbound
, etc., in common lisp?
(The following is lightly rewritten and sourced from Peter Norvig’s Paradigms of Artificial Intelligence Programming)
…because common Lisp has at least seven name spaces. The two we think of most often are
- Functions and Macros
- Variables
Python, R, Scheme, etc. conflate these two name spaces, but Common Lisp keeps
them separate, so that in a function application like (f)
the function/macro
name space is consulted for the value of f
, but in (+ f)
, f
is treated as
a variable name.
- Special variables form a distinct name space from lexical
variables
So thef
in(+ f)
is treated as either a special or lexical variable, depending on if there is an applicable special declaration. - Data types
Even iff
is defined as a function and/or a variable, it can also be defined as a data type withdefstruct
,deftype
, ordefclass
. - Labels for
go
statements within a tagbody - Block names for
return-from
statements within a block - Symbols inside a quoted expression are treated as constants, and thus form
name space
These symbols are often used as keys in user-defined tables, and in a sense each such table defines a new name space. One example is the tag name space, used by catch and throw. Another is the package name space.
Just because you can do something, doesn’t mean you should
It is a good idea to limit each symbol to only one name space. Common Lisp will not be confused if a symbol is used in multiple ways, but the poor human reader probably will be.
- Peter Norvig, Paradigms of Artificial Intelligence Programming
🤣
Why do I like the lisp approach?
It cuts down on errors.
When collaborating it is easy to clash in your namespaces. You might define the
variable two_pi
, I might want to define two_pi
as a function. My code runs,
I merge it. Your code runs, you merge it. We didn’t have appropriate testing…
💣💥
Here’s a contrived example, presented in three different programming languages: R, python and (common) lisp.
Here I define a variable two_pi
to be twice the value of \( \pi \), then I
define a function to calculate the circumference of a circle and then I define a
function (also) called two_pi
. Let’s see what happens…
In R
two_pi <- 2 * pi
circumference <- function(r) {
cat("R: The circumference is ", (two_pi * r), "\n")
}
# We all know pi is actually 3
two_pi <- function() {
return(6);
}
circumference(1)
In this case R redefines what is meant by two_pi
and the circumference function
fails. Why? Because you need to call functions: two_pi()
Error in two_pi * r : non-numeric argument to binary operator
In python
import math
two_pi = 2 * pi
def circumference(r):
print(f"python: The circumference is {two_pi * r}")
# We all know pi is actually 3
def two_pi():
return 6
circumference(1)
Python also redefines what is meant by two_pi
and the circumference function fails.
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 2, in circumference
TypeError: unsupported operand type(s) for *: 'function' and 'int'
In lisp
(setf two_pi (* 2 pi))
(defun circumference (r)
(format t "cl: The circumference is ~a~&" (* two_pi r)))
;; We all know pi is actually only 3.
(defun two_pi ()
6)
(circumference 1)
Common lisp works, because it keeps functions and variables in difference namespaces and knows which one to call at which point.
cl: The circumference is 6.283185307179586d0