t3x.org / sketchy / vol1 / sl26.html

Sketchy LISP

  By Nils M Holm, 2006,2007,2008
Buy a copy at Lulu.com

An Introduction to Functional Programming in Scheme

A.2 Scheme Style Guide

The rules and templates listed in this appendix are useful for writing readable Scheme programs. They are intended to reflect the logical structure of programs. Of course, style is a highly personal matter, so feel encouraged to develop your own one.

Conventions are there to be bent or broken occasionally. When the rules get in your way, feel free to ignore them. Make your code as readable as you can. Your code is a document written in an abstract language that describes how to solve a specific problem. It just happens to be executable.

Here are the most basic rules for formatting Scheme programs:

The following program would be acceptable, but not really beautiful:

(define (foo x)
  (and
    (pair?
      x)
    (pair?
      (cdr
        x))
    (cadr x)))

This is why there are two additional rules:

(define (foo x)
  (and
    (pair? x)
    (pair? (cdr x))
    (cadr x)))
(define (foo x)
  (and (pair? x)
       (pair? (cdr x))
       (cadr x)))

These simple rules are sufficient for writing quite readable code. Further details and exceptions follow below.

A.2.1 Definitions and Bindings

Most variable definitions are one-liners:

(define variable value)

Variable definitions with lengthy values (like procedures) have their values indented:

(define variable
  (lambda (formal ...)
    body))

The following style is preferred for static procedures (procedures whose names are not assigned different values at a later time):

(define (procedure formal ...)
  body)

Very short procedures can be defined in single lines:

(define (procedure formal) body)

The structure of define-syntax and syntax-rules is rather static:

(define-syntax keyword
  (syntax-rules (symbol ...)
    (pattern template)
    ...
    (pattern template)))

or:

(define-syntax keyword
  (syntax-rules (symbol ...)
    (pattern
      template)
    ...
    (pattern
      template)))

Both patterns and templates of syntax rules are formatted like code, not like data, e.g.:

(define-syntax when
  (syntax-rules ()
    ((_ predicate consequent ...)
       (if predicate
           (begin consequent
                  ...)))))

Short lambda functions are placed in a single line:

(lambda (formal ...) body) 

Otherwise the body is indented:

(lambda (formal ...)
  body)

Let and let* both follow the same scheme. Their first arguments are indented like data at the first level of nesting, so the opening parentheses in front of the variables are in the same column:

(let ((variable value)
      ...
      (variable value))
  body)

(let* ((variable value)
       ...
       (variable value))
  body)

When a value of let or let* does not fit in a single line, it is indented as usual:

(let ((s (cond ((positive? x) "positive")
               ((negavive? x) "negative")
               (else          "zero"))))
  (string-append "x is " s))

When horizontal space is tight, letrec-style indentation may be used.

Because letrec is mostly used to define procedures, letrec itself and the symbols bound by it are placed in separate lines to save horizontal space:

(letrec
  ((symbol
     (lambda (formal ...)
       body))
   ...
   (symbol
     (lambda (formal ...)
       body)))
  letrec-body)

A.2.2 Procedure Application

There are not many choices for procedures of no arguments:

(procedure)

Applications that fit in a single line are placed in a single line:

(procedure argument ...)

In longer expressions, the arguments are lined up with respect to a common column:

(procedure argument
           ...
           argument)

If there is insufficient horizontal space, arguments may be indented in the lines following the procedure:

(procedure
  argument
  ...
  argument)

Short lambda function applications are placed in a single line:

((lambda (variable ...) body) argument ...)

The arguments of more complex applications are indented by one blank so that they share the first column with the beginning of the lambda function itself:

((lambda (variable ...)
   body)
 argument
 ...
 argument)

You will rarely need this, though, unless you are dealing with lambda calculus.

A.2.3 Conditionals, Logic, Etc

Applications of and, begin, if, and or are preferably indented in the following way (but short applications may be compacted to single lines):

(and expression
     ...
     expression)

(begin expression
       ...
       expression)

(if predicate         (if predicate
    consequent            consequent)
    alternative)

(or expression
    ...
    expression)

The difference between the following case styles is mostly a matter of taste (the left one may save some horizontal space, though):

(case expression    (case expression
  (list body)             (list body)
  ...                     ...
  (list body))            (list body))

When the lists of cases are long or have different lengths, the following style may be preferable:

(case expression
  (list
    body)
  ...
  (list
    body))

When all lists of cases have similar lengths, the bodies may be adjusted with respect to a common column:

(case digit
      ((1 3 5 7 9) "odd")
      ((0 2 4 6 8) "even")
      (else        "not a digit"))

The formatting of cond is similar to that of case. This style is used for short precidates and bodies:

(cond (predicate body)
      ...
      (predicate body))

In cond expressions with long precicates and/or bodies the following format saves some horizontal space:

(cond (predicate
        body)
      ...
      (predicate
        body))

This format squeezes out four more characters:

(cond
  (predicate
    body)
  ...
  (predicate
    body))

Even in cond two left-adjusted columns may be used for predicates and bodies:

(cond ((< x 0) "negative")
      ((> x 0) "positive")
      (else    "zero"))

A.2.4 Data and Quotation

When quoted lists fit in a single line, this is how it should be done:

'(member ...)
(quote (member ...))

Otherwise members are indented by a single character:

'(member          (quote (member
  ...                     ...
  member)                 member))

'((member ...)    (quote ((member ...)
  ...                     ...
  (member ...))           (member ...)))

The following template illustrates why indentation by a single character is essential, especially in deeply nested lists:

'(((member ...)
   ...
   (member ...))
  ((member ...)
   ...
   (member ...)))

Although quasiquoted forms are technically data, they are mostly used to form expressions, so they are formatted in the style of code:

`(if ,predicate
     ,consequent
     ,alternative)

`(lambda ,formals
   ,@body)