Lambda bodies in Scheme (by alaric)
So, if you look at a recent Scheme standard such as R7RS, you'll see that the body of a lambda expression is defined as <definition>* <expression>* <tail expression>; zero or more internal definitions, zero or more expressions evaluated purely for their side-effects and the results discarded, and a tail expression whose evaluation result is the "return value" of the resulting procedure.
I used to find myself using the internal definitions as a kind of let*, writing procedures like so:
(lambda (foo)
(define a ...some expression involving foo...)
(define b ...some expression involving a and/or foo...)
...some final expression involving all three...)
But the nested defines looked wrong to me, and if I was to follow the specification exactly, I couldn't intersperse side-effecting expressions such as logging or assertions amongst them. And handling exceptional cases with if involved having to create nested blocks with begin.
For many cases, and-let* was my salvation; it works like let*, creating a series of definitions that are inside the lexical scope of all previous definitions, but also aborting the chain if any definition expression returns #f. It also lets you have expressions in the chain that are just there as guard conditions; if they return #f then the chain is aborted there and #f returned, but otherwise the result isn't bound to anything. I would sometimes embed debug logging and asserts as side-effects within expressions that returned #t to avoid aborting the chain, but that was ugly:
(and-let* ((a ...some expression...)
(_1 (begin (printf "DEBUG\n") #t))
(_2 (begin (assert (odd? a)) #t)))
...)
And sometimes #f values are meaningful to me and shouldn't abort the whole thing. So I often end up writing code like this:
(let* ((a ...)
(b ...))
(printf "DEBUG\n")
(assert ...)
(if ...
(let* ((c ...)
(d ...))
...)
...))
And the indentation slowly creeps across the page...
However, I think I have a much neater solution!
First, I'll demonstrate what it looks like, then get into how it works.
(block
(/let a 1)
(begin (printf "DEBUG LOGGING\n"))
(/assert (odd? a))
(/let b 3)
;; Final value expression
(+ a b))
block is my new macro, although I'd like to redefine lambda to wrap its body in an implicit block rather than invoking it directly like this. As is hopefully obvious, (/let A B) defines A to the value of B thereafter in the block, begin can be used to wrap some code to run purely for side-effects, /assert just checks an assertion (shorthand for (begin (assert ...)) really), and the final expression is returned from the block.
For convenience, /let is defined in terms of Chicken's match-let, so it's destructuring - you can write:
(block
(/let (a b) '(1 2))
(+ a b))
...and get 3. I've also defined a /flet that's shorthand for writing a procedure, much like you can with define. /flet actually uses a letrec under the hood so the procedure can recursively call itself:
(block
(/flet (odd? x)
(cond
((zero? x) #f)
((eq? 1 x) #t)
(else (odd? (- x 2)))))
(odd? 5))
My /let just defines one thing at a time, and if you have lots of definitions, you might miss the behaviour of traditional let. But the good news is, you can still use it, just leave the body empty:
(block
(let ((a 1)
(b 2)))
(+ a b))
You can also use if to early abort, creating an effect sort of like a cond:
(define (odd? x)
(block
(if (zero? x) #f)
(if (eq? 1 x) #t)
(odd? (- x 2))))
This may have given you a hint as to what's going on here - all the block macro does is to convert its body into a kind of macro-level continuation passing style:
(block (A A1 A2) (B B1 B2) C) => (A A1 A2 (B B1 B2 C))
So when we use things like let and if in there, and begin for that matter, we're just wrapping the rest of the body into the final part of the let/if/begin form. /let just rewrites into a match-let with the final argument as its body:
(/let VAR VAL REST) => (matched-let ((VAR VAL)) REST)
Where VAR and VAL come from the (/let ...) expression inside the block, and REST is provided by the block macro itself.
This avoids out-of-control indentation for any form that nests a "body" as its final part. For instance, handle-exceptions:
(block
(handle-exceptions exn (printf "An error happened!\n" #f))
...
#t)
Any exceptions after the handle-exceptions in the block will be handled as described. For convenience, I've also defined a /finally that contains one or more expressions that will be executed after the rest of the block, whether it's an exceptional or normal exit - a bit like Go's defer.
Sometimes you want to use some syntax that doesn't have the body at the end, so I've got a macro for that, which I called ->. It's like a sort of syntactic let/cc, bundling the final body argument into a thunk that it binds to a chosen name:
(block
(-> ok (if X (ok) #f))
...)
If the if succeeds it will "continue" and execute the rest of the block by calling ok, otherwise the whole expression returns #f. This is equivalent to:
(block
(if (not X) #f))
Which reminds me, once can use short-circuiting and and or in a block, although I think it looks a little confusing!
Conclusions
I'm not sure about the names. Ideally, I think I'd re-bind begin instead of block, and also redefine lambda and things that wrap it to create block structures (like define) to have an implicit block as their body.
The /... convention is just for things I've redefined to have a final "body argument" so they fit the block pattern nicely; chosen purely because / is an easy key to press on my keyboard and the glyph isn't too jarring, no better reason than that.
I don't like the name ->, but have yet to think of a good, succinct, name for the operation!
Try it yourself...
You can view the syntax-highlighted source for Chicken 5, or download the raw source code and try it yourself. Please tell me what you think! I'd like to iterate the design a bit, tidy it up, then publish it as a Chicken egg (and maybe as an SRFI with a portable reference implementation, if there's interest?)
