Explaining Wisp Without Parentheses

By Artyom Bologov

Many people—including even some Lispers!—dislike Lisp's overabundance of parentheses—called P-WORD from now on. Looking at overly friendly tiger tails all day might be somewhat tiring. Many Lisp programmers rely on indentation as a marker of code structure/nesting instead. There's a niche for indentation-based syntax, cleaner and more readable than all these P-WORD.

Enter Wisp. SRFI-119, a Scheme Request for Implementation from 2015. It's super minimalist with only two special syntax markers and indentation as the only nesting indicator. It also supports the P-WORD syntax, so that one can always fall back to the comfort of regular Scheme.

One problem with Wisp, though—it's defined in terms of P-WORD syntax. It's not self-sufficient and only makes sense to the ones already into Lisp. Wisp is not suitable for Lisp propaganda aiming at those disliking the P-WORD. And that propaganda, according to Arne Babenhauserheide, the author of SRFI-119, is one of the goals for the syntax.

This post tries to explain Wisp as a self-sufficient language. No mention of the P-WORD. Let's see whether it's even possible.

A Bit of Cheating: Blocks

P-WORD is a marker of s-expressions in Lisps. So one needs an alternative word/concept for... everything Lisp has? I suggest "block", as in Python blocks. Blocks are numerous, almost everything in Wisp is blocks. Blocks usually occupy a line of their own. Procedure calls are blocks:

procedure arg1 arg2
Procedure call as a block

Procedure call might be split over multiple lines. Arguments to the procedure—often blocks themselves—are marked by deeper indentation level:

procedure arg
          another-procedure arg1 arg2
Procedure call as a multiline indented block

Special forms with bodies are blocks too:

begin
 procedure arg
 another-procedure arg1 arg2
Begin form as a nested multiline block

Wisp concerns itself with blocks and how they wrap each other. You'll see how both Wisp operators tie into this.

Period: No Blocks!

So I said that almost everything in Wisp is a block, didn't I? Almost. In case there's something that isn't a block, it needs to be delineated.

Say you want to return #t from your function. It's not a block, it calls no function. That's why it must be marked with the period—to be returned verbatim:

lambda :
  . #t
Returning a value instead of interpreting it as a block

Inline Colon as an Application Marker

Colon is the second operator in Wisp, and it's also concerned with blocks. But it has two different uses depending on where in the code it is found. One of the uses is inline. Colon between two things. When positioned like that, the colon means nested procedure applications/block. Making the right part up until newline an argument to the left part. Something like Haskell $ operator. Examples:

;; display a sum of 1 and 2
display : + 1 2
;; display a sum of 1 with a difference of 2 and 3
display : + 1 : - 2 3
Examples of colon as procedure application operator

You can also see inline colon in some special forms. Procedure creation depends on colons, for example.

define : a b c
  . #t
;; Equivalent
define a
  lambda : b c
    . #t
Defining/creating a new procedure using the inline colon

It definitely is not a procedure application operator in this context. But it's still used this way—as a block that define requires for procedure prototype. Colon is a mere syntactic marker, not a semantic one. You just need to memorize the use of the colon in define/lambda. Much like in any language with syntax.

Leading Colon: Wrapping Block

Another use of the colon is the leading colon case. It introduces a new block. A new layer of nesting for things. Even if the thing after it is a block—like procedure call—already. It's useful for Scheme special forms like let:

let
  :
    a 3
    b 4
;; Or the form I like more
let
  : a 3
    b 4
Use of the leading colon in let-bindings

Both of the cases above involve multi-line blocks. The indentation makes both lines a part of the colon-introduced block. But what if the leading colon doesn't wrap multiple lines? What if there are no lines indented below it? Then it just wraps whatever follows:

do
  ;; Using inline colon for procedure application too
  : i 0 : + 1 i
  : = 10 i
 display i
;; 0123456789
Use of the leading colon in do

The only use for the leading colon is macros, really. There are not a lot of macros in Scheme, so you can easily memorize the patterns. Like with lambda, define, let, and do.

Curly-infix Notation

We've got rid of P-WORD, but what about prefix math? It hurts. Any way to replace it with something more intuitive/familiar? Like SRFI-105, curly-infix expressions.

Wisp SRFI requires curly-infix expressions as part of the dialect. So one can freely do

display {1 + 2}
Use of curly-infix expression in Wisp

Missed weird precedence rules and duplicated signs? Here you go!

Applying The Takeaways

How about applying it to the ultimate syntactic monstrosity, case-lambda?

define my-plus
 case-lambda
  ;; Wrapping the thing into a block.
  : a
    ;; Returning the value verbatim
    . a
  : a b
    ;; Curly-infix use
    {a + b}
  : a b c
    ;; Inline colon use
    + a : + b c
  : a b . rest
    ;; A multiline procedure application
    apply + a b
          . rest
case-lambda in Wisp

I like how it looks! Despite inconsistencies and being absolutely useless, of course. An ultimate test and result of our newly acquired understanding of Wisp!

The whole reason for me exploring Wisp is the news of it being added to Guile 3.0.10! The manual section is quite frugal, though. So let this post be an introduction to Wisp syntax, with the added benefit of self-sufficiency and absence of paren... the P-WORD, sorry. Go try Wisp, you might end up liking it as much as I did!

P.S. I was irritated by Guix not shipping Guile 3.0.10 for so long. And using the SRFI-provided parser (either Python or Guile-specific one) wasn't cool. So I made my own... as an ed script. Find it at scripts/wisp.ed! It produces correct results in 99% of cases and works everywhere ed does. So... Can I call it the most portable Wisp implementation?

Leave feedback! (via email)