Author Topic: About Defun  (Read 4314 times)

0 Members and 1 Guest are viewing this topic.

Coder

  • Swamp Rat
  • Posts: 827
About Defun
« on: July 11, 2012, 07:52:43 AM »
Hello guys . :)

When do we localize the defun function in a routine ?

e.g.

Code: [Select]
(defun c:test ( / *error* makepoints p1 p2 ......)

while *error* is sub-routine and makepoints also .

I saw some routines don't localize these sub-routines and sometimes do .

Many thanks

gile

  • Gator
  • Posts: 2505
  • Marseille, France
Re: About Defun
« Reply #1 on: July 11, 2012, 08:06:16 AM »
LISP (as with most functional languages) uses "first class functions", thats to say functions can be considered as data.

Localizing a function is the same as localizing a data (variable) it reduces its scope to the parent function.

As *error* is a 'global' function (natively defined), you *must* localize *error* while you redefine it, otherwise it will keep your definition in the global namespace after the parent function ends.
« Last Edit: July 11, 2012, 08:10:40 AM by gile »
Speaking English as a French Frog

irneb

  • Water Moccasin
  • Posts: 1794
  • ACad R9-2016, Revit Arch 6-2016
Re: About Defun
« Reply #2 on: July 11, 2012, 08:27:20 AM »
Generally you'd localize such a sub-defun if it's only going to be used inside the containing defun. The localization helps by avoiding any name clashes. A very good example if the *error* defun, because there's a built-in *error* defun which simply prints the error - but if your main defun requires some cleanup code you want to happen even after an error, then this is the easiest way to go about it.

What happens is a new *error* defun is placed on top of the stack for the global *error* defun (instead of simply overwriting it). Then while the main defun is still running the top-most *error* defun is in effect. After the main defun completes or closes due to an error, the new localized version of the *error* defun is removed from the stack, making the previous one the effective *error*. There are some problems though, e.g. consider the following:
Code - Auto/Visual Lisp: [Select]
  1. (defun c:Test1 (/ *error* var1)
  2.   (defun *error* (msg / )
  3.     (if var1 (setvar "USERI1" var1)) ;Reset the sysvar to its old value
  4.     (princ msg))
  5.   (setq var1 (getvar "USERI1")) ;Save the current value of the sysvar
  6.   (setvar "USERI1" 0) ;Set the sysvar to something else
  7.   ;; Continue with some code which might cause an error - e.g. the user pressing Esc
  8.   (setvar "USERI1" var1) ;Reset the sysvar to its old value
  9.   (princ))
  10.  
  11. (defun c:Test2 (/ *error* var1)
  12.    (defun *error* (msg / )
  13.      (if var1 (setvar "USERI2" var1)) ;Reset the sysvar to its old value
  14.      (princ msg))
  15.    (setq var1 (getvar "USERI2")) ;Save the current value of the sysvar
  16.    (setvar "USERI2" 0) ;Set the sysvar to something else
  17.    ;; Continue with some code which might cause an error - e.g. the user pressing Esc
  18.   (c:Test1) ;Call the Test1 defun also
  19.    (setvar "USERI2" var1) ;Reset the sysvar to its old value
  20.    (princ))
Notice since Test2 calls Test1 as well, there's now 3 versions of *error* on the stack. Say an error happens inside Test1, then the error for Test2 is still one place down in the stack and thus won't run when needed and thus USERI2 won't get reset.

Another point about this is variables also have such a stack when localized. E.g. the var1's stack contains 2 versions after Test2 called Test1. So it is also showing only the original value from USERI1 and thus you can thus not get to the original value for USERI2 at this stage.

This is one of the major issues with AutoLisp's error handling: nested errors don't work properly.

As for other defuns, if they aren't declared as localized then they will become global. As if the defun was created outside of the main defun. In which case I'd advise to rather just create it outside in the first place - all else being equal. Sometimes however you're using a feature of Lisp (something many other languages don't have): Runtime self-modification, e.g. say you want a command which acts a certain way in one case but a different way in another, you could have a defun simply redefine that command when called and thew new one will be run - in this case you define a defun inside another without localizing it - because you want to redefine a global only when needed.

