Thursday, May 28, 2009

AutoLISP variable scope and "local globals"

I've been programming with AutoLISP for around 15 years, but I never realized there is more to variable scope in AutoLISP/Visual LISP than a simple local vs. global scope dichotomy.

As a primer to those who are unfamiliar with variable scope, here is a little example you can try at the AutoCAD command line. I've intersperse the example with my comments that have semi-colons preceding them:


;;; AutoLISP variable scope tutorial
;;; Copyleft 2009 GNU GPL style by Thomas Gail Haws
;;;
;;; Type these code snippets at the AutoCAD command line
;;; to observe the effect of variable scope.
;;;
;;; First see what the TGH symbol (variable) returns.
Command: !tgh
nil
;;; Set tgh to 0
Command: (setq tgh 0)
0
;;; Write a function that sets tgh to 1
Command: (defun tgh1 () (setq tgh 1))
TGH1
;;; See that writing the function didn't change tgh
Command: !tgh
nil
;;; Invoke the function tgh1
Command: (tgh1)
1
;;; See that invoking the function changed tgh
Command: !tgh
1
;;; Write another function that changes tgh,
;;; but make it a LOCAL version of tgh that is changed.
Command: (defun tgh2 (/ tgh) (setq tgh 2))
TGH2
;;; See that defining the function didn't change
;;; the "outside" (global) version of tgh
Command: !tgh
1
;;; Invoke the tgh2 function.
;;; See it returns 2 because the last thing it did was
;;; set ITS LOCAL VERSION of tgh to 2
Command: (tgh2)
2
;;; See that invoking the tgh2 function didn't change
;;; the "outside" (GLOBAL) version of tgh.
Command: !tgh
1


What I discovered this year is that a variable that's global to function A can be local to a function B that calls A. In other words, if you don't make an "in-house" version of a variable in one function A, another function B that calls A can still make A work with B's local version of the variable. And that B/A version is not available to or visible to anything outside of B.

Here's an analogy. It is a lovely spring day in the town of Session. The flowers are in bloom. The East and the West family live in three bedroom houses, each with a brothers room, a sisters room, and a parents room. If we want to know the air particulate load at any location around the East or the West house, we need to know whether the location has its own inside (local) air, or instead follows and modifies the outside (global) air. For simplicity, there are no open windows--only open doors--, and particulate loads disperse and equalize immediately. Let's jump right into the AutoLISP code, with the story continuing as comments indicated by lines that start with semi-colons (;)


(defun Story ()
;;; To simplify reporting and seting pload, make two little functions first.
(defun princpload (location)
(princ (strcat "\nCurrent particulate load in " location " is "))
(princ pload)
(princ ".")
)
(defun setqpload (location)
(princ "\n")
(princpload location)
(setq pload (getint (strcat "\nNew particulate load in " location ": ")))
(princpload location)
(princ)
)
;;; Session has an ambient springtime air with a particulate load of 100.
(setq pload 100)
;;;Write a function to report and change the pload in Session.
(defun Session () (setqpload "Session"))
;;; The East family leaves their outside doors wide open.
;;; So their pload matches and changes the Session pload.
;;; We put the sisters and brothers rooms inside the house.
(defun East ()
(setqpload "East")
(EastSisters)
(setqpload "East")
(EastBrothers)
(setqpload "East")
)
;;; The West family keeps all their outside doors closed.
;;; So their pload is a local "inside" value independent of Session's pload.
;;; We make their pload local by adding in it the West function arguments list
;;; after the / forward slash.
;;; We put the sisters and brothers rooms inside the house.
(defun West (/ pload)
(setqpload "West")
(WestBrothers)
(setqpload "West")
(WestSisters)
(setqpload "West")
)
;;; All brothers have allergies, so their doors are always closed.
;;; We make their ploads local.
(defun EastBrothers (/ pload) (setqpload "East brothers"))
(defun WestBrothers (/ pload) (setqpload "West brothers"))
;;; All sisters care nothing about particulates, so their doors are open.
;;; We leave their ploads global.
(defun EastSisters () (setqpload "East sisters"))
(defun WestSisters () (setqpload "West sisters"))
)
(Story)


