Author Topic: OOP (Con/De)structor behavior in Lisp  (Read 2542 times)

0 Members and 1 Guest are viewing this topic.

JohnK

  • Administrator
  • Seagull
  • Posts: 10655
OOP (Con/De)structor behavior in Lisp
« on: October 05, 2023, 12:13:45 PM »
Thinking about how classes work (and/or even how the C flavor languages work)...

I saw a bit of code in C# that uses the IDisposable interface to set/restore a variable and it got me to thinking about how we accomplish this same thing with lisp. You'd be surprised how close we can get to some OOP behavior in Lisp.

The constructor/destructor mechanism is complicated, but the basics of the subject is that a variable (object) is created in the constructor and then the memory for that variable (object) is freed in the destructor. ...in lisp terms think of a variable/object as a list of lists of variables and values like:
((person (name (first "John") (last "Doe")) (location "USA")))
so the all of the memory used for this list of lists gets destroyed in a destructor.


Normally, I would construct a procedure like the code below, which uses a "trick", -i.e. I define an error function which gets called at the end of the procedure to restore any variables that were set (oh?! and the "*error-push-vars*" is a procedure to set and store variables and values into a list) but this isn't how destructors in an OOP language work because I had to actually call/trigger the variable restoration/destruction. This is, generally speaking, how you would do things in C but not C++ or C# (the latter two flavors being "OOP"--you can technically do OOP in plain C as well--but typically in C you allocate memory then free memory later in your procedures and in an OOP the destruction is more automatic).

