2010 April 05
Lisp was the first language to support 'productivity boosting' features like Automatic Storage Management (or Garbage Collection), Dynamic Typing and First-Class Functions. It was also the first language to introduce an Interactive Environment for software development. (Commonly known as the toplevel or REPL). Newer languages like Java, Python and Ruby are trying to catchup with Lisp by reimplementing some or all of these features. They have a long way to go as Lisp already has half-a-century of history behind it! It is no surprise that Common Lisp, the most popular dialect of Lisp, offers a rich collection of tools for increasing programmer productivity at the toplevel. Most of these are absent in other 'interactive' languages. They seemed so useful to me that I decided to bring together these tools under a single, easily accessible manual, for the benefit of new Common Lisp programmers.
trace is used to print the following information about an invoked function:
These values are printed to the trace output. This is an output stream which is the value of the dynamic variable *trace-output*.
Example:
> (defun fact (n)
(if (zerop n)
1
(* n (fact (- n 1)))))
> (fact 4)
24
; Add fact for tracing
> (trace fact)
> (fact 4) ; The trace output is implementation dependent. This example uses CLisp.
1. Trace: (FACT '4)
2. Trace: (FACT '3)
3. Trace: (FACT '2)
4. Trace: (FACT '1)
5. Trace: (FACT '0)
5. Trace: FACT ==> 1
4. Trace: FACT ==> 1
3. Trace: FACT ==> 2
2. Trace: FACT ==> 6
1. Trace: FACT ==> 24
24
To remove a function from being traced, pass its name to the untrace macro. If untrace is invoked with no arguments, all functions that are currently traced will be untraced.
> (untrace fact)
> (fact 4)
24
Both trace and untrace can take an arbitrary number of arguments.
A macro that is related to trace is step. It implements a tiny debugger which allows the programmer to interactively step through the evaluation of an expression so that he can see everything that takes place.
; Stepping through the evaluation of (fact 2). The programmer types :s to move
; from one step to the next
> (step (fact 2))
step 1 --> (FACT 2)
Step 1 [20]> :s
step 2 --> 2
Step 2 [21]> :s
step 2 ==> value: 2
step 2 --> (IF (ZEROP N) 1 (* N (FACT #)))
Step 2 [22]> :s
step 3 --> (ZEROP N)
Step 3 [23]> :s
step 4 --> N
Step 4 [24]> :s
step 4 ==> value: 2
step 3 ==> value: NIL
step 3 --> (* N (FACT (- N 1)))
Step 3 [25]> :s
step 4 --> N
Step 4 [26]> :s
step 4 ==> value: 2
step 4 --> (FACT (- N 1))
Step 4 [27]> :s
step 5 --> (- N 1)
Step 5 [28]> :s
step 6 --> N
Step 6 [29]> :s
step 6 ==> value: 2
step 6 --> 1
Step 6 [30]> :s
step 6 ==> value: 1
step 5 ==> value: 1
step 5 --> (IF (ZEROP N) 1 (* N (FACT #)))
Step 5 [31]> :s
step 6 --> (ZEROP N)
Step 6 [32]> :s
step 7 --> N
Step 7 [33]> :s
step 7 ==> value: 1
step 6 ==> value: NIL
step 6 --> (* N (FACT (- N 1)))
Step 6 [34]> :s
step 7 --> N
Step 7 [35]> :s
step 7 ==> value: 1
step 7 --> (FACT (- N 1))
Step 7 [36]> :s
step 8 --> (- N 1)
Step 8 [37]> :s
step 9 --> N
Step 9 [38]> :s
step 9 ==> value: 1
step 9 --> 1
Step 9 [39]> :s
step 9 ==> value: 1
step 8 ==> value: 0
step 8 --> (IF (ZEROP N) 1 (* N (FACT #)))
Step 8 [40]> :s
step 9 --> (ZEROP N)
Step 9 [41]> :s
step 10 --> N
Step 10 [42]> :s
step 10 ==> value: 0
step 9 ==> value: T
step 9 --> 1
Step 9 [43]> :s
step 9 ==> value: 1
step 8 ==> value: 1
step 7 ==> value: 1
step 6 ==> value: 1
step 5 ==> value: 1
step 4 ==> value: 1
step 3 ==> value: 2
step 2 ==> value: 2
step 1 ==> value: 2
2
describe displays information about an object to the stream. For example, describe of a symbol might show the symbol's value, its definition, and each of its properties. describe of a float might show the number's internal representation in a way that is useful for tracking down round-off errors. describe is a good way to learn about how Lisp systems work internally. The output of describe is implementation dependent. The following examples uses CLISP from SLIME.
> (describe #'fact)
#<FUNCTION FACT (N) (DECLARE (SYSTEM::IN-DEFUN FACT)) (BLOCK FACT (IF # 1 #))> is an interpreted function.
Argument list: (N)
> (describe 'fact)
FACT is the symbol FACT, lies in #<PACKAGE COMMON-LISP-USER>, is accessible in 1 package COMMON-LISP-USER, names a function, has 1 property
SYSTEM::DEFINITION
.
For more information, evaluate (SYMBOL-PLIST 'FACT).
#<PACKAGE COMMON-LISP-USER> is the package named COMMON-LISP-USER. It has 2
nicknames CL-USER, USER.
It imports the external symbols of 2 packages ASDF, COMMON-LISP and exports no symbols,
but no package uses these exports.
#<FUNCTION FACT (N) (DECLARE (SYSTEM::IN-DEFUN FACT)) (BLOCK FACT (IF # 1
#))> is an interpreted function.
Argument list: (N)
> (describe 10.45)
10.45 is a float with 24 bits of mantissa (single-float).
Calling describe on cons, defun, nil etc can throw light on how they are implemented:
> (describe 'nil)
NIL is the empty list, the symbol NIL, lies in #<PACKAGE COMMON-LISP>, is
accessible in 20 packages ASDF, CLOS, COMMON-LISP, COMMON-LISP-USER, EXPORTING, EXT, FFI, LDAP, MONITOR, POSIX,PXREF, RAWSOCK, REGEXP, SCREEN, SLIME-NREGEX, SWANK, SWANK-BACKEND,
SWANK-IO-PACKAGE, SWANK-LOADER, SYSTEM, a constant, value: NIL, names a type,
has 2 properties SYSTEM::INSTRUCTION, SYSTEM::TYPE-SYMBOL.
For more information, evaluate (SYMBOL-PLIST 'NIL).
#<PACKAGE COMMON-LISP> is the package named COMMON-LISP. It has 2 nicknames
LISP, CL.
It imports the external symbols of 1 package CLOS and exports 978 symbols to
18 packages ASDF, SWANK, MONITOR, PXREF, SLIME-NREGEX, SWANK-BACKEND, SWANK-LOADER, LDAP, RAWSOCK, REGEXP, POSIX, EXPORTING, FFI, SCREEN, CLOS, COMMON-LISP-USER, EXT, SYSTEM.
NIL [see above]
describe is especially useful for looking at the internals of structures. describe shows the fields of a structure in a more readable format than the default #S notation:
> (defstruct person name age dob)
PERSON
> (setf dave (make-person :name "Dave" :age 35 :dob "30-MAR-1975"))
#S(PERSON :NAME "Dave" :AGE 35 :DOB "30-MAR-1975")
> (describe dave)
#S(PERSON :NAME "Dave" :AGE 35 :DOB "30-MAR-1975") is a structure of type
PERSON
.
Slots:
NAME = "Dave"
AGE = 35
DOB = "30-MAR-1975"
"Dave" is a simple 1 dimensional array (vector) of CHARACTERs, of size 4 (a
ISO-8859-1 string).
35 is an integer, uses 6 bits, is represented as a fixnum.
"30-MAR-1975" is a simple 1 dimensional array (vector) of CHARACTERs, of
size 11 (a ISO-8859-1 string).
inspect is an interactive version of describe. The nature of the interaction is implementation-dependent, but the purpose of inspect is to make it easy to wander through a data structure, examining and modifying parts of it. For instance, inspect may let you to see the structure of an object by simply pointing the mouse on it. The following sample shows how the inspect implementation in CLISP allows the user to inspect a structure using text commands:
> (inspect dave)
#S(COMMON-LISP-USER::PERSON :NAME "Dave" :AGE 35 :DOB "30-MAR-1975"): structure object
type: COMMON-LISP-USER::PERSON
0 [NAME]: "Dave"
1 [AGE]: 35
2 [DOB]: "30-MAR-1975"
INSPECT-- type :h for help; :q to return to the REPL ---> 0
"Dave": String
dimension: 4
element-type: CHARACTER
no fill pointer
not displaced
0: #\D
1: #\a
2: #\v
3: #\e
INSPECT-- type :h for help; :q to return to the REPL ---> :u
#S(COMMON-LISP-USER::PERSON :NAME "Dave" :AGE 35 :DOB "30-MAR-1975"): structure object
type: COMMON-LISP-USER::PERSON
0 [NAME]: "Dave"
1 [AGE]: 35
2 [DOB]: "30-MAR-1975"
INSPECT-- type :h for help; :q to return to the REPL ---> 1
35: atom
type: (INTEGER 0 16777215)
class: #1=#<BUILT-IN-CLASS INTEGER>
INSPECT-- type :h for help; :q to return to the REPL ---> :u
#S(COMMON-LISP-USER::PERSON :NAME "Dave" :AGE 35 :DOB "30-MAR-1975"): structure object
type: COMMON-LISP-USER::PERSON
0 [NAME]: "Dave"
1 [AGE]: 35
2 [DOB]: "30-MAR-1975"
INSPECT-- type :h for help; :q to return to the REPL ---> 2
"30-MAR-1975": String
dimension: 11
element-type: CHARACTER
no fill pointer
not displaced
0: #\3
1: #\0
2: #\-
3: #\M
4: #\A
5: #\R
INSPECT-- type :h for help; :q to return to the REPL ---> 0
#\3: atom
type: STANDARD-CHAR
class: #1=#<BUILT-IN-CLASS CHARACTER>
INSPECT-- type :h for help; :q to return to the REPL ---> :u
"30-MAR-1975": String
dimension: 11
element-type: CHARACTER
no fill pointer
not displaced
0: #\3
1: #\0
2: #\-
3: #\M
4: #\A
5: #\R
INSPECT-- type :h for help; :q to return to the REPL ---> :u
#S(COMMON-LISP-USER::PERSON :NAME "Dave" :AGE 35 :DOB "30-MAR-1975"): structure object
type: COMMON-LISP-USER::PERSON
0 [NAME]: "Dave"
1 [AGE]: 35
2 [DOB]: "30-MAR-1975"
INSPECT-- type :h for help; :q to return to the REPL ---> :q
The break function allows us to go directly to the debugger, so that we can examine the state of a computation when an error has occured. The argument to break is a string with optional format controllers. The string will be printed as a message when the debugger is entered:
> (break "You have entered the debugger with arguments: ~s" '(1 2 3))
You have entered the debugger with arguments: (1 2 3)
[Condition of type SIMPLE-CONDITION]
Restarts:
0: [CONTINUE] Return from BREAK loop
1: [ABORT-REQUEST] Abort handling SLIME request.
2: [ABORT] Return to SLIME top-level.
3: [ABORT] ABORT
Typing 0 will exit the debugger. The way the debugger interacts with the user will differ between implementations.
As an extented example, let us re-write the fact function so that it enters the debugger when n is zero.
;; Sample code from Common Lisp: Gentle Introduction to Symbolic Computation
;; by David S. Touretzky
> (defun fact (n)
(if (zerop n)
(break "n is zero")
(* n (fact (- n 1)))))
> (fact 4)
; CLisp with SLIME gives the following output, including the backtrace.
n is zero
[Condition of type SIMPLE-CONDITION]
Restarts:
0: [CONTINUE] Return from BREAK loop
1: [ABORT-REQUEST] Abort handling SLIME request.
2: [ABORT] Return to SLIME top-level.
3: [ABORT] ABORT
Backtrace:
0: #<FUNCTION FACT (N) (DECLARE (SYSTEM::IN-DEFUN FACT)) (BLOCK FACT (IF (ZEROP N) (BREAK "n is zero") (* N #)))> 1
1: #<SPECIAL-OPERATOR IF>
2: #<FUNCTION FACT (N) (DECLARE (SYSTEM::IN-DEFUN FACT)) (BLOCK FACT (IF (ZEROP N) (BREAK "n is zero") (* N #)))> 1
3: #<SPECIAL-OPERATOR IF>
4: #<FUNCTION FACT (N) (DECLARE (SYSTEM::IN-DEFUN FACT)) (BLOCK FACT (IF (ZEROP N) (BREAK "n is zero") (* N #)))> 1
5: #<SPECIAL-OPERATOR IF>
6: #<FUNCTION FACT (N) (DECLARE (SYSTEM::IN-DEFUN FACT)) (BLOCK FACT (IF (ZEROP N) (BREAK "n is zero") (* N #)))> 1
7: #<SPECIAL-OPERATOR IF>
8: #<FUNCTION FACT (N) (DECLARE (SYSTEM::IN-DEFUN FACT)) (BLOCK FACT (IF (ZEROP N) (BREAK "n is zero") (* N #)))> 1
9: #<SYSTEM-FUNCTION EVAL>
10: #<COMPILED-FUNCTION SWANK::EVAL-REGION>
11: #<COMPILED-FUNCTION SWANK::LISTENER-EVAL-1>
12: #<COMPILED-FUNCTION #:|273 275 (DEFINTERFACE CALL-WITH-SYNTAX-HOOKS (FN) ...)-31-3-1-1|>
13: #<COMPILED-FUNCTION SWANK::CALL-WITH-BUFFER-SYNTAX>
14: #<COMPILED-FUNCTION SWANK:LISTENER-EVAL>
15: #<SYSTEM-FUNCTION EVAL>
16: #<COMPILED-FUNCTION SWANK::EVAL-FOR-EMACS-1>
17: #<COMPILED-FUNCTION #:|472 477 (DEFINTERFACE CALL-WITH-DEBUGGER-HOOK (HOOK FUN) ...)-52-3-1-1|>
18: #<COMPILED-FUNCTION SWANK::EVAL-FOR-EMACS>
19: #<COMPILED-FUNCTION SWANK::READ-FROM-EMACS>
20: #<COMPILED-FUNCTION SWANK::HANDLE-REQUEST-1>
21: #<COMPILED-FUNCTION SWANK::CALL-WITH-CONNECTION-1>
22: #<COMPILED-FUNCTION SWANK::CALL-WITH-REDIRECTED-IO>
23: #<COMPILED-FUNCTION SWANK::MAYBE-CALL-WITH-IO-REDIRECTION>
24: #<COMPILED-FUNCTION SWANK::CALL-WITH-CONNECTION>
25: #<COMPILED-FUNCTION SWANK::HANDLE-REQUEST>
26: #<COMPILED-FUNCTION SWANK::SIMPLE-SERVE-REQUESTS-2>
27: #<COMPILED-FUNCTION SWANK::SIMPLE-SERVE-REQUESTS>
28: #<COMPILED-FUNCTION SWANK::SERVE-REQUESTS>
29: #<COMPILED-FUNCTION SWANK::SERVE-CONNECTION>
30: #<COMPILED-FUNCTION SWANK::SETUP-SERVER-SERVE>
31: #<COMPILED-FUNCTION SWANK::SETUP-SERVER>
32: #<COMPILED-FUNCTION SWANK:START-SERVER>
33: #<SYSTEM-FUNCTION SYSTEM::READ-EVAL-PRINT> 2
; Inputing 0 will produce the following prompt, where I am allowed to provide a value to return
*: NIL is not a number
[Condition of type SIMPLE-TYPE-ERROR]
Restarts:
0: [USE-VALUE] You may input a value to be used instead.
1: [ABORT-REQUEST] Abort handling SLIME request.
2: [ABORT] Return to SLIME top-level.
3: [ABORT] ABORT
; Selecting 0 gave me a prompt where I can enter 1 and terminate the functions
Use instead: 1
24
A popular idiom used by programmers to measure efficiency of a function is:
start_time = get_time_in_milliseconds()
function_to_check()
end_time = get_time_in_milliseconds()
print("function took " + (end_time - start_time) + " milliseconds to execute)
Common Lisp wraps this useful idiom into a single macro called time. time evaluates its arguments and print various timing data and information as elapsed real time, machine run time, and storage management statistics. The following example shows two implementations of a function that add the squares of n numbers. It also demonstrates how time helps us in measuring and improving the time/space efficiency of the algorithm. We measure the performance of the first implementation with time and find it not as much optimized as we want. Then we use a clever formula to create a much better performing function:
> (defun add-n-squares (n)
(do ((i 0 (+ i 1))
(sum 0 (+ sum (* i i))))
((> i n) sum)))
> (time (add-n-squares 10000))
Real time: 0.0156261 sec.
Run time: 0.015625 sec.
Space: 219004 Bytes
333383335000
;; Optimized function that executes faster using less amount of memory:
> (defun add-n-squares (n)
"Find the sum of squares upto n using the formula n(n + 1)(2n + 1) / 6"
(/ (* n (* (+ n 1) (+ (* 2 n) 1))) 6))
> (time (add-n-squares 10000))
Real time: 0.0 sec.
Run time: 0.0 sec.
Space: 44 Bytes
333383335000
The room function prints the information about the state of internal storage and its management. This might include descriptions of the amount of memory in use and the degree of memory compaction, possibly broken down by internal data type if that is appropriate. The nature and format of the printed information is implementation-dependent. The intent is to provide information that a programmer might use to tune a program for a particular implementation.
The following output was produced by calling room from CLISP:
> (room)
Bytes permanently allocated: 90624
Bytes currently in use: 3258228
Bytes available until next GC: 180536
3258228
180536
A language may claim to support 'dynamic-interactive development' by just providing a simple REPL prompt. Common Lisp lives up to the claim by offering tools that make interactive development easy and as much productive as possible.
References: