Author Topic: Self-commenting code  (Read 1119 times)

0 Members and 1 Guest are viewing this topic.

JohnK

  • Administrator
  • Seagull
  • Posts: 10626
Self-commenting code
« on: March 14, 2023, 09:56:57 AM »
People have often heard that code can be "self-commenting" but that--in lisp--may not mean what you think it does (more often than not I see chunks of code a few lines long with no comments at all and when asked, they always say the "comments are not necessary because the code comments itself"; which is almost always NOT true). The best example of `self-commenting` code I can think of (because it was the first example I saw), comes from the SICP book on the example of rational numbers. The topic/example was in essence about using bottom-up design to support a new data type called a "pair" but it also created what is called `self-commenting` code.

The example starts by building seemingly trivial functions, but those functions assign a "name" to concepts which in turn will result in `self-commenting` code.

Code - Auto/Visual Lisp: [Select]
  1. (defun numer (pair)
  2.   ;; (numer <pair>)
  3.   ;; return the numerator of a rational number.
  4.   (car pair) )
  5.  
  6. (defun denom (pair)
  7.   ;; (denom <pair>)
  8.   ;; return the denominator of a rational number.
  9.   (cdr pair) )

As you can see these two functions could be replaced with simple calls to `CAR` and `CDR` but the name--the function name--is what is important, not the actual functions within.

Below are a few functions to make a rational number and another to print one and some to perform some arithmetic on the rational numbers.

Code - Auto/Visual Lisp: [Select]
  1. (defun make-rat (n d / x)
  2.   ;; (make-rat <n> <d>)
  3.   ;; returns the rational number whose numerator is the integer <n>
  4.   ;; and whose denominator is the integer <d>.
  5.   (cons n d) )
  6.  
  7. (defun print-rat (pair)
  8.   ;; (print-rat <pair>)
  9.   ;; prints a rational number with a slash.
  10.   (princ "\n ")
  11.   (princ (numer pair))
  12.   (princ "/")
  13.   (princ (denom pair))(princ) )
  14.  
  15. (defun add-rat (pairx pairy)
  16.   ;; (add-rat <pairx> <pairy>)
  17.   ;; preforms addition on two rational number pairs.
  18.   (make-rat (+ (* (numer pairx) (denom pairy))
  19.                (* (numer pairy) (denom pairx)))
  20.             (* (denom pairx) (denom pairy))) )
  21.  
  22. (defun sub-rat (pairx pairy)
  23.   ;; (sub-rat <pairx> <pairy>)
  24.   ;; preform subtraction on two rational number pairs.
  25.   (make-rat (- (* (numer pairx) (denom pairy))
  26.                (* (numer pairy) (denom pairx)))
  27.             (* (denom pairx) (denom pairy))))
  28.  
  29. (defun mul-rat (pairx pairy)
  30.   ;; (mul-rat <pairx> <pairy>)
  31.   ;; preform multiplication on two rational number pairs.
  32.   (make-rat (* (numer pairx) (numer pairy))
  33.             (* (denom pairx) (denom pairy))) )
  34.  
  35. (defun div-rat (pairx pairy)
  36.   ;; (div-rat <pairx> pairy>)
  37.   ;; preform division on two rational number pairs.
  38.   (make-rat (* (numer pairx) (denom pairy))
  39.             (* (denom pairx) (numer pairy))) )
  40.  
  41. (defun equal-rat? (pairx pairy)
  42.   ;; (eual-rat? <pairx> <pairy>)
  43.   ;; determine if two rational number pairs are equal.
  44.   (= (* (numer pairx) (denom pairy))
  45.      (* (numer pairy) (denom pairx))) )

Now we can begin to construct `self-commenting` code. As you can see the operations are pretty self-explanatory; each operation lists the parts and operations and if you try to follow along you can go back upwards into the support procedures and see what parts are what/where. This is because of the names we assigned to each part.

Code - Auto/Visual Lisp: [Select]
  1. ;; make a few rational numbers.
  2. (setq one-half  (make-rat 1 2)
  3.       one-third (make-rat 1 3))
  4.  
  5. ;; print them to the command line.
  6. (print-rat one-half)
  7. (print-rat one-third)
  8.  
  9. ;; add two rational numbers and print the result.
  10. (print-rat
  11.   (add-rat
  12.     one-half
  13.     one-half))
  14.  
  15. ;; subtract two rational numbers and print the results.
  16. (print-rat
  17.   (sub-rat
  18.     one-half
  19.     one-third))

