------------------------------------------------------------------------------
MC logo
Tom's Lisp Definitions and Scope
[^] Tom's Lisp
------------------------------------------------------------------------------
[Basic Input Format] [Lists, Pairs and Related Operations] [Conditional Evaluation] [Basic Function Definition] [Definitions and Scope] [Functions Which Take Functions] [String Functions] [Exception Handling] [Quoting And Evaluation] [Variable-Length Parameter Lists] [Macro Definitions] [Printing] [The Tomslsp command and its switches] [Index of Standard Functions]

As noted in the input section, identifiers have values which are assigned. The most straightforward way is this:

lsp>(set 'fred 495)
fred
lsp>fred
495
lsp>(set 'fred (cdr '(some days it rains)))
fred
lsp>fred
(days it rains)
The identifier sent to set must be quoted. This is because all function arguments are evaluated before they are sent, so trying to set a an undefined name does not work well:
lsp>(set zomba 17)
**** Error 1: Undefined zomba ****
   (set zomba 17)
The name zomba must be evaluated before it can be sent to set, and this evaluation fails.

Of course, it can be even more interesting if zomba does have a value:

lsp>(set 'zomba 'fred)
zomba
lsp>(set zomba 17)
fred
lsp>zomba
fred
lsp>fred
17
Here, the second set looks as if it is assigning zomba, but it is actually assigning fred, since that is the value of zomba. In any case, the more usual ways of assigning values are:
lsp>(setq boris 56)
boris
lsp>(define mike 34)
mike
lsp>boris
56
lsp>mike
34
The setq operation automatically quotes the name; this form of define, using a plain name instead of a list, is the same as setq.

We previously used define to create functions, be we can also use plain old setq. As noted in the functions section, lambda creates anonymous functions. The set operations gives names to things. So, what happens when you name an anonymous function? You get a named function, just like the ones define creates.

lsp>(setq napple (lambda (no2 lis) (cons (car lis) (cons no2 (cdr lis)))))
napple
lsp>(napple 'wump '(a b c d e))
(a wump b c d e)

Names belong to a scope. The scope of a variable is the portion of the code where it can be used. Scopes are familiar from other languages: the { and } symbols delimit a scope in C/C++/Java. Variables created in a scope disappear at its end. If a name is declared in a scope which matches a name available outside it, the new definition covers the old one to the end of the scope, after which the outer definition reappears. In Tom's Lisp, the program starts in a main scope, and a new scope is introduced by the scope operator. It takes an arbitrary set of forms and evaluates them left to right. These are evaluated in a new scope which ends with the closing ) of the scope construct. The value of the scope is the value of the last form.

lsp>(setq cute 78)
cute
lsp>cute
78
lsp>ugly
**** Error 1: Undefined ugly ****
lsp>(scope (setq ugly 23) ugly)
23
lsp>ugly
**** Error 1: Undefined ugly ****
lsp>(scope (setq cute 100) (* 2 cute))
200
lsp>cute
78
lsp>(* cute 2)
156
In the first scope call, ugly is defined to be 23, but that definition vanishes at the end of the scope call. In the second, cute is given a definition of 100 which covers the definition of 78 in the main scope. When the scope form finishes, the value of cute reverts to 78.

Contexts are created and searched in a stack-like manner. When a scope is entered, it is pushed atop the current scope. When the scope exits, it is popped, so the previous current scope is current again. To find a variable, the system checks the current scope, then moves down the stack until the variable is found.

Scopes for Function Evaluation

Functions also introduce new scopes. When a function is run, its parameters are defined in a new scope. Also, when a function is created, it remembers the scope where it was created, and runs the body in that scope, regardless of the scope from which it is called. This arrangement is called static scoping, and is used by most programming languages. Specifically, a function call:
  1. Evaluates the arguments in the scope of the function call.
  2. Enters a new scope, but stacks it atop the one where the function was created, not where it was called and its parameters evaluated.
  3. Binds the argument values to the parameter names in this new scope.
  4. Evaluates the function body.
  5. Leaves the scope and returns the value produced by the last expression in the body.
For instance:
lsp>(setq inc-amt 10)
inc-amt
lsp>(define (incr x) (+ x inc-amt))
incr
lsp>(incr 5)
15
lsp>(scope (setq inc-amt 250) (incr 5))
15
lsp>(scope (setq inc-amt 250) (+ 5 inc-amt)))
255
Here, the function incr simply adds its parameter to the value of inc-amt. Since the function is created in the main context, the inc-amt in the function body is evaluated in that scope. We enter a new scope, set inc-amt to 250 and call (incr 5). The function returns 15, since it was defined in the main scope where inc-amt has the value 10. When we refer to inc-amt directly in the second scope call (rather than from the body of incr), it has the value 250.

Let

Another way to get scopes in Tom's Lisp is the let construct. The let operation is somewhat traditional in Lisp. In Tom's, it takes the form:
(let ((var1 val1) . . . (varn valn)) form)
This evaluates the form after entering a new scope and setting each variable vari to its value vali.
lsp>(let ((fred 10) (barney (+ fred 5)) (alice "ok"))
--->(cons (* fred barney) alice))
(150 . "ok")
This is the same as:
lsp>(scope (setq fred 10) (setq barney (+ fred 5)) (setq alice "ok")
--->(cons (* fred barney) alice))
(150 . "ok")

Tom's Lisp uses a fairly simple scope model. Production Lisp interpreters may have somewhat greater complexity.

Helper Functions

One application of scope rules is the creation of private helper functions. Here's one way to do it:
(define (sumsqr x y)
    (begin
        (define (sqr x) (* x x))
        (+ (sqr x) (sqr y))
    )
)
The begin construct simply executes each member form in order, left to right.
lsp>(sumsqr 9 18)
405
lsp>(sqr 3)
**** Error 1: Undefined sqr ****
   (sqr 3)
lsp>(sumsqr 5 6)
61
The function sqr is created in sumsqr's own scope each time sumsqr is run. It is destroyed when sumsqr exits, and is never visible anywhere else.

Defining a function within a function requires the inner (private) definition to be evaluated each time the outer (public) function is called. An alternative is to define the functions inside a scope. When the public function runs, it has access to the contents of this scope, but nothing else does.

(setq sumsqr
    (scope
        (define (sqr x) (* x x))
        (lambda (x y) (+ (sqr x) (sqr y)))
    )
)
 
lsp>(sumsqr 3 4)
25
lsp>(sumsqr 10 12)
244
lsp>sqr
**** Error 1: Undefined sqr ****
lsp>
Here the sqr function is defined inside the scope. The last expression of the scope is the lambda operation. Its value becomes the value of the scope and is assigned to sumsqr. When sumsqr is run, it is run in that scope where the function was created, and has access to sqr. But since the scope has exited, the sqr is not available elsewhere.