Why have a style guide?
Code was made for the human, not only for the machine, otherwise we’d all be writing programs in binary. Style is not merely a matter of aesthetics. Consistency lifts a burden from the mind, and, with experience, improves human performance. Style is a practical matter.
Code is written once, and rewritten many times. It is read much more than it is written, and often by multiple individuals, so it is that much more important to make code easy to read and edit than to write.
To the uninitiated, Lisp is an unreadable homogenous ball of mud. Lots of Irritating Superfluous Parentheses. That’s L.I.S.P. How can anyone even read that?
Consistency. Experience. I’ll let you in on the secret:
Real Lispers don’t count the brackets.
Don’t Count the Brackets¶
It is impossible for the human to comprehend the code of a nontrivial program in totality; working memory is too small. We handle complexity by chunking it into hierarchies of labeled black boxes within boxes within boxes. Mental recursive trees.
But syntax trees are the very essence of Lisp, and you understand them in the same hierarchical way. There are, of course, many more layers of abstractions below Hissp, but at the bottom of Hissp’s hierarchy are tuples of atoms. Conceptually, it’s a box with a label and contents.
LABEL item1 item2 ...
Order matters in Hissp, and repeats are allowed. Mathematically speaking, these boxes are tuples, not just sets. The first element is the label. The rest are contents.
When it gets complicated is when the items are themselves boxes. How do we keep that readable?
Why, the same way we do in Python, of course: indentation.
If your code is properly formatted, you will be able to delete all the trailing brackets and unambiguously reconstruct them from indentation alone.
Given code like this,
(define fib (lambda (n) (if-else (le n 2) n (add (fib (sub n 1)) (fib (sub n 2))))))
this is what the experienced human sees.
(define fib (lambda (n (if-else (le n 2 n (add (fib (sub n 1 (fib (sub n 2
Learn to see it that way. There is no missing information.
While the following will compile fine, if you write it this way,
(define fib(lambda(n)(if-else( le n 2)n(add(fib( sub n 1))(fib(sub n 2)))))
Just kidding! This doesn’t really compile because the parentheses are not balanced. You didn’t notice, did you? You didn’t count them. I left one off to prove a point: The bracket trails are there for the computer, not the human. It’s much easier to program a parser to read brackets than indentation.
While surprisingly controversial, I feel that making the computer read the structure via indentation like the human does, instead of via a parallel language written with brackets, is one of the things that Python got right. With a bracketed language, on the other hand, these two delimiters can get out of sync, and the human and computer no longer agree on the meaning of the code.
To some extent, this is a criticism that applies to any language with bracketed code blocks, but it’s especially bad for Lisp, which is so syntactically regular that it has almost no other cues that could compensate for bad indentation. That’s the “ball of mud”. For Lisp, proper indentation is not just best practice, it’s an absolute necessity for legibility.
Unlike Python, with Lisp, you have to take on the extra responsibility to keep these two block delimiters in sync. This is hard to do consistently without good editor support. But because the brackets make it easy to parse (for a computer), editor support for Lisp is really very good. Emacs can do it, but it’s got a bit of a learning curve. For a beginner, try installing Parinfer in a supported editor, like Atom. If you get the indent right, Parinfer will manage the trails for you. Parinfer makes editing Lisp feel more like editing Python.
Lisp style guides are hard to find online, because most Lispers will simply tell you to let Emacs handle it. While I strongly recommend using editor support for Lissp files, Hissp is a modular system. You might need to format raw Hissp in readerless mode, or Lissp embedded in a string or notebook cell or documentation or comment on some web forum, or maybe you’re one of those people who insists on programming in Notepad. Or maybe you want to write one of these editor tools in the first place. For these reasons, I feel the need to spell out what good indentation looks like well enough that you could do it by hand.
The only absolute rules are
Don’t Dangle Brackets
If you break these rules, Parinfer can’t be used. Team projects with any Lissp files should be running Parlinter along with their tests to enforce this. Basic legibility is not negotiable. Use it.
Don’t Dangle Brackets¶
Trailing brackets are something we try to ignore. Trailing brackets come in trains. They do not get their own line; that’s more emphasis than they deserve. They don’t get extra spaces either.
;; Wrong. (define fib (lambda (n) (if-else (le n 2) n (add (fib (sub n 1) ) (fib (sub n 2) ) ) ) ) ) ;; Still wrong. ( define fib ( lambda ( n ) ( if-else ( le n 2 ) n ( ..add ( fib ( sub n 1 ) ) ( fib ( sub n 2 ) ) ) ) ) )
This also goes for readerless mode.
# Very wrong. ( "define", "fib", ( "lambda", ("n",), ( "ifQz_else", ("operator..le", "n", 2), "n", ( "operator..add", ("fib", ("operator..sub", "n", 1)), ("fib", ("operator..sub", "n", 2)), ), ), ), )
If you’re using an auto formatter that isn’t aware of Hissp, you may have to turn it off.
# Right. # fmt: off ('define','fib', ('lambda',('n',), ('ifQz_else',('operator..le','n',2,), 'n', ('operator..add',('fib',('operator..sub','n',1,),), ('fib',('operator..sub','n',2,),),),),),) # fmt: on
Note also that tuple commas are used as terminators,
even on the same line.
This is to prevent the common error of forgetting the required trailing comma for a single.
If your syntax highlighter can distinguish
(x,), you may be OK without it.
But this had better be the case for the whole team.
A new line’s indentation level determines which tuple it starts in. Go past the parent’s opening bracket, not the sibling’s.
(a (b c)) x ;(a (b c)) is sibling (a (b c) x) ;(a is parent, (b c) is sibling (a (b c x)) ;(b is parent, c is sibling
Even after deleting the trails, you can tell where the
(a (b c x (a (b c x (a (b c x
Indent with spaces only. Because indents have to be between parent and sibling brackets, lines in Lisp may have to start on any column, therefore, Lisp cannot be indented properly with tabs alone. There are arguments to be made for using tab indents in other languages, but they mostly don’t apply to Lisp. You have to use spaces. It’s possible to reach any column using an invisible mix of tabs and spaces, but indentation can’t be called “unambiguous” if no-one can agree on the width of their tab stops! Tab indents are already considered bad practice in Python and in other Lisps, but to pre-empt this kind of problem, it’s not just a matter of style in Lissp—it’s a syntax error. If you run into these, check your editor’s configuration.
The rule is to pass the parent bracket. You might not pass the head atom in some alignment styles.
(foo (bar x) body) ;(foo is parent, (bar x) is special sibling (foo (bar x body)) ;(bar is parent, x is special sibling
We can still unambiguously reconstruct the trails from the indent.
(foo (bar y body (foo (bar y body
Note that a multiline string is still an atom.
(foo (bar "abc xyz")) (foo (bar) "abc xyz") (foo (bar #"\ abc xyz")) (foo (bar) #"\ abc xyz")
We can still unambiguously reconstruct the trails.
(foo (bar "abc xyz" (foo (bar "abc xyz" (foo (bar #"\ abc xyz" (foo (bar #"\ abc xyz"
" is not a bracket,
so we don’t delete it or ignore it.
The remaining rules are more a matter of that practical consistency. Exactly what rules implement that consistency matters much less than the consistency itself. This is not always black and white, but that doesn’t mean all shades of gray are the same. There may be better and worse approaches, while other differences are merely taste.
Lisp is one of the oldest programming languages in common use. It has splintered into many dialects (Lissp among them), with a common culture, but without perfect agreement in all details. Lissp’s recommended style is based on these, with some small modifications for its own unique features.
Separate top level forms from each other by a single blank like, unless they are very closely related.
- Top Level
Not nested inside another form. “Top” here means the top of the syntax tree, not the top of the file.
Try to avoid blank lines within forms. You may need them for separating groups whose elements span lines or to separate methods in long classes. This is a code smell indicating your form may be too complex. You can use comment lines to separate internal groups instead, but consider refactoring. Blank lines are OK in docstrings.
Keep the elements in a tuple aligned to start on the same column. Treat sibling groups equally: If you add a line break for one group, then put all of its sibling groups on their own line as well. Keep items within implied groups (like kwargs) together. Control words used as labels should be grouped with what they label. Your code should look like these examples, recursively applied to subforms:
'(data1 data2 data3) ;Treat all data items the same. '(data1 ;Line break for one, break for all. data2 ;Items start on the same column. data3) '( ;This is better for linewise version control. data1 ; Probably only worth it if there's a lot more than 3, data2 ; or it changes frequently. Use this style sparingly. data3 _#/) ;Trails NEVER get their own line. ; But you can hold it open with a discarded item. (function arg1 arg2 arg3) ;; The function name is separate from the arguments. (function arg1 ;Break for one, break for all. arg2 ;Args start on the same column. arg3) ;; The previous alignment is preferred, but this is OK if the line would be too long. (function arg1 ;Indented one space past the (, unlike data. arg2 arg3) ((lambda (a b c) (reticulate a) (frobnicate a b c)) arg1 ;The "not past the sibling" rule is absolute. arg2 ; Not even one space past the (lambda. arg3) ((lambda (a b c) (print c b a)) arg1 arg2 arg3) ;Break for all args or for none. ;; One extra space between pairs. (function arg1 arg2 : kw1 kwarg1 kw2 kwarg2 kw3 kwarg3) (function arg1 arg2 : kw1 kwarg1 kw2 kwarg2) ;Breaking groups, not args. (function arg1 arg2 : kw1 kwarg1 ;The : starts the line. kw2 kwarg2) ;Break for args, but pairs stay together. (function : kw1 kwarg1 ;The : starts the "line". Sort of. kw2 kwarg2) ;; The previous alignment is preferred, but this is OK if the line would be too long. (function arg1 arg2 : kw1 kwarg1 ;Break for everything, and extra space to separate pairs. kw2 kwarg2) (macro special1 special2 special3 ;Macros can have their own alignment rules. body1 ; Simpler macros may look the same as functions. body2 ; Special/body is common. Lambda is also like this. body3) (macro special1 body1) (macro special1 special2 special3 body1 body2 body3) ;; Without any positional-only parameters, there's no need for :/ at all, so it groups left. (lambda (pos1 :/ param1 param2 ;; Without any pairs, there's no need for : at all, so it groups right. : default value1 default2 value2) body) ;; Same structure as above, but written with only pairs. (lambda (: pos1 :? :/ :? param1 :? param2 :? default value1 default2 value2) body) ;; Parameter groups are separated by lines. Pairs are separated by extra space. (lambda (a b :/ ;positional-only group c d ;normal group : e 1 f 2 ;colon group :* args h 4 i :? j 1 ;star group :** kwargs) ;kwargs body)
Readerless style is similar:
('function','arg1','arg2', ':','kw1','kwarg1', 'kw2','kwarg2',)
Alignment styles can be bent a little in the interest of readability,
especially for macros, but even for calls,
as long as the two absolute rules are respected.
For example, the
enstr function from the basic
prelude builds a string from multiple arguments.
Omitting spaces between atoms and having a variable number per line is acceptable,
because the string’s structure is more important for readability than the tuple’s.
(enstr ;Preferred. "Weather in "location" for "date" will be "weather" with a "chance"% of rain.") (enstr "Weather in " ;OK. location " for " date " will be " weather " with a " chance "% of rain.")
Exactly where the implied groups are can depend on the function’s semantics, not just the fact that it’s a call.
(engarde (entuple FloatingPointError ZeroDivisionError) truediv 6 0) ;(truediv 6 0) is a deferred call, so groups. (endict 1 2 3 4 5 6) ;Extra space between key-value pairs. (.update (globals) : ;OK. Easier for linewise version control. + operator..add - operator..sub * operator..mul / operator..truediv _#/) (.update (globals) ;Preferred. Standard style. : + operator..add - operator..sub * operator..mul / operator..truediv)
Multiline strings can mess with alignment styles.
Strings are atoms, so this won’t affect Parinfer,
but it can impact legibility.
For short strings in simple forms,
don’t worry too much, but consider using
For deeply nested multiline strings, use a dedent string, which can be safely indented:
#> (print (.upper 'textwrap..dedent#.##"\ #.. These lines #.. Don't interrupt #.. the flow.")) >>> print( ... "These lines\nDon't interrupt\nthe flow.".upper()) THESE LINES DON'T INTERRUPT THE FLOW.
Don’t forget the quote
Long multiline strings should be declared at the top level and referenced by name.
(define MESSAGE #"\ These lines don't interrupt the flow either. But, a really long string would be longer than this one. ") (deftype MessagePrinter () __doc__ "It is safe to align docstrings. " display (lambda (self) (print MESSAGE)))
Indent any multiline docstring to match its opening quote, including the closing quote. Put the closing quote for any multiline docstring on its own line. (Pydoc automatically strips indents.)
Reader macros should not be separated from each other or from their primary argument with whitespace.
' builtins..repr# .# (lambda :) ;Bad. 'builtins..repr#.#(lambda :) ;Preferred.
However, if a primary argument spans multiple lines, it’s acceptable to separate with a newline, but be careful not to accidentally put a comment in between, unless you explicitly discard it.
_# ; Bad. Comments are valid reader macro arguments! ((lambda abc ;This wasn't discarded! (frobnicate a b c)) arg) _# ;; Bad. This comment would have been discarded anyway. ((lambda abc ;But this wasn't discarded! (frobnicate a b c)) arg) _#_# ;; OK. This actually works. ((lambda abc ;This was discarded too. (frobnicate a b c)) arg) ;; OK. Put the tag after the comment on its own line. _# ((lambda abc (frobnicate a b c)) arg) _#((lambda abc (frobnicate a b c)) ;Bad. Wrong indentation! arg) _#((lambda abc ;Preferred. No separation, good indents. (frobnicate a b c)) arg) ;; OK. Composed macros can group. Primary spanned multiple lines. `', ((lambda abc (frobnicate a b c)) arg) `',((lambda abc ;Preferred. No separation. (frobnicate a b c)) arg)
Extras may always be separated, but only imply groups of extras with whitespace if they are semantically grouped.
builtins..int#!6 .#"21" ;Preferred. Spacing not required. builtins..int# !6 "21" ;OK. Extras may always be separated. 'foo#!(spam)!(eggs)bar ;Preferred. Spacing not required. 'foo# !(spam) !(eggs) bar ;OK. Extras may always be separated. 'foo# !(spam)!(eggs) bar ;Bad if grouping not meaningful. 'foo#!(spam) !(eggs) bar ;Same.
Align extras spanning lines like tuple contents.
;; Extras aligned with the first extra. <<#!;C:\bin !;C:\Users\ME\Documents !;C:\Users\ME\Pictures ";" ;Primary isn't an extra. Aligned with tag. ;; Extras aligned with the first extra. (exec <<# !;for i in 'abc': !; for j in 'xyz': !; print(i+j, end=" ") !;print('.') !; #"\n") ;; Indent recursively. foo#!;spam !bar#!;sausage !;bacon :tomato !;eggs :beans ;; Don't dangle brackets! (print <<#;Hello, World! _#/)
If you’re writing an API that’s exposed to the Python side, avoid unpythonic identifiers (including package and module names) in the public interface. Use the naming conventions from PEP 8.
CapWords for class names.
snake_case for functions,
and that or single letters like
I) for locals,
UPPER_CASE for “constants”.
Name the first method argument
and the first classmethod argument
Python does not enforce this,
but it’s a very strong convention.
For internal Lissp code,
Python conventions are fine,
but the munger opens up more characters.
*FOO-BAR* is a perfectly valid Lissp identifier,
but it munges to
which is awkward to use from the Python side.
Even in private areas, let the munger do the munging for you. Avoid writing anything in the Quotez style yourself. (This can confuse the demunger and risks collision with compiler-generated names like gensyms.)
Docstrings use reStructuredText markup, like Python. Any docstring for something with a munged name should start with the demunged name in doubled backticks (this includes anything with a hyphen), followed by the pronunciation in single quotes, if it’s not obvious from the identifier:
"``&&`` 'and'. Like Python's ``and`` operator, but for any number of arguments."
Method Syntax vs Attribute Calls¶
Often, code like
(.foo bar spam eggs)
could also be written like
(bar.foo spam eggs).
In some cases, the choice is clear,
because they compile differently,
but in others, these would compile exactly the same way.
Which is preferred then depends on whether
bar is a namespace or an argument.
For a namespace, prefer
Internal use of
self in methods and
cls in classmethods,
is also more namespace than argument.
For an argument, i.e. other method calls, prefer
(_macro_.define greeting "hi") ;Compiler Macro (.define _macro_ 'greeting '"hi") ;Run-time expansion. ;;; Arguments (.upper "hi") ;Preferred. ("hi".upper) ;SyntaxError (.upper greeting) ;Preferred. (greeting.upper) ;Bad. ;;; Namespaces (tkinter..Tk) ;Preferred. (.Tk tkinter.) ;Bad. ;;; Kind of Both (self.foo spam eggs) ;Preferred. (.foo self spam eggs) ;OK. (cls.foo spam eggs) ;Preferred. (.foo cls spam eggs) ;OK. ;; self as namespace, self.accumulator as argument (.append self.accumulator x)
The End of the Line¶
Ending brackets should also end the line.
That’s what lets us indent and see the tree structure clearly.
It’s OK to have single
)’s inside the line,
but don’t overdo it.
(lambda (x) (print "Hi" x) (print "Bye" x)) ;OK. (lambda (x) ;Preferred. (print "Hi" x) (print "Bye" x))
Don’t put a train of
)’s inside the line,
because then we’d have to count brackets!
If the train is trailing at the end of the line, then the tree structure is clear from the indents.
(print (/ (sum xs) (len xs)) "on average.") ;Bad. Internal ))'s. (print (/ (sum xs) (len xs)) ;OK. "on average.") (print (/ (sum xs) ;Preferred. (len xs)) "on average.")
Implied groups should be kept together.
Closing brackets inside a pair can happen in
(lambda (x) ;Preferred. (cond (lt x 0) (print "negative") (eq x 0) (print "zero") (gt x 0) (print "positive") :else (print "not a number")))
A train of
)’s must not appear inside of a line,
even in an implied group.
(define compare ;Bad. Internal ))'s. (lambda (xs ys) (cond (eq (len xs) (len ys)) (print "0") (lt (len xs) (len ys)) (print "<") (gt (len xs) (len ys)) (print ">")))) (define compare ;Bad. Pairs not grouped. (lambda (xs ys) (cond (eq (len xs) (len ys)) (print "0") (lt (len xs) (len ys)) (print "<") (gt (len xs) (len ys)) (print ">")))) (define compare ;OK, but the blank lines smell. (lambda (xs ys) (cond (eq (len xs) (len ys)) (print "0") (lt (len xs) (len ys)) (print "<") (gt (len xs) (len ys)) (print ">")))) (define compare ;Preferred. Keep cond simple. (lambda (xs ys) (let (lxs (len xs) lys (len ys)) (cond (eq lxs lys) (print "0") (lt lxs lys) (print "<") (gt lxs lys) (print ">"))))))