So you can see there's good and bad points to the way AutoLisp works. This idea is known as Dynamic Scope, which means any symbol (function / variable) defined as local is in effect while that defun is running. Most other languages (including most other Lisps) have a Lexical Scope (either as well or only Lexical) - which means that localized variables are only applicable inside that defun, even if that defun calls another the 2nd cannot see the vars of the 1st.

Lexical, also known as Static scope, works more like most people expect localization to work. Lexical also usually helps with the problem as per the var1 variable in the above, but it has a cost. If Lexical instead of Dynamic then this wouldn't work:
Code - Auto/Visual Lisp: [Select]
  1. (defun RunOnce ()
  2.     (setq n (1+ n)))
  3.  
  4. (defun RunMany (/ n)
  5.   (setq n 0)
  6.   (repeat 10 (RunOnce)))
In AutoLisp that would increment n 10 times and the result would be 10. But if it was Lexical, then it would error, since there's no variable n inside of the RunOnce defun.
« Last Edit: July 11, 2012, 08:32:02 AM by irneb »
Common sense - the curse in disguise. Because if you have it, you have to live with those that don't.

irneb

  • Water Moccasin
  • Posts: 1794
  • ACad R9-2016, Revit Arch 6-2016
Re: About Defun
« Reply #3 on: July 11, 2012, 09:15:16 AM »
Actually, some languages have a way around the case of Lexical / Dynamic by allowing nested functions. E.g. here's a Pascal version of the RunOnce and RunMany idea:
Code - Pascal: [Select]
  1. Function RunOnce : Integer;
  2. Begin
  3.   n := Inc(n);
  4.   RunOnce := n;
  5. End;
  6.  
  7. Function RunMany : Integer;
  8. var n : Integer;
  9. Begin
  10.   n := 0;
  11.   Repeat 10 Begin
  12.     RunOnce;
  13.   End;
  14.   RunMany := n;
  15. End;
This would error though, since Pascal uses Lexical scope and thus there's no variable n inside of the RunOnce function.

But this would work:
Code - Pascal: [Select]
  1. Function RunMany : Integer;
  2. var n : Integer;
  3.  
  4.   Function RunOnce : Integer;
  5.   Begin
  6.     n := Inc(n);
  7.     RunOnce := n;
  8.   End;
  9.  
  10. Begin
  11.   n := 0;
  12.    Repeat 10 Begin
  13.     RunOnce;
  14.   End;
  15.   RunMany := n;
  16. End;
Since now the RunOnce function is nested inside the RunMany, thus there is a n variable. Unfortunately not all Lexically scoped languages allow nested functions, one major example of these is C++.

But consider this: Say you want to have another defun reusing the RunOnce. So you can do the following in AutoLisp:
Code - Auto/Visual Lisp: [Select]
  1. (defun RunOnce ()
  2.   (setq n (1+ n)))
  3.  
  4. (defun Run20 (/ n)
  5.   (setq n 0)
  6.   (repeat 20 (RunOnce)))
  7.  
  8. (defun RunMany (/ n)
  9.  (setq n 0)
  10.  (repeat 10 (RunOnce)))
In a lexically scoped language (even with nested functions) you'd need to duplicate the RunOnce function inside both the RunMany and the Run20 functions. This is the main problem with Lexical scope - i.e. reuse is limited.

Then you get other languages like Common Lisp which allows you to specify which variable is Dynamic and which is Lexical - so you can fine-tune to get the best from both scenarios.
Common sense - the curse in disguise. Because if you have it, you have to live with those that don't.

gile

  • Gator
  • Posts: 2505
  • Marseille, France
Re: About Defun
« Reply #4 on: July 11, 2012, 01:17:08 PM »
Nice explaination Irne, thanks.
Speaking English as a French Frog

Coder

  • Swamp Rat
  • Posts: 827
Re: About Defun
« Reply #5 on: July 12, 2012, 12:40:21 AM »
Thank you irneb for all your hard efforts .

I am so sorry , because I couldn't get what I want between all these long lines of explanations .  :oops:

