Style

Here are some style guidelines that may help you create easy-to-read code. We focus on sweet-expressions here.

General Guidance

Mentally, this is pretty straightforward - on each line, write an expression; everything after the first term on the line, or all child lines, are parameters of the first term. You can use grouping operators ( ), [ ], and { } to put subexpressions on the same line, if you want. Use -( ... ) to negate something.

Whenever you have an infix expression, just surround it with {...}. You can use the form f(...) to call a function; if it has zero parameters, express it as f(), and if it has more than one parameter, separate the parameters with spaces. The f(...) form is especially handy for creating short expressions as a parameter on a line; for long expressions, use indentation instead.

Let's go through some specifics.

Use infix notation

If the function is typically written as infix (including "+", "*", "or", and "\<"), use {...} to write it as an infix value. Generally these operators will be "and", "or", or an operator that only uses punctuation. If you're calling a function with only one parameter, and that parameter is calculated with an infix operation, use the f{...} shorthand. You can see all of these suggestions in this example:

define factorial(n)
  if {n <= 1}
    1
    {n * factorial{n - 1}} ; f{...} => f({...})

However, you may want to keep using prefix form if indentation still matters and one or more of the parameters is exceedingly complex (e.g., it's nested very deeply or includes program structuring forms like "cond" and "define"). This situation can often occur with "and" and "or" if you're using a functional programming style.

Use indentation for major program/data structure

In general, use indentation to make it easy to see the larger-scale structure of a program or data. Typically major structural atoms should start a new line, including defining a new term (e.g., "define" and "let"), conditionals (e.g., "if" and "cond"), and loops (e.g., "loop"). For example, the traditional "cond" function of Common Lisp looks like this:

(cond ((test-1) (consequent-1-1) (consequent-1-2) ...)
      ((test-2) (consequent-2-1))
      ((test-3) (consequent-3-1) ...)
      ... )

This is easily implemented as:

cond
 test-1() consequent-1-1() consequent-1-2() ...
 test-2() consequent2-1()
 test-3() consequent-3-1() ...
 ...

Indent as necessary to make it easy to understand. For example, if the consequents for test-1 (above) are long, indent them too:

cond
 test-1()
   consequent-1-1()
   consequent-1-2()
    ...
 test-2()
 test-3() consequent-3-1() ...
 ...

Use a consistent amount of indenting for each level. I tend to use 2 spaces for indentation; indentation nesting is more common in sweet-expressions, so 8-character indentations are often too much.

Use function call notation for parameters if they fit in a line

When calling a function, if the parameters will fit easily on a line if you use function notation like f(x y(z)), then put them all on a line. When you're calling a function with no parameters, use function-calling format with "()" at the end, e.g., "f()". Here's a good example (this example is for Scheme):

cond
  list?(x) extractify(car(x))
  atom?(x) x
  #t       #f

In general, indentation is used for the major "structural" elements of a program, and function calls get used once you're "near the leaf" of structure (where you won't go beyond the end of the line).

If you are providing a list of data (and not performing a function/method call), then use the traditional list notation such as "(a b c)". This is exactly equivalent to "a(b c)", but expressing it as a list will give the human reader a hint that this data is not considered a potential program. If it's used as both data and as program, then consider it a program, and use function call notation.

Occasionally you may what to use function call notation even if the parameters won't fit in a line, because once inside a function call indentation processing isn't relevant - and that can be useful.

Avoid unnecessary parentheses

Where it's understandable, don't include unnecessary parentheses. In particular, when indentation processing is active, the name of the function is right after the indent, and there are no child lines, simply state the function followed by space-separated parameters. So don't do this (assuming it has no child lines):

  myfunction(1 a(2) 3)

Instead, do this:

  myfunction 1 a(2) 3

The latter is easier for humans to read, because the human reader has one less pair of parentheses to track. If you move it back to being later in the line (e.g., after a condition in "cond"), be sure to restore the function call parentheses.

This is one of the more debatable style recommendations.

Beware of almost-function-call errors

Remember that "f(x)" is completely different from "f (x)"; the former means the 2-element list "(f x)", while the latter means an atom followed by a single-element list, "f (x)". In sweet-expressions (and traditional Lisp notation), whitespace is a separator, so for sweet-expression prefixed function calls, be sure to not put a space between the function name and the open paren.

You can put whitespace after the "(", or before the ")", and it'll make no difference, so "f(x)" and "f( x )" are equivalent. However, as a style I suggest not inserting this unnecessary space in most cases. That way, any whitespace between an atom and open paren is "unusual" and will catch the eye, making it easier to read.

Width

