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 call might be split over multiple lines. Arguments to the procedure—often blocks themselves—are marked by deeper indentation level:
Special forms with bodies are blocks too:
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:
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:
You can also see inline colon in some special forms. Procedure creation depends on colons, for example.
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
:
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:
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
Missed weird precedence rules and duplicated signs? Here you go!
Applying The Takeaways
- Blocks are mere lines.
- Blocks can be nested, with inner blocks indented deeper than the outer block.
- Non-blocks should be marked with the period
.
. - Blocks can be nested inline with the colon
:
. - Blocks can be wrapped in the extra block with the colon too. It's usually important for macros.
- Curly-infix notation should be available on Wisp systems too.
How about applying it to the ultimate syntactic monstrosity, case-lambda
?
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 assets/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?