Now let's consider two scenarios, one with the brother rooms and a second with the sister rooms.

First consider the brother rooms. It should be simply intuitive that if a dust bag explodes in a brother room, it will affect only the particulate load in that room, and if the Session particulate load drops, it will affect neither brother room. Now load (or paste) the code above into a drawing session and then follow the steps below to see how the code behaves at the AutoCAD command prompt in the brothers scenario:


Command: (Session)

Current particulate load in Session is 100.
New particulate load in Session: 100
Current particulate load in Session is 100.

Command: (East)

Current particulate load in East is 100.
New particulate load in East: 100
Current particulate load in East is 100.

Current particulate load in East sisters is 100.
New particulate load in East sisters: 100
Current particulate load in East sisters is 100.

Current particulate load in East is 100.
New particulate load in East: 100
Current particulate load in East is 100.

Current particulate load in East brothers is nil.
New particulate load in East brothers: 500
Current particulate load in East brothers is 500.

Current particulate load in East is 100.
New particulate load in East: 100
Current particulate load in East is 100.

Command: (West)

Current particulate load in West is nil.
New particulate load in West:
Current particulate load in West is nil.

Current particulate load in West brothers is nil.
New particulate load in West brothers: 600
Current particulate load in West brothers is 600.

Current particulate load in West is nil.
New particulate load in West:
Current particulate load in West is nil.

Current particulate load in West sisters is nil.
New particulate load in West sisters:
Current particulate load in West sisters is nil.

Current particulate load in West is nil.
New particulate load in West:
Current particulate load in West is nil.


While their functions were running, the brother room particulate loads were independently higher than 100. But be aware that now the functions have ended, the independent loads have also gone away.

For scenario 2, consider what happens if a smaller dust bag explodes in the sister rooms. Each house (exclusive of its brother room) now has a particulate load to match its sister room. And the town of Session now has a particulate load to match which sister room? Let's see.


Command: (Session)

Current particulate load in Session is 100.
New particulate load in Session: 100
Current particulate load in Session is 100.

Command: (East)

Current particulate load in East is 100.
New particulate load in East: 100
Current particulate load in East is 100.

Current particulate load in East sisters is 100.
New particulate load in East sisters: 200
Current particulate load in East sisters is 200.

Current particulate load in East is 200.
New particulate load in East: 200
Current particulate load in East is 200.

Current particulate load in East brothers is nil.
New particulate load in East brothers:
Current particulate load in East brothers is nil.

Current particulate load in East is 200.
New particulate load in East: 200
Current particulate load in East is 200.

Command: (West)

Current particulate load in West is nil.
New particulate load in West:
Current particulate load in West is nil.

Current particulate load in West brothers is nil.
New particulate load in West brothers:
Current particulate load in West brothers is nil.

Current particulate load in West is nil.
New particulate load in West:
Current particulate load in West is nil.

Current particulate load in West sisters is nil.
New particulate load in West sisters: 300
Current particulate load in West sisters is 300.

Current particulate load in West is 300.
New particulate load in West: 300
Current particulate load in West is 300.

Command: (Session)

Current particulate load in Session is 200.
New particulate load in Session: 200
Current particulate load in Session is 200.


Answer: The Session particulate load now matches the East sister room (and the East house) particulate load. This is because both the East sister room door and the East house doors are open.

The bottom line: Even though the West sisters have their door open and are using a global pload variable, the fact that the West house doors are closed keeps the West sisters from using the Session pload variable. They are using the "house" global pload variable. Since the WestSisters function is called by the West function that has pload declared as a local symbol, the WestSister function's global scope for pload can't get outside the West function.

No comments: