Golo Roden
Golo Roden

Reputation: 150624

Indentation of Lisp code

I have written some Lisp code, and it works, but I am not sure how to indent it properly.

Basically I have a global variable and three functions:

(setf my-hand '((3 hearts)
                (5 clubs)
                (2 diamonds)
                (4 diamonds)
                (ace spades)))

(defun rank (card)
  (car card))

(defun suit (card)
  (cadr card))

(defun count-suit (suit hand)
  (length (remove-if-not #'(lambda (card) (equal suit (suit card))) hand)))

I'm fine with the global variable and the functions rank and suit, but what about count-suit? How should I wrap its body and indent it? I can think of several ways, but can't decide on what seems right.

Any hints?

Is there a canonical way of how to do this?

Upvotes: 1

Views: 3004

Answers (3)

Rainer Joswig
Rainer Joswig

Reputation: 139261

Note that there is a slight difference between indenting and formatting.

Indenting usually means to move contents of a line horizontally. Usually we know already what is on the line and the line before. If you ask a typical editor to indent, it will do nothing more than adjusting the horizontal position of the line content. It will not distribute expressions over lines.

Formatting means to layout out code over one or more lines. Lisp provides a pretty printer for automatic layout. But in the editor full layout is not that well supported, especially since the rules can be complicated and it is kind of difficult to deal with comments and other non-s-expression code contents. Layout of macros is based on simple principles. Automatic layout for more complex macros like LOOP would be really difficult.

Your question is actually about formatting.

(length (remove-if-not #'(lambda (card) (equal suit (suit card))) hand))

Can I identify function calls? What are the arguments? What is syntax? What about line-length? Indentation depth?

Let's look at the function calls: place them more prominently:

(length
 (remove-if-not
  #'(lambda (card)
      (equal
       suit
       (suit card)))
  hand))

Above doesn't look so bad.

Maybe we want to concentrate on the arguments and make sure that two or more arguments are on separate lines:

(length (remove-if-not #'(lambda (card)
                           (equal suit
                                  (suit card)))
                       hand))

Typically we want short arglists to be on one line, if the line is not too long:

(length (remove-if-not #'(lambda (card)
                           (equal suit (suit card)))
                       hand))

Above is what I would write in this case. Code structure is clear enough and it does not waste too much space.

Formatting code means applying a lot of local and global constraints/rules.

If we look at the expression, we also would want to write it differently, because it triggers a lot of things one typically does not like:

  • it counts, but it does not use a counting function
  • it does unnecessary work
  • it creates a lambda expression for extracting a value and testing it against an item: extract and test

So:

(count suit hand :key #'suit :test #'eql)

or just (eql is the default):

(count suit hand :key #'suit)

Back to formatting. We can make a few experiments and see how Lisp does it, since it has a code formatter built in (here Clozure Common Lisp):

? (defun test ()
    (dolist (*print-right-margin* '(80 60 40 30))
      (format t "~%Margin: ~a" *print-right-margin*)
      (pprint '(length (remove-if-not #'(lambda (card) (equal suit (suit card))) hand)))))
TEST
? (test)

Margin: 80
(LENGTH (REMOVE-IF-NOT #'(LAMBDA (CARD) (EQUAL SUIT (SUIT CARD))) HAND))
Margin: 60
(LENGTH (REMOVE-IF-NOT
          #'(LAMBDA (CARD) (EQUAL SUIT (SUIT CARD)))
          HAND))
Margin: 40
(LENGTH
 (REMOVE-IF-NOT
  #'(LAMBDA
     (CARD)
     (EQUAL SUIT (SUIT CARD)))
  HAND))
Margin: 30
(LENGTH
 (REMOVE-IF-NOT
  #'(LAMBDA
     (CARD)
     (EQUAL
      SUIT
      (SUIT CARD)))
  HAND))

Even though manually formatted code might look better in many cases, it is useful to get familiar with automatic formatting (aka pretty printing or 'grinding') and being able to deal with it.

Upvotes: 8

Joshua Taylor
Joshua Taylor

Reputation: 85843

Chris' answer addresses indentation, but there are a few other points, too. First, setf doesn't declare global variables. For that you need defvar or defparameter, and you should follow the "earmuff" convention:

(defparameter *my-hand*
  '((3 hearts)
    (5 clubs)
    (2 diamonds)
    (4 diamonds)
    (ace spades)))

You can actually remove some of the indentation issues with count-suit by using the keyword arguments to remove. In this case, you want to remove cards with a different suit from the hand. That means you can call remove with suit, with a negated comparison for the test, and using a key function to get the suit from each card:

(defun count-suit (suit hand)
  (length (remove suit hand
                  :key #'suit
                  :test (complement #'eql)))) ; or `:test-not #'eql`

(count-suit 'diamonds *my-hand*)
;;=> 2

But even that's more verbose than it needs to be, since Common Lisp already provides a count function that also has a key argument, so that you can do this:

(defun count-suit (suit hand)
  (count suit hand :key #'suit))

(count-suit 'hearts *my-hand*)
;;=> 1

Also, with regard to the accessors, you may be interested in using defstruct to define them. You can tell defstruct to use a list as its underlying representation. This means you can do:

(defstruct (card (:type list))
  rank
  suit)

(make-card :rank 3 :suit 'hearts)
;;=> (3 hearts)

(card-rank '(ace spaces))
;;=> ace

(card-suit '(5 clubs))
;;=> clubs

Upvotes: 6

C. K. Young
C. K. Young

Reputation: 223023

I'd use this:

(defun count-suit (suit hand)
  (length (remove-if-not (lambda (card)
                           (equal suit (suit card)))
                         hand)))

Alternatively, this is okay too:

(defun count-suit (suit hand)
  (length
   (remove-if-not (lambda (card)
                    (equal suit (suit card)))
                  hand)))

Note that the remove-if-not is only one-space indented from the length, since length is not a body-containing form. Read Riastradh's Lisp Style Rules for more guidance.

Upvotes: 2

Related Questions