It is hard for me , I am sorry again for disappointing you  :-(

irneb

  • Water Moccasin
  • Posts: 1794
  • ACad R9-2016, Revit Arch 6-2016
Re: About Defun
« Reply #6 on: July 12, 2012, 03:37:35 AM »
Maybe a more useful example would help to explain it.

A sample command to draw a 5 pointed star:
Code - Auto/Visual Lisp: [Select]
  1. (defun c:Star5  (/ radius center CalcStar MakePoly)
  2.   (if (and (setq center (getpoint "\nPick center of star: "))
  3.            (setq radius (getdist center "\nOuter radius: ")))
  4.     (progn ;; Helper function to calculate the vector points on a star
  5.            (defun CalcStar  (points / ang ptLst 2pi inc n)
  6.              (setq 2pi   (+ pi pi)
  7.                    ang   0.0
  8.                    n     0
  9.                    inc   (/ pi points)
  10.                    ptLst (list (polar center 0.0 radius)))
  11.              (while (< (setq ang (+ ang inc)) 2pi)
  12.                (setq ptLst (cons (polar center
  13.                                         ang
  14.                                         (if (= (rem (setq n (1+ n)) 2) 0)
  15.                                           radius
  16.                                           (/ radius 2.0)))
  17.                                  ptLst)))
  18.              ptLst)
  19.            ;; Helper function to make a polyline from a list of vector points
  20.            (defun MakePoly  (ptLst /)
  21.              (entmake
  22.                (apply 'append
  23.                       (cons (list '(0 . "LWPOLYLINE")
  24.                                   '(100 . "AcDbEntity")
  25.                                   (cons 67
  26.                                         (if (> (getvar "CVPORT") 1)
  27.                                           0
  28.                                           1))
  29.                                   (cons 8 (getvar "CLAYER"))
  30.                                   '(100 . "AcDbPolyline")
  31.                                   (cons 90 (length ptLst))
  32.                                   '(70 . 1)
  33.                                   '(43 . 0.0))
  34.                             (mapcar '(lambda (pt) (list (cons 10 pt) '(40 . 0.0) '(41 . 0.0) '(91 . 0))) ptLst)))))
  35.            (MakePoly (CalcStar 5))))
  36.   (princ))
Now a few things in that defun can be used inside of others. E.g. say you want a 10 point star as well. So take the 2 nested defuns outside:
Code - Auto/Visual Lisp: [Select]
  1. ;; Helper function to calculate the vector points on a star
  2. (defun CalcStar  (points / ang ptLst 2pi inc n)
  3.   (setq 2pi   (+ pi pi)
  4.         ang   0.0
  5.         n     0
  6.         inc   (/ pi points)
  7.         ptLst (list (polar center 0.0 radius)))
  8.   (while (< (setq ang (+ ang inc)) 2pi)
  9.     (setq ptLst (cons (polar center
  10.                              ang
  11.                              (if (= (rem (setq n (1+ n)) 2) 0)
  12.                                radius
  13.                                (/ radius 2.0)))
  14.                       ptLst)))
  15.   ptLst)
  16.  
  17. ;; Helper function to make a polyline from a list of vector points
  18. (defun MakePoly  (ptLst /)
  19.     (apply 'append
  20.            (cons (list '(0 . "LWPOLYLINE")
  21.                        '(100 . "AcDbEntity")
  22.                        (cons 67
  23.                              (if (> (getvar "CVPORT") 1)
  24.                                0
  25.                                1))
  26.                        (cons 8 (getvar "CLAYER"))
  27.                        '(100 . "AcDbPolyline")
  28.                        (cons 90 (length ptLst))
  29.                        '(70 . 1)
  30.                        '(43 . 0.0))
  31.                  (mapcar '(lambda (pt) (list (cons 10 pt) '(40 . 0.0) '(41 . 0.0) '(91 . 0))) ptLst)))))
  32.  
  33. (defun c:Star5  (/ radius center)
  34.   (if (and (setq center (getpoint "\nPick center of star: "))
  35.            (setq radius (getdist center "\nOuter radius: ")))
  36.     (MakePoly (CalcStar 5)))
  37.   (princ))
  38.  
  39. (defun c:Star10  (/ radius center)
  40.   (if (and (setq center (getpoint "\nPick center of star: "))
  41.            (setq radius (getdist center "\nOuter radius: ")))
  42.     (MakePoly (CalcStar 10)))
  43.   (princ))
So now it's a trivial task to make yet others. Even one which allows a user specified number of points:
Code - Auto/Visual Lisp: [Select]
  1. (defun c:Star (/ radius center)
  2.   (if (and (setq center (getpoint "\nPick center of star: "))
  3.            (setq radius (getdist center "\nOuter radius: "))
  4.            (setq points (max (abs (getint "\nNumber of points: ")) 2)))
  5.     (MakePoly (CalcStar points)))
  6.   (princ))
