Jump to content

Recommended Posts

Posted
Yes, Kerry ... you don't need to pass the pt1 and pt2 as arguments to line (in this case). This is due to a "quirkiness" of lisp, in most other programming languages this won't work though.

 

irneb,

quirky or not, it's the way it works ... that's all that matters. The question was asked and I considered those who answered missed the main point. ie 'which variables need to be localised ?'

 

Frankly I didn't read your explaination .. it looked way too confusung for me to demonstrate a simple concept. I've been using this language for a long time and I've always found that a few simple examples, used properly, demonstrate the point quite well for anyone who wants to do a bit of work testing and also a bit of thinking.

 

I'm not detracting from your post, I'm sure it's perfectly correct.

  • Replies 52
  • Created
  • Last Reply

Top Posters In This Topic

  • irneb

    10

  • Se7en

    8

  • Lee Mac

    8

  • cadman6735

    7

Posted

Well that would explain why I liked it; an academic discussion on a simple premise.

Posted
irneb, is this behavior typical for all interpreted languages (Note: You had mentioned VB in your list and I'm trying to shore up my memory[ies] about VB but i haven't had my coffee yet so bear with me please)?
I "think" so. I'm no expert in VB, have much more experience in Java, JavaScripting and Delphi. And as far as I can remember VB & Delphi is highly similar. In which case the scope of localized variables are local to the defining function only (unlike Lisp).

 

irneb,

quirky or not, it's the way it works ... that's all that matters. The question was asked and I considered those who answered missed the main point. ie 'which variables need to be localised ?'

You're correct. In most cases you don't need localized variables, they're just good to have (see just the last 6 points of my previous post). But you can run into situations where unexpected results occur, so it's always better to localize where possible.

 

My rule of thumb: localize everything, except where I don't want to re-initialize the same value over and over. E.g. getting hold of the vla object for the current DWG:

(or *acad* (setq *acad* (vlax-get-acad-object)))
(or *doc* (setq *doc* (vla-get-ActiveDocument *acad*)))

And before someone asks: Why the asterisks? It's simply a Lisp convention to indicate that the variable is to be considered global: http://www.cliki.net/Naming conventions

Posted

Lists are the best example for the need to localize variables.

 

Functions:

(defun c:No (/) (print (setq lst (cons "YES" lst))) (princ))

(defun c:Yes (/ lst) (print (setq lst (cons "YES" lst))) (princ))

eg.

Command: !lst
nil

Command: no

("YES")

Command:
Command: !lst
("YES")

Command: no

("YES" "YES")

Command:
Command: !lst
("YES" "YES")

Command: no

("YES" "YES" "YES")

Command:
Command: !lst
("YES" "YES" "YES")

Command: yes

("YES")

Command:
Command: !lst
("YES" "YES" "YES")

Command: yes

("YES")

Command:
Command: yes

("YES")

Notice when I execute the 'No' routine, I keep receiving a progressivly larger list.

Posted

Notice when I execute the 'No' routine, I keep receiving a progressivly larger list.

 

This is why you told me to localize my variables in a different post, I understand now. If I don't localize I am using memory or space, using up something anyway.

 

So when I run the 'No' routine, I keep building a list and it remains in memory but when I use the 'Yes' routine it does not add to the (lst) or memory it is only accessed or used during the routine then clears itself out. And the 'No' routine maintains a list of ("Yes" "Yes" "Yes")

 

This seems to be a good way of, if you don't know if a variable is being used or exists in a document holding information to be used somewhere else that you don't override that variable or add to the variable list.

 

Is this the concept?

Posted

Alan's example wasn't so much about memory, but rather using old information when you run the program again.

 

Think about a scenario in which you are collecting information into a list (there are many such scenarios), building the list using 'cons' perhaps (hence referencing the lst variable when building).

 

Say we then process this list later on in the program. If the program is run twice, the previously processed information will still be in the list.

 

Example:

 

