Lambda bodies in Scheme (by )

So, if you look at a recent Scheme standard such as [R7RS](https://r7rs.org/), you'll see that the body of a `lambda` expression is defined as `* * `; 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*`](https://srfi.schemers.org/srfi-2/srfi-2.html) 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`](https://api.call-cc.org/5/doc/miscmacros/let%2Fcc), 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](https://love.warhead.org.uk/~alaric/paste/block.scm.html), or download [the raw source code](http://love.warhead.org.uk/~alaric/paste/block.scm) 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?)

No Comments

No comments yet.

RSS feed for comments on this post.

Leave a comment

WordPress Themes

Creative Commons Attribution-NonCommercial-ShareAlike 2.0 UK: England & Wales
Creative Commons Attribution-NonCommercial-ShareAlike 2.0 UK: England & Wales