Author Topic: Tutorial 3 : AutoLisp : Make it pretty  (Read 2739 times)

0 Members and 1 Guest are viewing this topic.

JohnK

  • Administrator
  • Seagull
  • Posts: 10605
Tutorial 3 : AutoLisp : Make it pretty
« on: January 27, 2020, 11:50:29 AM »
Tutorial 3 : AutoLisp : Make it pretty
Because we will be developing and using more examples in these later tutorials I wanted to give a general note/reminder, theSwamp has a built in custom code tag we can use for code snip-its in our posts that links to our own help page. Feel free to click on a function (IF, ABS, APPLY, etc) and you will be brought to the AutoLisp help for that function.

Style
There are a few different indentation styles you can employ in/on your AutoLisp code.

One style is as old as the Lisp language itself. Another seems to be only prevalent in the AutoLisp community. Each style has good and bad attributes and it should be up to you to decide which you like better because as you will soon discover, you will spend far more of your time READING code then you do WRITING it. This is because you spend an awful amount of your time debugging and fixing errors.

I will attempt to lay out each style and give at least one solid reason for it's use.

The older style cam best be described as "compact". Many AutoLispers complain that it's much more unreadable then the other style however, I think if you know HOW to read it you may find it faster to read then the other. Here is a pretty good representation of the older ("compact") style.