Now say you want something where the star's pointy-ness is allowed to vary:
Code - Auto/Visual Lisp: [Select]
  1. (defun c:StarOption  (/ factor)
  2.   (while (and (setq factor (getreal "\nEnter a factor between 0.0 and 1.0 (non-inclusive) <0.5>: "))
  3.               (or (<= factor 0.0) (>= factor 1.0)))
  4.     (princ "\nSorry that number is incorrect, please try again."))
  5.   (if factor
  6.     (eval (list 'defun 'CalcStar '(points / ang ptLst 2pi inc n)
  7.                 '(setq 2pi (+ pi pi) ang 0.0 n 0 inc (/ pi points) ptLst (list (polar center 0.0 radius)))
  8.                 (list 'while '(< (setq ang (+ ang inc)) 2pi)
  9.                       (list 'setq 'ptLst (list 'cons
  10.                                                (list 'polar 'center 'ang
  11.                                                      (list 'if '(= (rem (setq n (1+ n)) 2) 0) 'radius (list '* 'radius factor)))
  12.                                                'ptLst)))
  13.                 'ptLst))
  14.     (defun CalcStar  (points / ang ptLst 2pi inc n)
  15.       (setq 2pi (+ pi pi) ang 0.0 n 0 inc (/ pi points) ptLst (list (polar center 0.0 radius)))
  16.       (while (< (setq ang (+ ang inc)) 2pi)
  17.         (setq ptLst (cons (polar center ang
  18.                                  (if (= (rem (setq n (1+ n)) 2) 0) radius (/ radius 2.0)))
  19.                           ptLst)))
  20.       ptLst))
  21.   (princ))
This one now modifies the CalcStar defun depending on a user-input value. It would probably be easier to just save the factor as a global, but I've done this to indicate that such can be done in AutoLisp (whereas in other languages it near impossible to do so, or at least extremely difficult). This would be the only time I'd consider defining a defun inside another without localizing it - AFAIK all other times you should either make nested defuns localized, or move them outside to become true global defuns.

BTW, the CalcStar makes heavy reliance on the Dynamic Scope. So if you make say a Star7 command and name the localized variables inside differently - there would be errors. For such global functions I'd advise using arguments to the CalcStar instead of relying on the calling function to have an exactly named variable.
Common sense - the curse in disguise. Because if you have it, you have to live with those that don't.

Stefan

  • Bull Frog
  • Posts: 319
  • The most I miss IRL is the Undo button
Re: About Defun
« Reply #7 on: July 12, 2012, 05:22:10 AM »
Let me try a simple explanation.
You probably want to know when to use a way or another.
1.) In next sample, two main functions are defined in two separate .lsp files and each of them calls the same subroutine, sub1.
Code: [Select]
(defun C:MAIN1 ( / a)
(setq a (getint “\nNumber of items: “))
(sub1 a)
)
(defun sub1 (x) (1+ x))

Code: [Select]
(defun C:MAIN2 ( / a)
(setq a (getint "\nNumber of items: "))
(sub1 a)
)
(defun sub1 (x) (expt x 5))

If both files are loaded, the subroutine  sub1 is the last one loaded, so one of the main functions gives you wrong result.
So, you can use a localized subroutine to avoid redefining it in another lisp.

Code: [Select]
(defun C:MAIN1 ( / sub1 a)
(defun sub1 (x) (1+ x))
(setq a (getint "\nNumber of items: "))
(sub1 a)
)

Code: [Select]
(defun C:MAIN2 ( / sub1 a)
(defun sub1 (x) (expt x 5))
(setq a (getint "\nNumber of items: "))
(sub1 a)
)

Now both main functions will work properly. Note that you must define a localized subroutine before calling it.

2.) Now look at this sample:

Code: [Select]
(defun C:CUBIC_EQ ( / func first_deriv a c b d)
(defun func (x) (apply '+ (mapcar  '* (list a b c d) (list (expt x 3) (expt x 2) x 1))))
(defun first_deriv (x) (apply '+ (mapcar  '* (list a b c) (list (expt x 2) x 1) (list 3 2 1))))
(setq a (getreal "\nA param: "))
(setq b (getreal "\nB param: "))
(setq c (getreal "\nC param: "))
(setq d (getreal "\nD param: "))
(list (func 10.) (first_deriv 10.))
)
If func and first_deriv were defined externally, they require more than 1 variable, something like (define func (x a b c d)… and calling them too (func x a b c d) etc.
So, you can use a localized subroutine to avoid calling it with multiple arguments.

3). Finaly, you can use an unlocalized subroutine if you want to call it from other lisp:
Code: [Select]
(defun C:MAIN2 ( / b)
(load "main1.lsp")
(setq b (getint "\nNumber of items: "))
(sub1 b)
)
where main1.lsp is
Code: [Select]
(defun C:MAIN1 ( / a)
(setq a (getint "\nNumber of items: "))
(sub1 a)
)
(defun sub1 (x) (1+ x))

I guess there are more to say about using one or other method, but I hope this will help you. At the end, we are all here to learn… OK, maybe except Gile. :laugh:

irneb

  • Water Moccasin
  • Posts: 1794
  • ACad R9-2016, Revit Arch 6-2016
Re: About Defun
« Reply #8 on: July 12, 2012, 05:33:28 AM »
...
2.) Now look at this sample:

Code: [Select]
(defun C:CUBIC_EQ ( / func first_deriv a c b d)
   (defun func (x) (apply '+ (mapcar  '* (list a b c d) (list (expt x 3) (expt x 2) x 1))))
   (defun first_deriv (x) (apply '+ (mapcar  '* (list a b c) (list (expt x 2) x 1) (list 3 2 1))))
   (setq a (getreal "\nA param: "))
   (setq b (getreal "\nB param: "))
   (setq c (getreal "\nC param: "))
   (setq d (getreal "\nD param: "))
   (list (func 10.) (first_deriv 10.))
)
If func and first_deriv were defined externally, they require more than 1 variable, something like (define func (x a b c d)… and calling them too (func x a b c d) etc.
So, you can use a localized subroutine to avoid calling it with multiple arguments.
...
Actually, that's what I'm trying to explain is unnecessary in AutoLisp due to the Dynamic Scoping. In your sample it would work similar even if both func & first_deriv are defined outside of C:CUBIC_EQ, exactly as they are now without the extra arguments. Of course as long as they're loaded and not overwritten by something else.

It's always a good thing to go about it the way you did (i.e. nesting & localizing in order to use a surrounding function's variables directly), but due to Dynamic Scoping it's not necessary. If you stick with the rule that you have to always nest such functions, then you'll need to duplicate the nested func & first_deriv in every other function which uses them. If you simply define them as global, you don't have to.