Code - Auto/Visual Lisp: [Select]
  1. (defun C:co (/ *error* *error-push-vars*)
  2.   ;; Start the copy command without OTHO.
  3.         (defun *error* (msg) (mapcar (quote eval) #error_lst#) (setq #error_lst# nil))
  4.         (defun *error-push-vars* (lst / push->lst)
  5.           (defun push->lst (sym lst) (set lst (cons sym (eval lst))))
  6.           (mapcar
  7.             (function
  8.               (lambda (x)
  9.                 (push->lst
  10.                   (list (quote setvar) (car x) (getvar (car x)))
  11.                   (quote #error_lst#))
  12.                 (if (cadr x) (setvar (car x) (cadr x)))))
  13.             lst) )
  14.  
  15.   ;; Store and set some variables
  16.   (*error-push-vars* '(("ORTHOMODE" 0) ("OSMODE" 247)))
  17.  
  18.   ;; Issue the `copy` command.
  19.   (command "_.copy")
  20.   (while (eq 1 (logand 1 (getvar (quote cmdactive)))) (command pause))
  21.  
  22.   ;; Issue/call error handler to restore variable values.
  23.   (quit)
  24.  )

However, with a little refactoring we can use a reactor to restore the variable values for us automatically.

Code - Auto/Visual Lisp: [Select]
  1. (defun C:co (/ *error-push-vars*)
  2.   ;; Start the copy command without OTHO.
  3.  
  4.         (defun *error* (msg / *error*)
  5.           (*error-restore-vars* nil nil)
  6.           (princ) )
  7.  
  8.         (defun *error-restore-vars* (one two / *error-restore-vars*)
  9.           (mapcar (quote eval) #error_lst#)
  10.           (setq #error_lst# nil)
  11.           (vlr-remove-all :vlr-lisp-reactor) )
  12.  
  13.         (defun *error-push-vars* (lst / push->lst)
  14.           (defun push->lst (sym lst) (set lst (cons sym (eval lst))))
  15.           (mapcar
  16.             (function
  17.               (lambda (x)
  18.                 (push->lst
  19.                   (list (quote setvar) (car x) (getvar (car x)))
  20.                   (quote #error_lst#))
  21.                 (if (cadr x) (setvar (car x) (cadr x)))))
  22.             lst) )
  23.  
  24.   ;; Store and set some varables.
  25.   (*error-push-vars* '(("ORTHOMODE" 0) ("OSMODE" 247)))
  26.  
  27.   ;; Set up a reactor to call the `restore` function.
  28.   (vlr-lisp-reactor nil '((:vlr-lispEnded . *error-restore-vars*)))
  29.  
  30.   ;; Issue the `copy` command.
  31.   (command "_.copy")
  32.   (while (eq 1 (logand 1 (getvar (quote cmdactive)))) (command pause))
  33.  )
« Last Edit: October 05, 2023, 12:22:00 PM by JohnK »
TheSwamp.org (serving the CAD community since 2003)
Member location map - Add yourself

Donate to TheSwamp.org

Lee Mac

  • Seagull
  • Posts: 12922
  • London, England
Re: OOP (Con/De)structor behavior in Lisp
« Reply #1 on: October 05, 2023, 12:25:05 PM »
FWIW, you could simplify *error-push-vars* to the following for better readability:
Code - Auto/Visual Lisp: [Select]
  1. (defun *error-push-vars* ( lst )
  2.     (foreach x lst
  3.         (setq #error_lst# (cons (list 'setvar (car x) (getvar (car x))) #error_lst#))
  4.         (if (cadr x) (apply 'setvar x))
  5.     )
  6. )

Lee Mac

  • Seagull
  • Posts: 12922
  • London, England
Re: OOP (Con/De)structor behavior in Lisp
« Reply #2 on: October 05, 2023, 12:30:59 PM »
Though, I think I'd be inclined to follow this approach, to account for releases for which a particular system variable was not present -
Code - Auto/Visual Lisp: [Select]
  1. (defun *error-push-vars* ( lst / v )
  2.     (foreach x lst
  3.         (cond
  4.             (   (setq v (getvar (car x)))
  5.                 (setq #error_lst# (cons (list (car x) v) #error_lst#))
  6.                 (if (cadr x) (apply 'setvar x))
  7.             )
  8.         )
  9.     )
  10. )
  11.  
  12. (defun *error-restore-vars* ( )
  13.     (foreach x #error_lst# (apply 'setvar x))
  14. )

stevej

  • Newt
  • Posts: 30
Re: OOP (Con/De)structor behavior in Lisp
« Reply #3 on: October 05, 2023, 06:02:53 PM »
In my efforts to interpret this, I have a couple questions:

1. How is this
Code: [Select]
(while (eq 1 (logand 1 (getvar (quote cmdactive)))) (command pause))
different from this
Code: [Select]
(while (= (getvar 'CmdActive) 1) (command pause))Both seem to work equally well.

2. Why does this implementation example using COPY only allow single selection and not a windowing selection?

Steve

It's Alive!

  • Retired
  • Needs a day job
  • Posts: 8766
  • AKA Daniel
Re: OOP (Con/De)structor behavior in Lisp
« Reply #4 on: October 05, 2023, 07:36:47 PM »
I saw a bit of code in C# that uses the IDisposable interface to set/restore a variable and it got

Yep, I use a similar construct in C++. Sometime I write them specifically for a var, I.e.
AutoCmdEcho(int mode = 0) noexcept
AutoOsMode(Adesk::Int16 mode = 0)  noexcept


BTW, SetSystemVariable can throw an exception... ideally, that should be wrapped with a try catch

Quote
A System.IDisposable.Dispose method should not throw an exception..

JohnK

  • Administrator
  • Seagull
  • Posts: 10655
Re: OOP (Con/De)structor behavior in Lisp
« Reply #5 on: October 06, 2023, 01:37:17 PM »
In my efforts to interpret this, I have a couple questions:

1. How is this
Code: [Select]
(while (eq 1 (logand 1 (getvar (quote cmdactive)))) (command pause))
different from this
Code: [Select]
(while (= (getvar 'CmdActive) 1) (command pause))Both seem to work equally well.

2. Why does this implementation example using COPY only allow single selection and not a windowing selection?

Steve

eq v "=" : Technically "=" is correct.

quote v ' : no difference that I know of.

use of logand : to be honest, I don't think I know why. The first time I learned how to pause an operation, the person used a logand wrapper and I guess I just didn't focus in on that aspect and it just became part of the syntax for me. -i.e. "check the one's bit..."  but I don't think I could comment on the NEED for a logand 1's bit check (past or present) in the CMDACTIVE variable, but a bitwise check isn't a bad habit to have either (needed or not).

cmdactive bits.
Code: [Select]
Command: (mapcar 'write-line (mapcar 'printbin '(0 1 2 4 8 16 32 64)))
00000000 00000000 00000000 00000000 : 0
00000000 00000000 00000000 00000001 : 1
00000000 00000000 00000000 00000010 : 2
00000000 00000000 00000000 00000100 : 4
00000000 00000000 00000000 00001000 : 8
00000000 00000000 00000000 00010000 : 16
00000000 00000000 00000000 00100000 : 32
00000000 00000000 00000000 01000000 : 64
TheSwamp.org (serving the CAD community since 2003)
Member location map - Add yourself

Donate to TheSwamp.org

JohnK

  • Administrator
  • Seagull
  • Posts: 10655
Re: OOP (Con/De)structor behavior in Lisp
« Reply #6 on: October 06, 2023, 01:44:31 PM »
I saw a bit of code in C# that uses the IDisposable interface to set/restore a variable and it got

Yep, I use a similar construct in C++. Sometime I write them specifically for a var, I.e.
AutoCmdEcho(int mode = 0) noexcept
AutoOsMode(Adesk::Int16 mode = 0)  noexcept


BTW, SetSystemVariable can throw an exception... ideally, that should be wrapped with a try catch

Quote
A System.IDisposable.Dispose method should not throw an exception..

heh! A try catch wrapper is something I have to get better at using (in many languages).

The noexcpt would be a good thing and I suppose Lisp has something similar (not exception level though) but it can be a pain to use; lisp stuff is just so high level I think it's just better to crash most of the time.
TheSwamp.org (serving the CAD community since 2003)
Member location map - Add yourself

Donate to TheSwamp.org

Lee Mac

  • Seagull
  • Posts: 12922
  • London, England
Re: OOP (Con/De)structor behavior in Lisp
« Reply #7 on: October 06, 2023, 06:21:29 PM »
CMDACTIVE is a bit-coded system variable, and so a bitwise comparison is appropriate; if, for whatever reason, multiple bits are combined, the bitwise test will remain successful whereas the equality test will fail.

ur_naz

  • Newt
  • Posts: 68
  • Made in Ukraine
Re: OOP (Con/De)structor behavior in Lisp
« Reply #8 on: October 07, 2023, 08:09:26 AM »
 :kiss:

kdub_nz

  • Mesozoic keyThumper
  • SuperMod
  • Water Moccasin
  • Posts: 2148
  • class keyThumper<T>:ILazy<T>
Re: OOP (Con/De)structor behavior in Lisp
« Reply #9 on: October 07, 2023, 06:23:56 PM »
fwiw:

isn't this statement likely to clobber functionality of any other loaded routine, incurring wrath ??

Code - Auto/Visual Lisp: [Select]


I haven't written much vl code for a few years, but this (see attached to the linked post ) is how I've managed errors and restoring  system variables for a long time.
https://www.theswamp.org/index.php?topic=33105.0

I had a call to (kdub:restoresysvar) in the kdub:on-error() method, but could have added it in-line into the local *error*() declaration.
Importing a base method and adding to it keeps the code cleaner.
ie:
Code - Auto/Visual Lisp: [Select]
  1. (defun *error* (msg) (kdub:on-error msg) (kdub:restoresysvar) (princ))
  2.  

I really prefer just calling the local *error*() with a null msg parameter at the end of the main method.
ie:
Code - Auto/Visual Lisp: [Select]
  1. // . . .
  2.   (*error* nil)
  3.   (princ)
  4. )
  5.  

At the end of the day, the manner of handling errors and restoring the environment is a matter of personal preference.
. . . not doing it is unprofessional.

« Last Edit: October 07, 2023, 06:30:44 PM by kdub_nz »
Called Kerry in my other life
Retired; but they dragged me back in !

I live at UTC + 13.00

---
some people complain about loading the dishwasher.
Sometimes the question is more important than the answer.

JohnK

  • Administrator
  • Seagull
  • Posts: 10655
Re: OOP (Con/De)structor behavior in Lisp
« Reply #10 on: October 16, 2023, 10:34:35 AM »
I haven't had time to review the code suggestions but Kerry's comment about the reactor got me thinking that the whole reactor concept was a bad idea and the 'standard method' of calling the error routine to restore the state is the better option.

That got me thinking so, I was toying with the idea of storing regular variables too--instead of just supporting system vars--in the variable list but, I don't really see a huge need for that "feature".

Concept (code suggestions aside, this code just demonstrates storing regular and system vars in one list):

Code - Auto/Visual Lisp: [Select]
  1. (defun *error* (msg) (mapcar 'eval #error_lst#) (setq #error_lst# nil) )
  2. (defun *error-push-vars* (lst / push->lst which)
  3.   ;; This function acts as a `envioment state` in which, a
  4.   ;; user can store--and set variables (envioment, and regular)
  5.   ;; to act as a `state`.
  6.   ;;
  7.   ;; BY: JohnK
  8.   ;;
  9.   ;; EXAMPLE:
  10.   ;; Store and set variables with the following statement.
  11.   ;;     (*error-push-vars* '(("ORTHOMODE" 0) ("OSMODE" 247) ("B" 123)))
  12.   ;; This will store a variable that contains a list.
  13.   ;; >   ((SETQ B nil) (SETVAR "OSMODE" 247) (SETVAR "ORTHOMODE" 1))
  14.   ;; Now that a list is stored, we can issue a statement to
  15.   ;; restore that state with (or use this within an error routine):
  16.   ;;      (mapcar 'eval #error_lst#)
  17.   (defun push->lst (sym lst) (set lst (cons sym (eval lst))))
  18.   (defun which (var)
  19.     ;; determine which way to set a variable.
  20.     ;; returns: either "(setvar <VARIABLE> <CURRENT VALUE>)"
  21.     ;;             or  "(SETQ <VARIABLE> <CURRENT VALUE>)"
  22.     (cond
  23.       ((getvar var) (list 'setvar var (getvar var)))
  24.       ((list 'setq (read var) (eval (read var))))) )
  25.   (mapcar
  26.     (function
  27.       (lambda (x / ty)
  28.         ; -Get the type of variable and it's current state.
  29.         (setq ty (which (car x)))
  30.         ; -Push it to a list.
  31.         (push->lst ty '#error_lst#)
  32.         ; -If there is a value to set, set it.
  33.         (if (cadr x) (eval (list (car ty) (cadr ty) (cadr x))))))
  34.     lst) )
  35.  
  36. ; in function:
  37. (*error-push-vars* '(("ORTHOMODE" 0) ("OSMODE" 247) ("B" 123)))
  38. ; doing stuff...
  39. (*error-push-vars* '(("A" 123)))
TheSwamp.org (serving the CAD community since 2003)
Member location map - Add yourself

Donate to TheSwamp.org