Because of the "bottom-up" and `self-commenting` design this example, we can change the way our program handles rational numbers--easier--without having to refactor. For example, if we alter the `make-rat` function to reduce the rational numbers to their lowest terms before actual construction of the `pair` the entire program is altered without much change.

Code - Auto/Visual Lisp: [Select]
  1. (defun make-rat (n d)
  2.   ;; (make-rat <n> <d>)
  3.   ;; returns the rational number whose numerator is the integer <n>
  4.   ;; and whose denominator is the integer <d>.
  5.   ;;
  6.   ;; NOTE: both <n> and <d> are reduced to their lowest terms before
  7.   ;;       construction of the rational number.
  8.   (
  9.    (lambda (x)
  10.      (cons (/ n x) (/ d x)))
  11.    (gcd n d)) )

This example, given in the SICP book, was a very good example because it taught many lessons in one, but I think it demonstrates the "proper definition of `self-commenting code`" well. However, in reality, the code itself cannot document the "why" something was done, so comments in my opinion are always necessary.

Full code listing:

Code - Auto/Visual Lisp: [Select]
  1. (defun numer (pair)
  2.   ;; (numer <pair>)
  3.   ;; return the numerator of a rational number.
  4.   (car pair) )
  5.  
  6. (defun denom (pair)
  7.   ;; (denom <pair>)
  8.   ;; return the denominator of a rational number.
  9.   (cdr pair) )
  10.  
  11. (defun make-rat (n d)
  12.   ;; (make-rat <n> <d>)
  13.   ;; returns the rational number whose numerator is the integer <n>
  14.   ;; and whose denominator is the integer <d>.
  15.   ;;
  16.   ;; NOTE: both <n> and <d> are reduced to their lowest terms before
  17.   ;;       construction of the rational number.
  18.   (
  19.    (lambda (x)
  20.      (cons (/ n x) (/ d x)))
  21.    (gcd n d)) )
  22.    
  23. (defun print-rat (pair)
  24.   ;; (print-rat <pair>)
  25.   ;; prints a rational number with a slash.
  26.   (princ "\n ")
  27.   (princ (numer pair))
  28.   (princ "/")
  29.   (princ (denom pair))(princ) )
  30.  
  31. (defun add-rat (pairx pairy)
  32.   ;; (add-rat <pairx> <pairy>)
  33.   ;; preforms addition on two rational number pairs.
  34.   (make-rat (+ (* (numer pairx) (denom pairy))
  35.                (* (numer pairy) (denom pairx)))
  36.             (* (denom pairx) (denom pairy))) )
  37.  
  38. (defun sub-rat (pairx pairy)
  39.   ;; (sub-rat <pairx> <pairy>)
  40.   ;; preform subtraction on two rational number pairs.
  41.   (make-rat (- (* (numer pairx) (denom pairy))
  42.                (* (numer pairy) (denom pairx)))
  43.             (* (denom pairx) (denom pairy))))
  44.  
  45. (defun mul-rat (pairx pairy)
  46.   ;; (mul-rat <pairx> <pairy>)
  47.   ;; preform multiplication on two rational number pairs.
  48.   (make-rat (* (numer pairx) (numer pairy))
  49.             (* (denom pairx) (denom pairy))) )
  50.  
  51. (defun div-rat (pairx pairy)
  52.   ;; (div-rat <pairx> pairy>)
  53.   ;; preform division on two rational number pairs.
  54.   (make-rat (* (numer pairx) (denom pairy))
  55.             (* (denom pairx) (numer pairy))) )
  56.  
  57. (defun equal-rat? (pairx pairy)
  58.   ;; (eual-rat? <pairx> <pairy>)
  59.   ;; determine if two rational number pairs are equal.
  60.   (= (* (numer pairx) (denom pairy))
  61.      (* (numer pairy) (denom pairx))) )

I hope you have as much fun studying this seemingly simple example as I did.