You should probably stick to an 80-character width for program text; sweet-expressions don't require them, but it can be annoying to handle overwide source code. If you occasionally go over, it's not the end of the world, though you might want to reformat later.

Indents

Indenting is more common in sweet-expressions, so 2-space indents are often useful. That's especially important if you're trying to stay within 80 columns. Of course, the indent distance is up to you.

You can use "!" as an indent character, just like space or tab. This is helpful if you want to include sweet-expressions in a medium that hides indentation. You can also use this to highlight a particular vertical group. We suggest always following "!" with space or tab. However, beware if you start a paired expression and let it continue to the next line; the "!" is not an indent character inside parentheses, braces, or brackets.

Long cond clauses in particular benefit well from "!":

cond
  {x < 0}
  ! display("negative")
  ! pass-to-other('negative)
  ! let
  !   \\
  !   ! result1 pass-to-this('negative)
  !   ! result2 pass-to-that(x)
  !   display(result1)
  !   display(result2)
  {x > 0}
  ! display("positive")
  ! pass-to-other('positive)
  ! let
  !   \\
  !   ! result1 pass-to-this('positive)
  !   ! result2 pass-to-that(x)
  !   display(result1)
  !   display(result2)
  #t
  ! error("is 0")

Notes about cond

The "cond" form is widely-used, and works beautifully. Here's an example:

define f(x)
  cond
    ; display negative, zero, or positive,
    ; and return -1, 0, or 1 respectively.
    {x < 0} display("negative") -1
    {x = 0} display("zero")      0
    #t      display("positive") +1

If the condition gets long, or you have many operations, just make the operations child lines of the condition. For example:

cond
  {x < 0}
    frobnitz param1 param2 param3 param4 param5 param6 param7 param8

If you put both the condition and following expression(s) on the same line, make sure that you use the parenthetical versions of the actions. Otherwise, each parameter will be interpreted as different actions. Thus:

cond  ; THIS IS ALMOST CERTAINLY WRONG
  {x < 0} frobnitz param1 param2 param3 param4 param5

should probably be:

cond
  {x < 0} frobnitz(param1 param2 param3 param4 param5)

Use \\ for complex lists of lists - and notes on "let"

If you have a list-of-lists that's so trivial that traditional list notation will easily fit on one line, just use (...) to surround the list items. This is traditional list notation, and that's just fine - it's short and it's clear. If you're doing this define variables (e.g., for "let"), consider using the form variable-name(initializer). For example, the traditional "let" form of:

(let ((var1 initializer1) (var2 initializer2))
  body1
  body2
  ...)

Can be rewritten using sweet-expressions as:

let (var1(initializer1) var2(initializer2))
  body1
  body2
  ...

If the list of variables or their initializer expressions get long, then the GROUP symbol \\ is what you need - it lets you use indentation to create lists of lists. For example, you can rewrite that list as:

let
  \\
    var1 initializer1
    var2 initializer2
  body1
  body2
  ...

Note that if the initializers are function calls, you need to use the form function(...), indent them again, or use $ between the variable name and the initializer expression.

Single-variable let

In some cases, you might find it more convenient to define a single-variable let (this is true whether or not you use sweet-expressions). Here's a straight s-expression form of this, using the define-macro form supported by many Scheme implementations including guile (it's a valid sweet-expression too, of course):

(define-macro let1
  ; Simple single-variable "let"; lets "variable" to "value", then computes.
  (lambda (variable value . computations)
    `(let ((,variable ,value)) ,@computations)))

By the way, here's the same macro, shown as a sweet-expression:

define-macro let1
  ; Simple single-variable "let"; lets "variable" to "value", then computes.
  lambda (variable value . computations)
    ` let ((,variable ,value)) ,@computations)

And if you prefer Scheme's syntax-rules:

define-syntax let1
  syntax-rules ()
    \\
    ! let1 var value
    !   body
    !   ...
    ! \\ let ((var value))
    ! !    body
    ! !    ...

Now we can do the same thing this way:

let1 x 2
  let1 y 3
    {x * y}

Another alternative introduced in Readable 0.4 is to take advantage of the new SUBLIST-at-the-start semantics:

let
  $ x 2
  let
    $ y 3
    {x * y}

One-armed if

In some Lisplike languages, a one-armed if exists called when:

when pred?(x)
  do-one-thing()
  do-another-thing()

But this is hardly universal; Scheme does not define it in standards up to R6RS.

An alternative would be to use the SUBLIST symbol together with a language's multi-expression sequencing form. For example on Scheme:

if pred?(x) $ begin
  do-one-thing()
  do-another-thing()

Related

Wiki: Home