Nested loop in lisp to process a 2d array - what's good style (with and/or without loop macro)? Code below is answer to an exercise in Paul Graham's ANSI Common Lisp (ex. 4.1). The challenge is to rotate an array by 90 degrees. I found an example solution which I've commented below.

It seems ok'ish and I'm enjoying learning how to use do, but in this particular case it seems actually more verbose than the familir c-style nested for loops to process a 2d array. I'm wondering if the below is good style or if it can be improved upon?

Is the answer going to be the loop macro?
I'm avoiding that at the moment while learning, waiting till I have better mastery of the basics. Yet would like to not pick up bad habits. Hence this question about style regarding the below.

(defun quarter-turn (arr)
  (let* ((dim (array-dimensions arr))  ; let* sets sequentially, not in parallel.
         (row (first dim))
         (col (second dim))
         (n row)  ; initialise n to row, just to organise
         new-arr) ; shorthand for (new-arr nil)
    (cond ((not (= row col))
           (format t "The arg is not a square array.~%"))
          (t (setf new-arr (make-array dim :initial-element nil))
             (do ((i 0 (+ i 1)))  ; Q. surprisingly, lisp's 'do' here looks more
                 ((= i n))        ; verbose than c-style 'for'. Is this good style?
                 (do ((j 0 (+ j 1)))
                     ((= j n))
                     (setf (aref new-arr j i) (aref arr (- n i 1) j))))
             new-arr))))


Source for the above code: http://www.cs.uml.edu/~lhao/ai/lisp/ansi-common-lisp/solution-ch04.htm
A few comments about your code:


Why introduce a new variable n copy of another variable? If you want to be more concise you could use n and m insted of row and col (that I don't like because they are singular names, while they represent the number of rows and of columns).
Why define new-arr before the test and then assign it after the test of squareness? Don't by shy of use let as many times as you need, to introduce variables when they are really necessary.
Use assert instead of cond to check for correct parameters: in this way you give the user the opportunity of correct them, and produce a shorter and more readable code.
Why initialize the new array to a value nil which is immediately overwritten?
The fist do could return the result, so to avoid the last line of the function.
Use the primitive function 1+ instead of summing by 1.
Document the function with a comment about its scope as first form of the body of the function.


So, after this first set of comments, the function could be rewritten as:

(defun quarter-turn (arr)
  "rotate a square matrix 90° clockwise"
  (let* ((dim (array-dimensions arr))
         (n (first dim))
         (m (second dim)))
    (assert (= n m) (arr) "The argument is not a square array.")
    (let ((new-arr (make-array dim)))
      (do ((i 0 (1+ i)))
          ((= i n) new-arr)
        (do ((j 0 (1+ j)))
            ((= j n))
          (setf (aref new-arr j i) (aref arr (- n i 1) j)))))))


DO versus LOOP

As you have already noted, in this case do is more verbose than loop. I think that in a case like this they are more or less equivalent (just a personal opinion). I would prefer the loop form not only because it seems to me slightly more concise, but also because it is more “natural” for me to think of a classical nested loop for a bidimensional array in terms two nested loop forms:

(defun quarter-turn (arr)
  "rotate a square matrix 90° clockwise"
  (let* ((dim (array-dimensions arr))
         (n (first dim))
         (m (second dim)))
    (assert (= n m) (arr) "The argument is not a square array.")
    (let ((new-arr (make-array dim)))
      (loop for i below n
            do (loop for j below n
                     do (setf (aref new-arr j i) (aref arr (- n i 1) j))))
      new-arr)))