It becomes dangerous because of 2 reasons:
  • The reused functions could be overwritten by something else as per your 1st example.
  • Another function could call them but not have the same named variables given the same type of values - thus causing an error.
Common sense - the curse in disguise. Because if you have it, you have to live with those that don't.

irneb

  • Water Moccasin
  • Posts: 1794
  • ACad R9-2016, Revit Arch 6-2016
Re: About Defun
« Reply #9 on: July 12, 2012, 06:36:32 AM »
Perhaps a picture would better explain.

The 1st is the scenario of the *error* and var1 idea in my first post #3. The second shows what's happening to the RunOnce & RunMany when the RunOnce is nested & localized. The 3rd shows what's happening if the RunOnce is global but references another function's local variable.
Common sense - the curse in disguise. Because if you have it, you have to live with those that don't.

Lee Mac

  • Seagull
  • Posts: 12904
  • London, England
Re: About Defun
« Reply #10 on: July 12, 2012, 07:31:32 AM »
Irn้, the effort that you put into your explanations is admirable.

Might I ask, what software did you use to create those flowcharts?

irneb

  • Water Moccasin
  • Posts: 1794
  • ACad R9-2016, Revit Arch 6-2016
Re: About Defun
« Reply #11 on: July 12, 2012, 08:47:15 AM »
Thanks for the compliment Lee!

I used LibreOffice Draw. IMO it's a whole lot better than trying to achieve this in Word's drawing tools, it's a lot closer to Visio in that sense.
Common sense - the curse in disguise. Because if you have it, you have to live with those that don't.

Lee Mac

  • Seagull
  • Posts: 12904
  • London, England
Re: About Defun
« Reply #12 on: July 12, 2012, 09:00:44 AM »
Thank you  :-)