(defun c:NoLocal ( )

 (while (setq i (getint "\nEnter an Integer! "))
   (setq l (cons i l))
 )

 (princ (strcat "\nThe lowest number entered was: " (itoa (apply 'min l))))
 (princ)
)

 

Command: NoLocal

Enter an Integer! 4

Enter an Integer! 6

Enter an Integer! 8

Enter an Integer! 7

Enter an Integer!

The lowest number entered was: 4

Command:
Command:
NOLOCAL
Enter an Integer! 7

Enter an Integer! 6

Enter an Integer! 9

Enter an Integer! 8

Enter an Integer!

The lowest number entered was: [color=black][b]4[/b][/color]

Wait a minute... 

Posted

Good description! It shows how not localizing causes those "unexpected" results. Of course, if you do want this as a feature in your program ... i.e. have persistent data between calls to the same routine, then this is the easiest way (there are others which are also more "persistent"). In which case I'd just advise you provide a much more descriptive (and unique) name for the variable.

 

Taking the same idea, here's another problem.

(defun c:AddText ( )
 (while (and (setq s (getstring "Enter a string to add to the list: ")) (/= s "")) (setq l (cons s l)))
 (foreach s (reverse l) (princ (strcat s "\t")))
 (princ)
)

Now try to run both the AddText or the NoLocal in the same DWG. The second (and further) issues will always cause errors, since the list will have a mixture of text & integers.

Posted

Sorry guys, we are talking about different things.

You are discussing and demonstrating some of the risks associated with global variables ... generally true and understood.

 

The original problem and the code I posted in response does NOT use global variables.

The Pt1 and Pt2 are bound as locals to the parent main routine. They CAN be used by the Line routine without problem and without passing the parameters and without any risk of poluting the environment and without any risk of contamination.

 

I'll demonstrate :

 

;; codehimbelonga kdub 20100915
;; establish some global variables
(setq v   '("I'm a Global Variable")
     val 100.0
)
;;
(defun _foodles ()
 (setq v (cons val v))
)
;;
(defun c:doit (/ v val)
 (setq v '()
       val 0
 )
 (repeat 5
   (setq val (1+ val))
   (_foodles)
   (prompt (strcat "\n" (vl-princ-to-string v)))
 )
 (princ)
)
;; 
(defun c:doit2 ()
 (prompt (strcat "\n Val: " (vl-princ-to-string val)))
 (prompt (strcat "\n V  : " (vl-prin1-to-string v)))
 (princ)
)

 

Pasted from the CommandLine

Command:

Command: doit2

Val: 100.0

V : ("I'm a Global Variable")

Command:

Command: Doit

(1)

(2 1)

(3 2 1)

(4 3 2 1)

(5 4 3 2 1)

Command:

Command: doit2

Val: 100.0

V : ("I'm a Global Variable")

Command:

 

Because variables V and Var are declared local to the main routine they can be used with impunity in any sub-routines without risk.

While I'm NOT advocating that anyone change the way they write code I believe it is critical that we as programmers at least understand some of the options and constraints.

Posted
While I'm NOT advocating that anyone change the way they write code I believe it is critical that we as programmers at least understand some of the options and constraints.
Very True! As you demonstrate, the variable could be localized in one function only while still used in another (as if global). Of course this needs to be understood, and the _foodles function should not be reused by other functions without a full understanding of how the values inside v and var should be formatted. And that's my biggest reason for rather using arguments: it becomes quite difficult to understand what's occurring when the function is operating on a global variable. Especially if you haven't written the code yourself.

 

One thing to note however: using localized variables and arguments is not as efficient as using global. The reason behind this is that memory allocation takes time, a table of RAM addresses needs to be checked to find a free space, then modified to indicate that the space is allocated to that variable's name. With global vars it's not necessary as the variable is already allocated. This becomes an issue if the function is going to be called numerous times in a loop or (even worse) recursion. For that reason you can then "wrap" the function inside another. BTW, you can even declare a defun as "localized" inside another - works exactly like variables do.

 

So I'd say to the OP: It's always safer to use localized (even when it's not necessary). Arguments have a similar benefit, but also makes for "easier to understand" code. The benefit of using "global" variables (or even "partly-global" :wink:) is efficiency in speed (in loops & recursion) and ram usage (in recursion).

Posted

As an example of why I say arguments and / or localized variables are extremely inefficient (although slightly easier) for recursion, add the following functions to Kerry's code:

(defun _foodles1 (v val)
 (setq v (cons val v))
)

(defun recurse ()
 (cond
   ((<= val 5)
     (_foodles)
     (prompt (strcat "\n" (vl-princ-to-string v)))
     (setq val (1+ val))
     (recurse)
   )
 )
)

(defun recurse1 (v val)
 (cond
   ((<= val 5)
     (setq v (_foodles1 v val))
     (prompt (strcat "\n" (vl-princ-to-string v)))
     (recurse1 v (1+ val))
   )
 )
)

(defun c:doitR (/ v val)
 (setq val 1)
 (recurse)
 (princ)
)

(defun c:doitR1 (/ v)
 (recurse1 v 1)
 (princ)
)

It produces the same results:

Command: doit2
Val: 100.0
V  : ("I'm a Global Variable")
Command: doitr
(1)
(2 1)
(3 2 1)
(4 3 2 1)
(5 4 3 2 1)
Command: doitr1
(1)
(2 1)
(3 2 1)
(4 3 2 1)
(5 4 3 2 1)
Command: doit2
Val: 100.0
V  : ("I'm a Global Variable")

So you'd think there's no difference? Wrong, the doit2 calls recurse1 with the (non-initialized - as it's already nil) list v and just a constant (no need for setting the value first - see later). Now recurse1 does the actual "looping" but in recursive style: It checks if it's reached the end of the loop, if not continues with calling _foodles1 with arguments for the list and the current value, and sets the current list v to the result. Prints the list and calls itself again (i.e. recursion) with modified values. So there's now at least 5 separate lists (v) and 5 separate values (val) in RAM. It does get cleared after all of recurse1's calls have completed, but up until its last, all the versions of these variables are contained in RAM. Now imagine doing something like this with 1000's of recursions.

 

The DoitR, recurse & _foodles functions use a "partially global" variable setup. For this reason DoitR needs to declare both the list (v) and the value (var), as well as initializing var to a start point (there's no other way to let recurse know from where to start). Recurse doesn't need to assign _foodles's result to the list, as _foodles does so itself. But recurse can't simply call itself with an incremented value (since it doesn't accept arguments), thus it needs to increment the value inside var before it recurses itself :?

 

But this method only uses the one single copy of v and val. It never allocates another set of RAM address space, making for both a speed improvement as well as using much less RAM. The above is a bit of a forced example, but it's done to try and prove the point by showing the extreme situation.

Posted

@irneb

*gack!* okay, assuming that you know that you have (badly) modeled two forms of a recursive procedure: a procedure using a "linear recursive process" (recurse1) and one using an "iterative process" (recurse) because you stated that this was a `forced example' but I wanted to say that by doing that you have just made me think about something I did a while ago and I think the "answer" to my problem just came to me all of a sudden (I did a write-up and got funny results). So, before I do the research, here is an early `Thank you'. :)

Posted

I must say, reading all of your posts, I love how this became a great discussion of experianced programmers. I am learning alot.

 

As I gain more exposure and experiance, I return to these posts for a deeper understanding, this is great stuff.

 

Thanks to all.

Posted

Yes this is very deep and really way beyond anything useful other then for fun (a lot of fun).

 

Can we go back a bit? I want to go back to the stack description you had in post 19. [ http://www.cadtutor.net/forum/showthread.php?52365-localizing-variables&p=354916&viewfull=1#post354916 ]. You have this line: " VAR=(nil "Test")" ...where did nil come from? that looks to me like a mini stack within a stack (I know that this is just a representation and not what the stack looks like but that rep. is giving me a headache trying to fit it into how i think of a stack--an ``array'' of sorts--and how it acts; this rep. gives me the impression that there is some sort of...what would be a good word..."lookup" happening).

Posted

To understand a stack you need to figure out some other (reasonably trivial) data structures. Lisp's lists uses what in other languages is called a linked-list (as opposed to those languages' default array structures). Now a queue and a stack are derivatives of a linked-list (or rather, that's how they're usually implemented as it's most efficient). The major difference is a queue can only receive new items at its tail, and remove items from its head. A stack can only receive and remove items from its head. So in lisp you'd implement a queue through using append (called push) to add, and car+cdr to extract (called pop). A stack would be implemented using cons to push while still car+cdr to pop.

 

Now, my post was being a bit simplistic. Actually variables have a "table" structure, in other languages this would be a stack of records. In lisp there's no such thing as a record, closest match is an item in an association list (i.e. similar to the DXF data list as obtained from entget). So when you use something like (setq var 100) the lisp interpreter checks this association list (probably using assoc on the variable's name). If it finds that it's already used it "pushes" a new blank memory reference (other languages call this a pointer) on to it, if not it creates a new "entry" in the association list with a pointer to a non-used RAM address. But unlike other languages lisp initializes the RAM to have a blank value (i.e. nil) before it sets its value to 100 - not really since lisp uses intermediate memory addressing (see later) but this is the effect. With other languages you may find garbage as the RAM would simply be linked to (i.e. whatever value(s) were there is now there as well). The same thing happens when you declare a local variable or an argument, only they don't get a new value after being "initialized to nil".

 

The intermediate addressing is yet another table which lisp creates to split the RAM up into chunks. This allows lisp to address a modifyable size to each variable. This is also the main reason lisp works better with lists and other languages work better with arrays - and array is most efficient when you have direct memory addressing. When a variable is declared, it gets an allocation in this memory list. It's item is not filled in yet, and the position & size pointers are then set to some arb value such as negative. Thus when the variable is accessed it returns a nil value. Only when some value is given to the variable does this memory list item get real values like address starting at RAM position #### and 32bits for an integer.

 

It sounds quite complex, and it is. Other languages like C don't do this automatically, the programmer needs to perform such things through his own code. C can only address ram directly from its "automatic" variable declarations. While the C method makes for most things being a lot faster, it has some drawbacks:

 

  • A variable needs to know exactly what type of value it will contain at the definition time. Otherwise C won't know how much RAM to give to the variable. Lisp has no such problem, since it allocates more as and when needed.
  • Lisp has the capability of garbage collection. C has no such thing, and it would be extremely difficult to implement in C. The C programmer has to remember to release any RAM they've manually allocated to make something like a linked list, this is usually where the ominous memory leak comes from ... the programmer forgot to release the RAM. With Lisp it's nearly completely fool-proof, there are situations which don't work nicely such as using the vlax-create-object, but they're scarce (and usually due to something outside lisp).

Posted

I'm still with ya. Very cool.

 

So in essence the "Anaphoirc IF" (Paul Grahm's On Lisp--I will summarize it below so you dont have to go looking for it, if you dont know it) does not apply (especially to AutoLISP; im not a common lisper so i wont assume WHY or IF for common lisp.) for performance reasons--because it still allocates space (32bits) for a variable even if it gets immediately set to nil--but because of logic/or program flow reasons.

 

This was a very good thread/read. I really enjoyed it. Thank you.

 

 

***

The Anaphoric IF

 

Something like:

(if (not (eq (setq myvar(<procedure call returning something>) 'RIGHT))
 (setq myvar 'LEFT) 
 )

 

...create and set a variable, then check that variables contents to see if it is correct. If it is not, then overwrite the variable with the correct information.

 

The anaphoric if would be to do something like:

(setq myvar
     (cond
       ( (eq (<procedure call returning something>?) 'RIGHT) 
         'RIGHT)
       ( 'LEFT )
       )
     )

 

Run a test to check the results to one of two conditions and fill the variable with it.

***

Posted

A very interesting read indeed Irneb, many thanks for taking the time to share your knowledge - it is truly appreciated.

 

If I may summarise the information ever so slightly so that it is aligned in my head, am I correct in noting that:

 

[color=blue];; Creating a Global
(setq var 123)[/color]

[color=green];; Stack for var looks something like (using idea of association list)
( var . [ 123 ] )[/color]

[color=blue];; Define a function
(defun foo ( / var )[/color]

[color=green];; Space is added (pushed?) to the start of the stack (space to be later determined by variable data type) 
( var . [ nil 123 ] )[/color]

[color=blue];; Set variable within function:
(setq var "abc")
[/color]
[color=green];; Stack now looks like:
( var . [ "abc" 123 ] )[/color]

[color=blue];; End function
)[/color]

[color=green];; Data entry for 'var' in stack is 'popped' [?], leaving previous entry:
( var . [ 123 ] )[/color]

Also, I realise the issues associated with allocating memory in C and not releasing it, and perhaps with LISP in cases using vlax-create-object for objects outside of the AutoCAD scope. Does this memory eventually get released, say, upon shut-down?

 

Once again, I appreciate the time you are devoting to this thread.

 

Lee

Posted

Shutdown of the PC? yes.

Shutdown of the application? its supposed to but applications dont always "clean up" properly either.

Posted
Shutdown of the PC? yes.

Shutdown of the application? its supposed to but applications dont always "clean up" properly either.

 

Gotcha, thanks.

Posted

You've got the gist of it yes. It's a pleasure! Glad those years in varsity came in handy :)

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Unfortunately, your content contains terms that we do not allow. Please edit your content to remove the highlighted words below.
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • Create New...