EDIT: Fixed small typo in the first DENOM function listed.
EDIT: Fixed function CDR in DENOM function (Bug by VovKa).
EDIT: changed function LIST to CONS in lowest terms make-rat (Bug by VovKa).
« Last Edit: March 15, 2023, 09:49:43 AM by JohnK »
TheSwamp.org (serving the CAD community since 2003)
Member location map - Add yourself

Donate to TheSwamp.org

kdub_nz

  • Mesozoic keyThumper
  • SuperMod
  • Water Moccasin
  • Posts: 2132
  • class keyThumper<T>:ILazy<T>
Re: Self-commenting code
« Reply #1 on: March 14, 2023, 03:05:49 PM »
For those interested
The SICP Book @
https://mitp-content-server.mit.edu/books/content/sectbyfn/books_pres_0/6515/sicp.zip/index.html

Remove the /index.html from the link for the full Zip compile.
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.

VovKa

  • Water Moccasin
  • Posts: 1629
  • Ukraine
Re: Self-commenting code
« Reply #2 on: March 14, 2023, 04:23:26 PM »
shouldn't denom be cdr?

JohnK

  • Administrator
  • Seagull
  • Posts: 10626
Re: Self-commenting code
« Reply #3 on: March 14, 2023, 05:57:43 PM »
Thank you. Fixed.
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: 8691
  • AKA Daniel
Re: Self-commenting code
« Reply #4 on: March 14, 2023, 06:42:10 PM »
What I dislike about this example is, they went through the trouble of fully typing ‘rational’ in the comments, but for some reason they must truncate the function name,  what’s wrong with using something like
Code - Auto/Visual Lisp: [Select]
  1. (defun Math:MakeRational(numerator denominator /)
  2. ...
  3. )
  4.  
then the need for the comment just goes away, except the behavior note is still valid.

If you look at the code design, its clearly intended to be an API for reuse, in this case the writer has a duty to make the code extra clear.

It's Alive!

  • Retired
  • Needs a day job
  • Posts: 8691
  • AKA Daniel
Re: Self-commenting code
« Reply #5 on: March 14, 2023, 06:45:40 PM »
In lisp its sometimes hard to make out the intended type, nothing wrong with a little Hungarian notation


Code - Auto/Visual Lisp: [Select]
  1. (defun Math:MakeRational(iNumerator iDenominator /)
  2. ...
  3. )
  4.  

or

Code - Auto/Visual Lisp: [Select]
  1. (defun Math:MakeRational(fNumerator fDenominator /)
  2. ...
  3. )
  4.  


JohnK

  • Administrator
  • Seagull
  • Posts: 10626
Re: Self-commenting code
« Reply #6 on: March 14, 2023, 07:54:47 PM »
I agree about the names but I suspect when this was written screen terminals were probably still being used so, I assume the 80 char rule had some factor.
...
Trying: "(math:print-rational (math:subtract-rational one-half one-third))" is only 66 so taking a 2 tab-stop and some indent, you would still fall within the 76-80 char range so that theory is sort of out the door. They should have used better naming--but I see 100 times worse naming, now, then this 40+ year old code.


Also general note (full disclosure): I took a lot of this code from my notes when I took the class and I had an AutoLisp version as well which I used for this post. The Scheme `make-rat` with lowest terms used a LET statement, but my AutoLisp version used the lambda construct (I thought that was pretty clever on my part).
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: 8691
  • AKA Daniel
Re: Self-commenting code
« Reply #7 on: March 15, 2023, 01:32:01 AM »
Wondering if self documenting code would have helped here  :lmao:
https://en.wikipedia.org/wiki/Mars_Climate_Orbiter#Cause_of_failure

Code - C++: [Select]
  1. namespace MarsClimateOrbiter
  2. {
  3.     class NavigationService
  4.     {
  5.     public:
  6.         void FireThrusters(double force, double duration);
  7.     };
  8. }
  9.  

VovKa

  • Water Moccasin
  • Posts: 1629
  • Ukraine
Re: Self-commenting code
« Reply #8 on: March 15, 2023, 02:45:43 AM »
The Scheme `make-rat` with lowest terms used a LET statement, but my AutoLisp version used the lambda construct (I thought that was pretty clever on my part).
i guess it should be cons not list