Here are a few examples of sweet-expressions (which include modern-expressions and curly infix), as defined in our [Solution] to the [Problem]. The first few use Scheme; they are followed by a Common Lisp example. This section closes with pointers to other materials.
First, here are the examples we often use to show off sweet-expressions, using Scheme:
define fibfast(n) ; Typical function notation if {n < 2} ; Indentation, infix {...} n ; Single expr = no new list fibup(n 2 1 0) ; Simple function calls
define fibup(maxnum count n-1 n-2) if {maxnum = count} {n-1 + n-2} fibup maxnum {count + 1} {n-1 + n-2} n-1
define factorial(n) if {n <= 1} 1 {n * factorial{n - 1}} ; f{...} => f({...})
Note that you can use traditional math notation for functions; fibfast(n)
maps to (fibfast n)
. Infix processing is marked with { ... }; {n <= 2}
maps to (<= n 2)
. Indentation is significant, unless disabled by ( ... ), [ ... ], or { ... }. This example uses variable names with embedded "-" characters; that's not a problem, because the infix operators must be delimited (e.g., by whitespace and are only used as infix operators when { ... } requests them.
It's actually quite common to have a function call pass one parameter, where the parameter is calculated using infix notation. Thus, there's a rule to simplify this common case (the prefix {} rule). So factorial{n - 1} maps to factorial({n - 1}) which maps to (factorial (- n 1)).
Credit where credit is due: The Fibonacci number code is loosely based on an example by Hanson Char.
Here's another trivial Scheme program, a greatest common divisor function straight from Carl A. Gunter's "Semantics of Programming Languages" page 2:
(define (gcd x y) (if (= y 0) x (gcd y (rem x y))))
A sweet-expression reader could accept the above, but this can be rewritten more clearly using sweet-expressions as:
define gcd(x y) if {y = 0} x gcd y rem(x y)
Everyone knows macros need s-expressions. Or do they? We think not! Here's an example implementation of Scheme's let*
:
Sweet-expression | (Awkward) S-expression |
---|---|
define-syntax let* syntax-rules () \\ ! let* () ! body ! ... ! \\ begin ! ! body ! ! ... \\ ! let* ! \\ ! assignment ! assignments ! ... ! body ! ... ! \\ let (assignment) ! ! let* ! ! \\ ! ! assignments ! ! ... ! ! body ! ! ... |
(define-syntax let* (syntax-rules () ( (let* () body ...) (begin body ...)) ( (let* ( assignment assignments ...) body ...) (let (assignment) (let* ( assignments ...) body ...))))) |
Note that \\ as the first symbol after indentation represents "nothing". This is helpful for creating lists of lists, by putting nothing after it. If there are symbols after an initial \\, the \\ has no semantic meaning (nothing+something = something), but in this case it visually sets off the following symbols as being special in some way.
The "!" is an indent character, which is unusual, but notice that it can be used to visually create vertical markers to visually indicate an important group; in the above example, the pattern to match for syntax-rules has one !
before it, while the resulting expansion has two !
- thus giving a good visual indicator and guide. The "!" can also be used when using a system that erases horizontal spaces and tabs (e.g., some email systems), resolving a common complaint about indentation-sensitive syntax.
Here's a larger example in Scheme, reformatted from the example in the Scheme Fixnum book:
define solve-kalotan-puzzle lambda () let \\ parent1 amb('m 'f) parent2 amb('m 'f) kibi amb('m 'f) kibi-self-desc amb('m 'f) kibi-lied? amb(#t #f) assert distinct? list(parent1 parent2) assert if eqv?(kibi 'm) not kibi-lied? assert if kibi-lied? xor {eqv?(kibi-self-desc 'm) and eqv?(kibi 'f)} {eqv?(kibi-self-desc 'f) and eqv?(kibi 'm)} assert if not(kibi-lied?) xor {eqv?(kibi-self-desc 'm) and eqv?(kibi 'm)} {eqv?(kibi-self-desc 'f) and eqv?(kibi 'f)} assert if eqv?(parent1 'm) and eqv?(kibi-self-desc 'm) xor {eqv?(kibi 'f) and eqv?(kibi-lied? #f)} {eqv?(kibi 'm) and eqv?(kibi-lied? #t)} assert if eqv?(parent1 'f) {eqv?(kibi 'f) and eqv?(kibi-lied? #t)} list parent1 parent2 kibi solve-kalotan-puzzle()
Here's a Scheme example from Wikipedia - specifically the article "Scheme (programming language)". This example adds an arbitrary list of numbers, and if a non-numeric value is found in the list the procedure is aborted immediately and the constant value #f (false) is returned. This is achieved by capturing the current continuation in the variable exit and using it as an "escape procedure".
First, here's the original Scheme:
(define (add-if-all-numbers lst) (call/cc (lambda (exit) (let loop ((lst lst) (sum 0)) (if (null? lst) sum (if (not (number? (car lst))) (exit #f) (+ (car lst) (loop (cdr lst)))))))))
Here's the same thing using sweet-expressions. You could indent exactly as above, but if you have a marching series of increasing indentation, the "$" can help as shown here:
define add-if-all-numbers(lst) call/cc $ lambda (exit) let loop (lst(lst) sum(0)) if null?(lst) sum if not(number?(car(lst))) exit #f {car(lst) + loop(cdr(lst))}
The git repository https://sourceforge.net/p/readable/code/ has additional examples. The src/sweeten.sscm has the first significant program ever written using sweet-expressions, and the "examples/" directory has additional small examples.
The notation is not limited to Scheme. Here's some Decision tree learning code in Common Lisp that accompanies the textbook "Machine Learning," Tom M. Mitchell, McGraw Hill, 1997; a longer fragment is in [Analysis]. It's been rewritten using sweet-expressions:
defun print.tree (tree &optional (depth 0)) tab depth format t "~A~%" first(tree) loop for subtree in cdr(tree) do tab {depth + 1} format t "= ~A" first(subtree) if atom(second(subtree)) format t " => ~A~%" second(subtree) progn terpri() print.tree second(subtree) {depth + 5}
defun tab (n) loop for i from 1 to n do format(t " ")
defun classify (instance tree) let $ val branch if atom(tree) return-from(classify tree) setq val get.value(first(tree) instance) setq branch second(assoc(val cdr(tree))) classify instance branch
The code distribution comes with "sweeten.sscm"; this is a program in Scheme that translates traditional S-expressions into sweet-expressions, and is itself written using sweet-expressions.
The code distribution also comes with "math.slisp"; this is a program in Common Lisp that simplifies math expressions, returning exact results. Its real purpose is to demonstrate sweet-expressions in Common Lisp.
"Letterfall" by Alan Manuel Gloria is a game written using sweet-expressions; you can see it here: https://github.com/AmkG/letterfall
The [Analysis] page shows many more examples, in many different Lisp variants.
The page [Hunchentoot-make-docstrings] shows a longer Common Lisp example.
See [Install-howto] for how to install our tools and code that implements this.
See [Common-lisp-tutorial] and [Scheme-tutorial] for our Common Lisp and Scheme tutorials.