Jump to content

(Auto)Lisp is first class


eengebruiker

Recommended Posts

Hello,

 

After a sidestep to Clojure, of course back to AutoLisp.

 

I have quite some exeperience with AutoLisp and on and off I worked with it for almost 20 years now. (Auto)Lisp is known amongst AutoCAD users but further on, the whole lisp-thing doesn't ring a bell with most programmers that I meet. They work with SQL, HTML, XHTML, Java, C# or maybe (V)BA.

I stayed interested in Lisp, although limited to AutoLisp. Recently. I discovered that there is a new Lisp called Clojure that is becoming big in the programming world. Possibly due to the fact that it translates to Java or javascript before anything is executed. But this aside.

I learned that Clojure is first class because it is capable of accepting a function as a parameter and clojure functions can produce functions. There's a whole lot more to be discoverd about this great language but I leave that to the reader. The subject 'Clojure' is easily found on the web. By taking this interest in Clojure I learned a lot about AutoLisp too.

 

On this page at the subject of 'Closures' halfway the page, there is an example showing how a function is produced in Clojure. Realy, it took me quite some time to grasp it. After that I wanted to know if this could be done with AutoLisp as well. After trials with defun-q-list-set/ref (which also work) I came up with the following. Hart of the solution is (eval (append '(lambda . .

 

Is it the best way to do this? I don't know.

Is it useful? I doubt it but see below.

Is it interesting? Highly (for me!)

(defun messenger-builder (greeting)
  (setq greeting (strcat "\n" greeting " "))
  (eval (append
   '(lambda (who))
    (list (list 'princ (list 'strcat greeting 'who)) '(princ)))))

(setq hello-er   (messenger-builder "Hello")
      goodbye-er (messenger-builder "Goodbye"))

(defun c:greet_the_world () (hello-er "World!"))
(defun c:say_goodbye     () (goodbye-er "Everyone!!"))

AutoLisp does a great job in AutoCAD. Perfect, but as a Lisp it is less thoroughly designed than Clojure. This shows off in the example above. Clojure handles the subject of producing functions easier. Maybe my example is ill-constructed. I could not find a better solution.

 

Question also is, can we think of any usufull application of this in AutoCAD. Although I took another route in the example, the designated functions for this are (I think) defun-q-list-set/ref. I suppose they are there for a reaon.  Or is it all limited to the s::startup example?

Edited by eengebruiker
Link to comment
Share on other sites

Seems a lot of effort to do

(alert "hello world\n \nhey its 4:30 Beer o clock")

or

 

; bit of fun

(defun SpeakSapi ( s / sapi )
(if (eq (type s) 'STR) 
(progn 
(setq sapi (vlax-create-object "Sapi.SpVoice"))
(vlax-put sapi 'SynchronousSpeakTimeout 1)
(vlax-invoke-method sapi 'WaitUntilDone 0)
(vlax-invoke sapi "Speak" s 0)
(vlax-release-object sapi)
)))

(speaksapi "Hello world")
(speaksapi "Welcome from BIG al the humour you can expect at times")
(speaksapi "PLEASE PAY YOUR MONEY")
(speaksapi "I know you have wound the clock back" )
(speaksapi "Call me on 1234 5678 if you like the software")
(princ)

Autodesk are going down the visual studio path as their future direction, not sure how that fits in with your preference.

  • Like 2
Link to comment
Share on other sites

@BIGAL: Of course it is a lot of effort for this example. That's why I am curious if anyone can think of an much more useful example.

You stay at the practical side and that is good. Especially if you want to be productive.

 

I know AutoDESK is totally into .NET, although I am not familiar with that. I am not a real programmer. They aim at the market.

 

I think most people don't realise that Lisp is not just a bit different from laguages like VB, C#, etc. (imperative languages). Lisp is a functional language (although AutoLisp is not strictly functional!). In Lisp the difference between data and code is thin. Everything is made up from lists and data can become code and the other way around. I thought it was mind blowing when I first discoverd that. The idea that you can have integers, reals, strings, etc., that you can construct lists and lists in lists and ultimately handle Lisp itself als a bunch of lists to be executed . . . . . pffff.

 

AutoDesk never wanted to have a Lisp in AutoCAD for the ultimate possibilities of Lisp itself. It was useful in handling coördinates (3 numbers in a list) and maybe it was the hobby of some programmer at the time. But it is there now and we can explore the lispy-abilities of the language. That is fun and interesting.

 

At the end the question remains. What useful things can we do with it in AutoCAD. Personally I would like to know if my example can be done better. Maybe, I approached it wrong.
Hope someone has the knowledge of this side of (Auto)Lisp.

 

André

Edited by eengebruiker
Link to comment
Share on other sites

  • 2 weeks later...

A function defined with defun-q is in fact a list. You may have noticed that.

 

So you can also use this:

(defun messenger-builder (greeting)
  (setq greeting (strcat "\n" greeting " "))
  (list '(who) (list 'princ (list 'strcat greeting 'who)) '(princ))
)

But I frankly do not see much practical use.

Link to comment
Share on other sites

  • 2 weeks later...

@ Roy_043

 

I think your solution without Lambda is shorter and therefore better. But I do not understand why the hello-er and goodbye-er only need to be a list and not a function.

 

I do know that defun-q functions handle functions as lists but did not give anymore thought on that. At the end I have functions. Enough for me. Now I think there's a part that I do not understand.

 

I tried to cook up a working exemple voor mapcar with a list instead of an anonymous function (lambda). I could nog get it working. Again there's a part that I don't understand.

 

 

Also don't see much practical use. Maybe that's due to AutoCAD. Clojurists make a point of this. Being a firstclass language is a big thing in the programming world.

The same is true for recursion. It seems to be important in a Lisp to be able to do this (in a non memory waisting way).

Link to comment
Share on other sites

I use BricsCAD, not AutoCAD. This mapcar example works for me:

(mapcar
  (list '(i) (list '+ 'i 'i))
  (list 0 1 2 3)
)

 

5 hours ago, eengebruiker said:

I do not understand why the hello-er and goodbye-er only need to be a list and not a function.

There is not much to understand: A list with a certain format is a function in Lisp.

Link to comment
Share on other sites

Heard about BricsCAD but never used it.

 

Your sample gives the same error as the ones I tried: error: bad argument type: numberp: nil

Might be due to difference between AutoCAD and BricsCAD? Do you have knowledge about differences that could explain this?

 

The hello-er and goodbye-er take the list. So far mapcar doesn't. The example(s) show the lambda approach for mapcar.
Does something like lambda exist in BrisCAD?

Link to comment
Share on other sites

Lambda works in Bricscad. There some odd differences not sure if I found one about some code yesterday using command-s.

 

Polygon command works slightly different is one I know of.

Link to comment
Share on other sites

To be complete:

(defun c:ttst (/ old new)
 ;(mapcar '(lambda (i) (+ i i)) '(0 1 2 3))       ;Working
  (mapcar (list '(i) (list '+ 'i 'i)) '(0 1 2 3)) ;Not working
)

I like the explanation from Roy_043: 'There is not much to understand: A list with a certain format is a function in Lisp.'

Until know it seems not to work in AutoCAD but that maybe, matches my earlier phrase 'AutoLisp does a great job in AutoCAD. Perfect, but as a Lisp it is less thoroughly designed than Clojure.'

 

The following:

(setq uhg (list '(i) (list '+ 'i 'i)))
(uhg 5)

There might be a reason for not working (in AutoCAD).
Before we use the function constructed as a list in the working examples, it has a name.

In the not working mapcar sample, it does not have a name.

Maybe in AutoCAD it needs a name and in BrisCAD not?

Edited by eengebruiker
Link to comment
Share on other sites

23 minutes ago, eengebruiker said:

The following:


(setq uhg (list '(i) (list '+ 'i 'i)))
(uhg 5)

There might be a reason for not working (in AutoCAD).
Before we use the function constructed as a list in the working examples, it has a name.

In the not working mapcar sample, it does not have a name.

Maybe in AutoCAD it needs a name and in BrisCAD not?

 

 

Analyse the following:

(defun uhg ( i ) (+ i i)) ; define named function, #<USUBR @000001a6636b2520 UHG>

(defun-q uhg ( i ) (+ i i)) ; define named function as a list, ((I) (+ I I))

(setq uhg '((i)(+ i i))) ; set the value of a symbol to a list-function, ((I) (+ I I))

(uhg 5) ; call the named function
>> 10

; remember the syntax for mapcar: (mapcar (quote <function>) <list>)
(mapcar 'ugh '(5 5 5)) ; invoke mapcar with a named function
>> (10 10 10)


( ; invoke anonymous list function 
  '((i)(+ i i)) ; anonymous function definition, ((I) (+ I I))
  5 ; argument(s)
) 
>> 10 

( ; invoke anonymous lambda function
  (lambda (i) (+ i i)) ; lambda definition, #<USUBR @000001a6636b2ed0 -lambda->
  5 ; argument(s)
)
>> 10

; remember the syntax for mapcar: (mapcar (quote <function>) <list>)
(mapcar ; invoke mapcar with anonymous function
  ''((i)(+ i i)) 
  '(5 5 5)
)
>> (10 10 10)

(mapcar ; invoke mapcar with anonymous lambda function
  '(lambda (i) (+ i i))
  '(5 5 5)
)
>> (10 10 10)

 

Link to comment
Share on other sites

@Grrr, I have explored your code and there's in my opinion nothing to be added. The fact that a function in AutoLisp is just a list with a certain format (Roy_043) was new to me. Regular programmers may be well aware of this.

 

We doubt about usefulness (apart from manipulating the startup-routine as the manual demonstrates with the defun-q functions.) Maybe because Clojure is LISP! where AutoLisp is more AutoCAD than Lisp?

As I wrote above, in a Lisp like Clojure it is of utmost importance that the language is first class (being able to pass functions as parameters like they are integers or whatever). I guess LISP (Clojure) programmers do use the capacities of there language to this extent regularly. So when familiar with it  . . . .

The subject here is btw more about creating and minipulating functions, than passing them back and forth, but when reasoning about usefulness, this all comes up with me.

 

 

I have a working example where I create an object as a simple list depicting a box. Nothing special:

 

(("p" 132 176) ("width" 48.1) ("height" 110)).

 

I create this by calling a function: 

 

(bo:makeObj "bx" "box" '("p" (132 176) "width" 48.1 "height" 110)

 

This creates the mentioned list with defaults, but automatically functions to access it, like:

 

(bx:width) to return the width from object 'box'.

(bx:width_ 89.5) to change the width in the 'box'.

 

At some point it was convenient to have this (weak) object-like approach because:

- One object with several values.

- Nice code. (bx:width) instead of (cdr (assoc . . . ))

I used it for more but that aside. If you choose the names and prefixes well, the code at the top-level becomes expressive. Not too much cluttered with details. But all this is also a matter of preference.

 

I should mention that I create the functions by gluing together strings and bringing them to live with (eval (read . . .)):

(eval (read "(defun bx:with_ (v) . . .)")).

This works but is not elegant at all. At that point I did not study the defun-q stuff enough.

 

Maybe, if I can find the time, I rewrite the whole thing and post it here.

It shows that once fimiliar, some usefull applications can be created with it.

 

[@Grrr, you have helped me earlier with this (for me) interesting subject. I made a final reply. The subject of usefulness also came up.]

 

Thanks everyone,

 

André

Edited by eengebruiker
Link to comment
Share on other sites

  • 6 months later...

After rereading and doing some trials (half a year later), I find that the analysis that Grrr did, gives in essence all there can be said. This apart from some differences there are between BricsCAD and AutoCAD.

 

For me it started with my interest in Clojure, where functions are explicitly mentioned as first class. Meaning that they can be passed to and from onther functions as a parameter. A took an interest in AutoLISP again and that is where the topic came from. (BTW, the conclusion is that it can be done in AutoLISP also.)

 

Unfortunately the clear analysis of Grr is burried in a lot of text. It would be nice if it was available somehwere summarized. because there is a lot to be learned from it for starting AutoLISP programmers. Including the notion that it is maybe not that practical for AutoCAD applications. But that shouldn't stop anyone from understanding it in the first place.

 

 

 

 

Link to comment
Share on other sites

Re draw a box this is a lisp example, it uses a Library function for the input, its in Cadtutor downloads.

 

; simple draw a box and dimension it 
; By Alan H March 2019
' info@alanh.com.au

(defun ah:box ( / pt1 pt2 pt3 ahl ahh ahoff )
(setq oldsnap (getvar 'osmode))
(setq oldang (getvar 'angdir))
(setq pt1 (getpoint "\nPick lower left"))
(setvar 'osmode 0)
(if (not AH:getvalsm)(load "Multi Getvals.lsp"))
(setq ans (AH:getvalsm (list "Simple rectang" "Enter length" 8 7 "1" "Enter height " 8 7 "2")))
(setq ahL (atof (nth 0 ans)))
(setq ahH (atof (nth 1 ans)))
(setq pt2 (polar pt1 0.0 ahl))
(setq pt3 (polar pt2 (/ pi 2.0) ahH))
(command "rectang" pt1 pt3)
(setq ahoff (* 2.0 (* (getvar 'dimasz)(getvar 'dimscale)))) ; change offset as required
(setq pt4 (polar pt2  (* pi 1.5) ahoff)) 
(command "dim" "hor" pt1 pt2 pt4 "" "exit")
(setq pt4 (polar pt3 0.0 ahoff))
(command "dim" "Ver" pt2 pt3 pt4 "" "exit")
(setvar 'osmode oldsnap)
)
(ah:box)

 

image.png.261816df60b77138d2850a05244a89a6.png

Link to comment
Share on other sites

@BIGAL. Thanks for this piece of code. I think you reply on an earlier post from me from 27th dec. 2020. But that was just an example. I think I wasn't clear about what I meant. I do know how to draw a box but I used it as an example of a way to hold information about a box in the code.

 

Asume you have functions like:

(bx:width)               to get the width from the box.

(bx:width_ value)  to set the width.

(bx:height)             to get the height from the box.

(bx:height_ value) to set the height.

(bx:layer)                to get the layer for the box.

(bx:layer_ value)   to set the layer for the box.

 

Etc. I call this an object and it has this form

(("layer" . "some layer") ("width" . 'some width') (height . 'some height))

 

Nothing special just an Association List but the special thing is that I automatically
create functions to maintain the list. For each property a function to read (get) and

a function to write (let) the value of that property.

 

---------------------------------

In the lingo of my earlier example. I call a function as follows:

 

(bo:makeObj "bx" "box" '("p" (132 176) "width" 48.1 "height" 110 "layer" "boxlay")

 

(bo:makeObj . . . .) makes functions for "p" (the startingpoint), for "width", "height"

and "layer".

---------------------------------

 

Now (a bit closer to the subject of this thread), what happens in bo:makeObj is that

the lisp-code creates functions like (bx:width) that, when called, looks in an object
with the name 'box' to present a value. Or writes a value of 23 in the object with

(bx:width_ 23). Etc.

 

 

Ok, I am not good in explaining. But this is how I am investigating and trying things.
I found several ways to create the functions. It is also an answer to the quest for

applications. When is it useful to have your code, writing functions itself?

(I believe this is called, meta programming. If I am not mistaken LISP was created
for AI in the first place. The idea was that a small running program, could create
it's own behaviour in the form of functions, written by itself.)

 

But to be fair. I am a bit disappointed about the way it can be done with AutoLisp.

(Grrr has given the best and consise explanation I think.) It is fairly cumbersome

to create functions when parts of it are variable. Like the name of the properties

and the object that needs to be maintained. Juggling with the quotes and lists.

 

I found a completely other way to reach my goal. I write functions out in the form
of a long string "(defun . . . . . )". With (eval (read . . . )) I bring them into existence.

Before doing so I replace certain parts with search and replace.

Maybe, I am wandering of here.

 

Regards

 

 

 

Link to comment
Share on other sites

I think what your saying is make little functions that in turn use other predefined functions. That is what I call Library code or I guess CORE code.

 

The running program may have like 1 line that calls 100 lines of code in another program.

 

eg

 (if (not AH:getvalsm)(load "Multi Getvals.lsp"))
(setq ans (AH:getvalsm (list "This is heading" "Line 1" 5 4 "11" "Line2" 8 7 "22" "Line3" 8 7 "33" "Line4" 8 7 "4")))

 

This is a dcl input multi values from 1 to about 20 screen size limit.

 

For those of us around for a while using core code is part of speeding up coding and reliability, 1 package Iwas involved in has 100 lisp files, the majority of the routines make calls to the core package, ensuring layer control etc.

 

This is an example of get properties you decide what you want from an object.

 

 

; properties use as a library function
; By Alan H july 2020

(defun cords (obj / co-ords xy )
(setq coordsxy '())
(setq co-ords (vlax-get obj 'Coordinates))
(setq numb (/ (length co-ords) 2))
(setq I 0)
(repeat numb
(setq xy (list (nth (+ I 1) co-ords)(nth I co-ords) ))
(setq coordsxy (cons xy coordsxy))
(setq I (+ I 2))
)
)


(defun AH:chkcwccw (obj / lst newarea)
(setq lst (CORDS obj))
(setq newarea
(/ (apply (function +)
            (mapcar (function (lambda (x y)
                                (- (* (car x) (cadr y)) (* (car y) (cadr x)))))
                    (cons (last lst) lst)
                    l)) 
2.)
)
(if (< newarea  0)
(setq cw "F")
(setq cw "T")
)
)

; Can use reverse in Autocad - pedit reverse in Bricscad.

(defun plprops (obj txt / lst)
(foreach val lst
(cond
((= (strcase val)  "LAY") (setq lay (vla-get-layer obj)))
((= (strcase val) "AREA")(setq area (vla-get-area obj)))
((= (strcase val) "START")(setq start (vlax-curve-getstartpoint obj)))
((= (strcase val) "END" (strcase txt))(setq end (vlax-curve-getendpoint obj)))
((= (strcase val) "LEN" (strcase txt))(setq len (vlax-get obj 'Length)))
((= (strcase val) "CW" (strcase txt))(AH:chkcwccw obj))
((= (strcase val) "CORDS" (strcase txt))(CORDS obj))
)
)
)

(defun lineprops (obj lst / )
(foreach val lst
(cond
((= (strcase val)  "LAY") (setq lay (vlax-get obj 'layer)))
((= (strcase val) "START")(setq start (vlax-get obj 'startpoint)))
((= (strcase val) "END" (strcase txt))(setq end (vlax-get obj 'endpoint)))
((= (strcase val) "LEN" (strcase txt))(setq len (vlax-get obj 'Length)))
)
)
)

(defun circprops (obj lst / )
(foreach val lst
(cond
((= (strcase val)  "LAY") (setq lay (vlax-get obj 'layer)))
((= (strcase val) "LEN" (strcase txt))(setq len (vlax-get obj 'Circumference)))
((= (strcase val) "RAD" (strcase txt))(setq rad (vla-get-radius obj)))
((= (strcase val) "CEN" (strcase txt))(setq cen (vlax-get obj 'Center)))
((= (strcase val) "AREA" (strcase txt))(setq end (vlax-get obj 'AREA)))
)
)
)

(defun arcprops (obj txtlst)
(foreach val lst
(cond
((= (strcase val)  "LAY") (setq lay (vlax-get obj 'layer)))
((= (strcase val) "LEN" (strcase txt))(setq len (vlax-get obj 'length)))
((= (strcase val) "RAD" (strcase txt))(setq rad (vla-get-radius obj)))
((= (strcase val) "CEN" (strcase txt))(setq cen (vlax-get obj 'Center)))
((= (strcase val) "START" (strcase txt))(setq area (vlax-get obj 'startpoint)))
((= (strcase val) "END" (strcase txt))(setq area (vlax-get obj 'endpoint)))
((= (strcase val) "AREA" (strcase txt))(setq end (vlax-get obj 'AREA)))
)
)
)

; starts here
(setq ent (vlax-ename->vla-object (car (entsel "Pick Object "))))
; do a check for object type then use defun
; pick an example below


; many examples copy to command line for testing mix and match 
; (plprops ent '("LAY"))(princ lay)
; (plprops ent '("END"))(princ end)
; (plprops ent '("START"))(princ start)
; (plprops ent '("END" "START"))(princ end)(princ start)
; (plprops ent '("AREA" "LAY" "END" "START"))(princ area)(princ lay)(princ end)(princ start)
; (plprops ent '("START" "AREA" "LAY" "CW"))(princ start)(princ area)(princ cw)
; (plprops ent '("start" "END" "CORDS" "cw"))(princ start)(princ end)(princ coordsxy)(princ cw)
; (plprops ent '("CW"))(princ cw)
; (plprops ent '("AREA"))(princ area)
; (plprops ent '("CORDS"))(princ coordsxy)
; (lineprops ent "len"))(princ len)
; (lineprops ent '("len" "lay"))(princ len)(princ lay)
; (lineprops ent '("lay" "end" "start" "len"))(princ len)(princ lay)(princ start)(princ end)
; (circprops ent '("lay" "rad" "area" "cen"))(princ lay)(princ rad)(princ area)(princ cen)
; (circprops ent '("lay" "rad"))
; (arcprops ent '("lay" "rad"))




 

  • Like 1
Link to comment
Share on other sites

@BIGAL: Thanks for you attention for the subject. You have spend a considerable amount of time on it, which for me is a hobby; a search for the edges of (auto)Lisp and metaprogramming in particular. It is (of course) possible that I don't understand you, or that we don't understand eachother. If you like to quit, than we end this thread. I would understand and respect that.

 

I am well aware of the idea of subroutines and there usage. Call it a library or core code, fine with me. It is an important part of any substantial application. But what I tried to explain is something different. It is about meta programming, do you know that concept?

 

 

Remember that the issue was not 'subroutines' or 'object' or . . . The issue was creating functions with defun-q functions (meta programming). Than others pointed out that functions are just lists with a certain structure. Grrr has given a nice summary of that, earlier in this thread.

Than the question was if that meta programming has much practical use apart from altering the s::startup(). In turn I have given my approach of maintaining simple lists with data (I call them objects) as an example of something that might be useful.

 

 

Anyway, in my world (for this subject) there are a few routines and they are produced at load time, the routines where never there before loading the application. They are brought to life and not written out in a lisp-file. So in line with the earlier example,  I intend to maintain a simple object (a list):

 

(("layer" . "boxlay") ("width" . 48.1) (height . 110) ("p" 132 176))

 

(I know that an object in AutoCAD is something different! I call it an object and it is a holder for a simple set of data.) I call the object 'box' and give the routines to maintain it a prefix of 'bx:'. I do that by calling a routine at load time as follows:

 

(bo:makeObj "bx" "box" '("p" (132 176) "width" 48.1 "height" 110 "layer" "boxlay")

 

After this I have created the object. And a set of functions to maintain it. The functions can be used in my code as follows:

 

(bx:width)               to get the width from the box.

(bx:width_ value)  to set the width.

(bx:height)             to get the height from the box.

(bx:height_ value) to set the height.

Etc.

 

The functions will create the object if it doesn't exist yet, add properties (const' lists) if they don't exist or replace there values.

(In the example above a call the routine with the intended properties together with a starting value. That is why the object is created right away.
In other situations I create only the functions and the object (the list) does not exist from the start.)

 

So instead of (cdr (assoc "width" box)), I can use (bx:width).

 

And instead of (setq box (if (setq k (assoc "width" box)) (subst (cons "width" val) k box) (cons (cons "width" val) box)))

I can use (bx:width_ val)

 

The routines that are created at load time, assist me by allowing for simpler/ clearer code in the main routines. That is the profit.

 

 

Edited by eengebruiker
Link to comment
Share on other sites

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...