Sketchy LISP |
By Nils M Holm,
2006,2007,2008 Buy a copy at Lulu.com |
An Introduction to Functional Programming in Scheme
| Previous Section | - Contents - Index - | Next Section |
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.
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)
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.
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"))
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)
| Previous Section | - Contents - Index - | Next Section |