# leah blogs

## 27jan2024 · Definitions with shared hidden variables in Gerbil Scheme

It is well known that a Scheme function definition is merely defining a lambda function to a variable:

``````(define (hypot a b)
(sqrt (+ (* a a) (* b b))))
``````

This function could be written just as well:

``````(define hypot
(lambda (a b)
(sqrt (+ (* a a) (* b b)))))
``````

Occasionally, we need this explicit style when a function needs to keep around some internal state:

``````(define generate-id
(let ((i 0))
(lambda ()
(set! i (+ i 1))
i)))

(generate-id) ;=> 1
(generate-id) ;=> 2
(generate-id) ;=> 3
``````

A problem arises when we need multiple functions to share the state, let’s say we also want a `reset-id` function:

``````(define i 0)

(define (generate-id)
(set! i (+ i 1))
i)

(define (reset-id)
(set! i 0))

(generate-id) ;=> 1
(generate-id) ;=> 2
(generate-id) ;=> 3
(reset-id)
(generate-id) ;=> 1
(generate-id) ;=> 2
``````

Here, I had to make `i` a global variable to allow two toplevel functions to access it. This is ugly, of course. When you did deeper into the scheme specs, you may find `define-values`, which lets us write:

``````(define-values (generate-id reset-id)
(let ((i 0))
(values
(lambda ()
(set! i (+ i 1))
i)
(lambda ()
(set! i 0)))))
``````

This hides `i` successfully from the outer world, but at what cost. The programming language Standard ML has a nice feature to write this idiomatically, namely `local`:

``````local
val i = ref 0
in
fun generate_id() = (i := !i + 1; !i)
fun reset_id() = i := 0
end
``````

Here, the binding of `i` is not visible to the outer world as well. It would be nice to have this in Scheme too, I thought. Racket provides it, but the implementation is quite hairy. Since I mostly use Gerbil Scheme these days, I thought perhaps I can reuse the module system. In Gerbil, we could also write:

``````(module ids
(export generate-id reset-id)
(def i 0)
(def (generate-id)
(set! i (+ i 1))
i)
(def (reset-id)
(set! i 0)))
``````

It is used like this:

``````(import ids)
(generate-id) ;=> 1
(generate-id) ;=> 2
(generate-id) ;=> 3
(reset-id)
(generate-id) ;=> 1
(generate-id) ;=> 2
``````

So, let’s use the module system of Gerbil (which supports nested modules) to implement `local`. As a first step, we need to write a macro to define a new module and immediately import it. Since all Gerbil modules need to have a name, we’ll make up new names using `gensym`:

``````(import :std/sugar)
(defrule (local-module body ...)
(with-id ((mod (gensym 'mod)))
(begin
(module mod
body ...)
(import mod))))
``````

Here, we use the `with-id` macro to transform `mod` into a freshly generated symbol. `defrule` is just a shortcut for regular Scheme `syntax-rules`, which guarantees a hygienic macro. We can use the identifier `mod` inside the body without problems:

``````> (local-module (export mod) (def (mod) 42))
> (mod)
42
> mod
#<procedure #27 mod378#mod>
``````

The internal representation of the function shows us it was defined in a generated module. Re-running `local-module` verifies that fresh modules are generated.

Now to the second step. We want a macro that takes local bindings and a body and only exports the definitions of the body. Using the Gerbil export modifier `except-out`, we can export everything but the local bindings; this is good enough for us.

Thanks to the power of `syntax-rules`, this is now straight forward to state:

``````(defrule (local ((var val) ...) body ...)
(local-module
(export (except-out #t var ...))
(def var val) ...
body ...))
``````

We rewrite all let-style binding pairs into definitions as well, but we make sure not to export their names.

Now, we can write our running example like this:

``````(local ((i 0))
(def (generate-id)
(set! i (+ i 1))
i)
(def (reset-id)
(set! i 0)))

(generate-id) ;=> 1
(generate-id) ;=> 2
(generate-id) ;=> 3
(reset-id)
(generate-id) ;=> 1
(generate-id) ;=> 2
``````

But perhaps you prefer the slightly less magical way of using modules directly (which is also what many recommend to do in Standard ML). Still, creating modules by macros for fine-grained scoping is a good trick to have up your sleeve.

[Addendum 2024-01-28:] Drew Crampsie showed me another trick with nested modules that will help improve the `local` macro: when you `import` a module into another, the bindings are not re-exported by `(export #t)` (which means “export all defined identifiers”). (You can use `(export (import: module))` if you really wanted this.) This means we don’t need the `except-out` trick, but we can just use two nested modules instead:

``````(defrule (local ((var val) ...) body ...)
(local-module
(local-module
(export #t)
(def var val) ...)
(export #t)
body ...))
``````

The inner module contains all the local bindings, the outer one the visible bindings made in the body. Now, definitions in the body can also shadow local bindings (not that this is very useful):

``````(local ((var 5))
(def (get)
var)
(def var 6))

(get) ;=> 6
``````