I think that no solution which wraps a binding around define
can work at all portably or safely as the wrapped define
's will either construct local bindings (leading forms in the body) or be illegal (non-leading forms in the body), although I'd be happy for a Scheme-standards person to point out where I'm wrong.
Instead something like this slightly nasty hack ought to work I think.
(begin
(define inc undefined)
(define dec undefined)
(let ((x 3))
(set! inc (lambda (y)
(set! x (+ x y))
x))
(set! dec (lambda (y)
(set! x (- x y))
x))))
Here I've relied on a constant called undefined
which means 'not yet defined properly': Racket provides this in racket/undefined
but it can in general be anything: you could just, somewhere, say
(define undefined 'undefined)
for instance.
The trick is to define the things you want at top-level with placeholder values, and then assign to them inside the let
.
I'm sure a macro can be defined which expands to something like (this is why I have the whole thing inside a begin
): I haven't done so because it's fiddly and I use Racket so I can't easily write old-style Lisp macros in it.
Note that the obvious approach in a modern scheme is to use define-values
:
(define-values (x y) (let (...) (values ...)))
Does what you want. As mentioned in another answer you can implement define-values
as a macro if you only have multiple values. But if you don't have multiple values at all, then you can use something which defines things based on a list of results:
(define-list (x y) (let (...) (list ...)))
Here are two rough variants of that macro: the first uses Racket's native macros:
(require racket/undefined)
(define-syntax define-list
(syntax-rules ()
[(define-list () expr)
(let ((results expr))
(unless (zero? (length results))
(error "need an empty list"))
(void))]
[(define-list (name ...) expr)
(begin
(define name undefined)
...
(let ([results expr])
(unless (= (length results)
(length '(name ...)))
(error "wrong number of values"))
(set! name (begin0
(car results)
(set! results (cdr results))))
...))]))
while the second uses unhygenic macros in Racket:
(require compatibility/defmacro
racket/undefined)
(define-macro (define-list names expr)
`(begin
,@(let loop ([ntail names] [defs '()])
(if (null? ntail)
(reverse defs)
(loop (cdr ntail)
(cons `(define ,(car ntail) undefined) defs))))
(let ([results ,expr])
(unless (= (length results)
(length ',names))
(error "wrong number of values"))
,@(let loop ([ntail names] [i 0] [assignments '()])
(if (null? ntail)
(reverse assignments)
(loop (cdr ntail) (+ i 1)
(cons `(set! ,(car ntail) (list-ref results ,i))
assignments)))))))
Note that neither of these has been tested, and I'd need to spend a little time convincing myself that the second is hygenic enough.
But with these:
> (define-list (inc dec)
(let ([i 0])
(list
(lambda ()
(set! i (+ i 1))
i)
(lambda ()
(set! i (- i 1))
i))))
> inc
#<procedure>
> (inc)
1
> (dec)
0
> (dec)
-1
>