Code - Auto/Visual Lisp: [Select]
  1. (defun SquareRoot ( x / squareroot-iter good-enough? improve average )
  2.         (defun squareroot-iter (guess)
  3.           (if (good-enough? guess)
  4.               guess
  5.               (squareroot-iter (improve guess))) )
  6.        
  7.         (defun good-enough? (guess)
  8.           (< (abs (- (* guess guess) x)) 0.001) )
  9.        
  10.         (defun improve (guess)
  11.           (average (list guess (/ x guess))) )
  12.        
  13.         (defun average (args)
  14.           (if args
  15.               (/ (apply '+ args)
  16.                  (length args))) )
  17.   (squareroot-iter 1.0) )

As you can see the closing parens are not easy to match with their respective opening paren but with many programming languages you read the code based on indention and/or syntax not necessarily just symbology (there are exceptions, of course); in other words, indentation is very important.  Notice the first IF statement in the "squareroot-iter" function below. The TEST, THEN, and ELSE all line up (are indented the same). This is the key; you do not try and locate the closing paren you look to see what arguments are lined up with each other. Juxtapose this with the IF statement in the "average" function, you will notice that the first use of IF has three parts while the second use of IF has only two (the TEST and the THEN). These clues, and others like them, are what guides you throughout the program and it's parts and structure.

The other style (we can call this the "newer style" but can also be called "expanded style" or "vertical style") is much more open. This style is read by matching parens to decipher the syntax's and structure.

Code - Auto/Visual Lisp: [Select]
  1. (defun SquareRoot ( x / squareroot-iter good-enough? improve average )
  2.         (defun squareroot-iter (guess)
  3.           (if (good-enough? guess)
  4.              guess
  5.              (squareroot-iter (improve guess))
  6.            )
  7.          )
  8.        
  9.         (defun good-enough? (guess)
  10.           (<
  11.             (abs (- (* guess guess) x))
  12.             0.001
  13.           )
  14.          )
  15.        
  16.         (defun improve (guess)
  17.           (average (list guess (/ x guess)))
  18.           )
  19.        
  20.         (defun average (args)
  21.           (if args
  22.             (/ (apply '+ args)
  23.                (length args))
  24.             )
  25.           )
  26.   (squareroot-iter 1.0)
  27.  )

While reviewing code, this style requires you to locate opening/closing parens because the syntax parts are not lined up as strictly as the other (thankfully, most good text editors will do this for you automatically). With this style, essentially each new line is indented 2-4 spaces more then the previous. One positive aspect of this style is the ability to digest the code in smaller chunks at a time (thus requiring you to build a sort of mind-map of the function/code as a whole). 

Most AutoLispers feel the newer style is much more friendly because of it's openness whereas they feel the older style is much more cluttered. However, as an exercise I would ask that you try to study code in both styles and decide which you prefer better (just remember how you read each style; old=look to indent and line ups new=look for matching parens) and choose your style for yourself. Please also try to remember that much of your time will be spent reading and maintaining the code you, or others, write and choose a style which you feel more comfortable with.

Comments
Contrary to what you may hear, even "good code" requires comments. All code (good/bad/otherwise) requires comments, for you and others LATER. I'm not saying every line should have a comment but every block of code should have a description of what it does or expects to do. A good habit to get into is to comment not only the expected behavior of the code block but also what it returns.

There has been a movement in new programming language development to create/be more "type safe"; this is because most programmers do not keep good track of the returns and types they expect. Furthermore, some people take advantage of loose function specifications and/or operations to save typing or to look like a better programmer (or for whatever reason) at the risk of an incorrect return type [-e.g. using a BOOLEAN function to ensure successful execution of a series of commands without regarding the return from that BOOLEAN wrapper function]. I'm not saying that would be a bad method all of the time--it can be an invaluable tool at times--but that specific example lends itself to a specific type/style of programming which most AutoLisp developers do not employ [-i.e. most AutoLisp developers tend to use a large sweeping and monolithic style where as the example I just gave tends to better employed in a tight, small black-box style of development where returns are more tightly controlled]. 

A specific experience of mine comes to mind, I started a CAD Manager position at a new company and I spent 9 months revising old convoluted code written by a predecessor. In one instance I deleted a hundred lines of code with one simple AND statement; I honestly felt bad about it because the previous coder had this (overly) complicated routine and all these comments and specifications and I come along and recognize that all of that could be a simple AND statement. However I couldn't have done that if the previous programmer didn't comment his code/thoughts (I had a very good idea as to his intentions). The previous programmer had fallen into a trap we all have at one point or another; we tend to loose sight of the forest when staring at the trees.

Because this tutorial lacks examples (is mostly wording) I will try to use another academic bit of code--which leans more towards a metaphor then usefulness--built for rational numbers to demonstrate the minimum amount of comments I feel would be necessary. This example would be more a "library of code" type of example where you may find yourself building a series of functions, built for a specific purpose, for use throughout your code.

NOTE: To show you that the rules that I discuss in these tutorials are not hard-fast rules--they are more like guidelines--I will use abbreviations. However, given the context of the code I would like you to note that there is no confusion as to meaning of the abbreviations. The abbreviations were chosen for artistic reasons (see how nicely everything lines up; notice how the "numerator", and "denominator" labels have been shorted to 5 characters, and the label "rational" was abbreviated to three characters to match the mathematical labels).

Also, I will use the "old" style for this code to give you an opportunity/exercise to read the old style of code. Remember, to fully appreciate the "old" style, pay attention to the number of arguments for the functions and the indentation of the code. You can also reformat this code in the VLIDE, Visual Studio Code, or most text editors to explore the code using the "vertical" style as well.

Code - Auto/Visual Lisp: [Select]
  1. (defun GreatestCommonDenominator (a b)
  2.   ;; Find the greatest common denominator of a number
  3.   (if (= b 0)
  4.       a
  5.       (GreatestCommonDenominator b (rem a b))))
  6.  
  7. (defun make-rat (n d / x)
  8.   ;; support procedure to ''create'' a rational number
  9.   (setq x (GreatestCommonDenominator n d))
  10.   (list (/ n x) (/ d x)))
  11.  
  12. (defun numer (x)
  13.   ;; return the numerator of a rational number.
  14.   (car x))
  15.  
  16. (defun denom (x)
  17.   ;; return the denominator of a rational number.
  18.   (cadr x))
  19.  
  20. (defun print-rat (x)
  21.   ;; support function to print the rational number in a pretty format.
  22.   (princ "\n ")
  23.   (princ (numer x))
  24.   (princ "/")
  25.   (princ (denom x))(princ))
  26.  
  27. (defun add-rat (x y)
  28.   ;; rational number addition.
  29.   (make-rat (+ (* (numer x) (denom y))
  30.                (* (numer y) (denom x)))
  31.             (* (denom x) (denom y))))
  32.  
  33. (defun sub-rat (x y)
  34.   ;; rational number subtraction
  35.   (make-rat (- (* (numer x) (denom y))
  36.                (* (numer y) (denom x)))
  37.             (* (denom x) (denom y))))
  38.  
  39. (defun mul-rat (x y)
  40.   ;; rational number multiply
  41.   (make-rat (* (numer x) (numer y))
  42.             (* (denom x) (denom y))))
  43.  
  44. (defun div-rat (x y)
  45.   ;; rational number divide
  46.   (make-rat (* (numer x) (denom y))
  47.             (* (denom x) (numer y))))
  48.  
  49. (defun equal-rat? (x y)
  50.   ;; does a rational number equal another.
  51.   (= (* (numer x) (denom y))
  52.      (* (numer y) (denom x))))

Notice I didn't comment on the returns--or possible returns--of each function. Should I have? ...I, personally feel, I should. The time investment of thinking about the potential returns is defiantly worth the cost; if you think about all the possible ways a function could be misused, you can choose what is and what isn't important to address in your code.  Now, that isn't to say, you need to build 100% bomb-proof code all the time but you need to make that choice more often then just writing a bit of code and expecting it to never be misused.


EDIT: Fixed spelling error in "make-rat" function.
« Last Edit: January 27, 2020, 12:49:14 PM by John Kaul (Se7en) »
TheSwamp.org (serving the CAD community since 2003)
Member location map - Add yourself

Donate to TheSwamp.org

BIGAL

  • Swamp Rat
  • Posts: 1398
  • 40 + years of using Autocad
Re: Tutorial 3 : AutoLisp : Make it pretty
« Reply #1 on: January 30, 2020, 03:04:57 AM »
2 nice options Blade in Briscad has a lisp styler and www.leee-mac.com lisp styler.
A man who never made a mistake never made anything

JohnK

  • Administrator
  • Seagull
  • Posts: 10605
Re: Tutorial 3 : AutoLisp : Make it pretty
« Reply #2 on: January 30, 2020, 08:37:22 AM »
I'm not sure I follow your train of thought. -i.e. `styler's' (formatter's) exist all over the place; my text editor--along with every major text editor--has one, two, or ten. But, their use was not really the point I was trying to make in the tutorial.

Side-bar: Writing a "lisp formatter" leans more towards the "compiler" side of programming studies because then you get into language semantics, abstract syntax trees, and etc.. Fun stuff but not really for beginners. We could write one in another group based project if you think that would be a fun project. <thought>...However, in this series, we build more of a "dumb formatter" and only have horizontal margin as our criteria. We'll see. I'm not quite there yet. On second thought, not really the best of ideas.
</thought>
« Last Edit: January 30, 2020, 09:23:23 AM by John Kaul (Se7en) »
TheSwamp.org (serving the CAD community since 2003)
Member location map - Add yourself

Donate to TheSwamp.org

It's Alive!

  • Retired
  • Needs a day job
  • Posts: 8661
  • AKA Daniel
Re: Tutorial 3 : AutoLisp : Make it pretty
« Reply #3 on: January 31, 2020, 04:41:50 AM »
I really like self documenting code like this  :smitten:, so I think comments like this are redundant....

Code - Lisp: [Select]
  1. (defun GreatestCommonDenominator (a b)
  2.   ;; Find the greatest common denominator of a number
  3.  

Then we get to other function names like sub-rat, The next poor soul must read the comment or dive into the code. It’s hard to name some functions… My boss once told me, make good names, even if it takes 15 minutes


It's Alive!

  • Retired
  • Needs a day job
  • Posts: 8661
  • AKA Daniel
Re: Tutorial 3 : AutoLisp : Make it pretty
« Reply #4 on: January 31, 2020, 04:50:10 AM »
Also, IMHO, never use function names that include characters that are operators in other languages.  :crazy2:

It's Alive!

  • Retired
  • Needs a day job
  • Posts: 8661
  • AKA Daniel
Re: Tutorial 3 : AutoLisp : Make it pretty
« Reply #5 on: January 31, 2020, 05:18:25 AM »
Maintaining proper formatting is pretty critical.
From a readability perspective and from a version control software perspective. WinMerge totally freaks out if a chuck of code gets prettified by another prettifier

tombu

  • Bull Frog
  • Posts: 288
  • ByLayer=>Not0
Re: Tutorial 3 : AutoLisp : Make it pretty
« Reply #6 on: January 31, 2020, 07:56:23 AM »
I prefer the second, but prefer opening and closing parentheses to be in same position to make their matching obvious even without spacing the lines. I generally space them anyway and comment them as well. For system variables that aren't used also as commands like AREA I often use their names as local variables as well. No conflict and what could be more descriptive?

Having downloaded a lot of code by others I've seen enough to be able to read the easily enough as long as the same style is used throughout.
Code: [Select]
(defun SquareRoot ( x / squareroot-iter good-enough? improve average )
        (defun squareroot-iter (guess)
          (if (good-enough? guess)
             guess
             (squareroot-iter (improve guess))
          )
        )
        (defun good-enough? (guess)
          (<
            (abs (- (* guess guess) x))
            0.001
          )
        )
        (defun improve (guess)
          (average (list guess (/ x guess)))
        )
        (defun average (args)
          (if args
            (/ (apply '+ args) (length args))
          )
        )
        (squareroot-iter 1.0)
)
Tom Beauford P.S.M.
Leon County FL Public Works - Windows 7 64 bit AutoCAD Civil 3D

JohnK

  • Administrator
  • Seagull
  • Posts: 10605
Re: Tutorial 3 : AutoLisp : Make it pretty
« Reply #7 on: January 31, 2020, 09:44:24 AM »
I really like self documenting code like this  :smitten:, so I think comments like this are redundant....

Code - Lisp: [Select]
  1. (defun GreatestCommonDenominator (a b)
  2.   ;; Find the greatest common denominator of a number
  3.  

Then we get to other function names like sub-rat, The next poor soul must read the comment or dive into the code. It’s hard to name some functions… My boss once told me, make good names, even if it takes 15 minutes
:) I like the long names too and yes it is very hard sometimes. Sometimes, 15 minutes would be a blink-of-an-eye; I'll code up a function and go back after I've thought about it for awhile (then I sometimes have to go back to the code and fix it *lol*).

I agree about the function names containing operators too but...
TheSwamp.org (serving the CAD community since 2003)
Member location map - Add yourself

Donate to TheSwamp.org

BIGAL

  • Swamp Rat
  • Posts: 1398
  • 40 + years of using Autocad
Re: Tutorial 3 : AutoLisp : Make it pretty
« Reply #8 on: January 31, 2020, 08:54:09 PM »
Some of the more complex code where its indented using tabs ends up so far to the right that you can not read it with out forcing a scroll.

I don't know why some people use multiple line coding yes sometimes for extra clarification.
Code: [Select]
(defun alg-ang (obj pnt)
  (angle '(0. 0. 0.)
     (vlax-curve-getfirstderiv
       obj
       (vlax-curve-getparamatpoint
         obj
         pnt
       )
     )
  )
)
Code: [Select]
(defun alg-ang (obj pnt)
  (angle '(0. 0. 0.)  (vlax-curve-getfirstderiv   obj    (vlax-curve-getparamatpoint  obj  pnt )   )   )
)
A man who never made a mistake never made anything