Scheme surprises for a clojurist
As many I discovered lisp thinkering with my emacs configuration. A bit later I got a book about common lisp and hobbyed some stuff together, then came clojure and I was hooked. But Scheme kept tickling me, it's small(ish), has many implementations and uses the same namespace for functions and variables like clojure (I really dislike that about common and emacs lisp). Then I stumbled into the gemini space and figured it's time for a new hobby.
When I started using clojure I missed "car", "cdr" and it's siblings, using scheme I miss namespaces (I still don't understand scheme modules well enough) but all that seems like tomatoes to me. The following differences are more interesting.
#t, #f, '(), null
There's no "nil" but there is "null" but "null" is not falsy (false in a conditional context). Actually "null" can also be written as "'()" both of which can be used as a terminator value for a cons cell to make a list. But don't try "car" or "cdr" on "null" because it isn't a "pair". About that, scheme is strict about those things probably because there's no "nil", things either fail or return #f.
void, values
In scheme procedures can return more than just a values but also nothing or multiple values. That's not "null" or some "cons" of values but really "void" or multiple values which can be collected with "let-values". This is all fine, I guess, but I am still to figure out how to capture the result from a procedure without knowing the result arity. The answer is probably: you should not want to do that.
(let-values (..)), (match), (let-match (..))
Where's destructuring bind? As a clojurist I really miss it! I think the "void" and "values" stuff killed destructuring in scheme because it seems impossible to predict how many values a procedure will return. Yes, there's "match" (and it's relative "let-match") and it's very cool but I think it's a power tool and therefore probably not very efficient for simply binding values to names and, to be honest, not very pretty.
(cond (.. => ..) (else ..))
The "cond" statement is quite sugary in scheme. The "=>" notation takes a truthy result from the predicate part and feeds it into the supplied procedure. For instance "(cond ((assq 'foo bar) => cdr)" gets the pair labeled "foo" from the "bar" association list and applies "cdr" to it and thus getting the value part. Convenient because "assq" returns #f when the pair is not in "bar" which can not be fed to "cdr". But let's not speak of the naming of "assq" and it's close relatives to avoid upsetting the clojurist.
The "else" part I don't understand. What's wrong with "#t" or "'else" both are pretty, truthy and readable.
(let foo (..) .. (foo ..))
A labelled "let", looks like "(loop [..] .. (recur ..))" but it's not. There's no explicit tail call syntax like "recur" in scheme so the "let label" thing is more like regular loop/recursion which gets optimized if it's a tail call. The first time I saw "recur" in clojure I was appalled but grown to love it, no magic.
(define (foo ..) ..), (let (..) (define ..))
Scheme's "define" baffles me, it's nothing like clojure's "def". The latter just defines some named value the first shape shifts depending on content and context; content when the first argument is wrapped in parentheses (it inserts a lambda symbol to define a procedure), context when it's wrapped by a "let" is wraps the following statements in a new "let" introducing the newly defined binding.
I am sure "define" has much more tricks up it's sleeve..
--
📅 2021-04-25
🏷 scheme, clojure, racket, programming
📧 hello@rwv.io
CC BY-NC-SA 4.0