Reputation: 1786
In Common Lisp, if I want two functions to share state, I would perform a let over lambda as follows:
(let ((state 1))
(defun inc-state ()
(incf state))
(defun print-state ()
(format t "~a~%" state))
These functions are not local to the let
- they are global functions that maintain a reference to a shared state variable, which itself is not visible from outside. For example, I could do the following elsewhere in my code:
(print-state) => 1
(inc-state) => 2
(print-state) => 2
In Scheme, however, such a construct declares local functions, that are not visible from outside:
(let ((state 1))
(define (print-state)
(print state))
(print-state)) => 1
(print-state) => error, no such variable print-state
The only way I can think to achieve this kind of functionality (aside from using un-exported globals inside a module), would be something like this:
(define print-state #f)
(define inc-state #f)
(let ((state 1))
(set! print-state (lambda () (print state)))
(set! inc-state (lambda () (inc! state))))
Is there a way in Scheme to write the let-over-lambda form without resorting to such ugly workarounds? Or would I need to write a macro to wrap this ugliness? (Btw I know about letrec
, and that's not a solution to this problem.)
Incidentally, I'm using Chicken Scheme, but my question should be relevant to all Schemes.
Upvotes: 5
Views: 1453
Reputation: 48735
Unfortunately top level binding can only be made top level and define
inside a procedure is actually just syntactic sugar for a letrec
. In Chicken Scheme you have a form called define-values
where you can do this:
(define-values (print-state inc-state)
(let ((state 1))
(values (lambda () (print state))
(lambda () (inc! state)))))
Note that define-values
is not part of any standard even though it seems it's common form to have. It would be easy to make a macro that uses the approach you used to implement it though. For an alternative solution you can return a dispatcher that you call to get access to procedures:
(define dispatcher
(let ((state 1))
(lambda (msg)
(case msg
((print) (lambda () (print state)))
((inc!) (lambda () (inc! state)))))))
(define print-state (dispatcher 'print))
(define inc-state (dispatcher 'inc!))
In reality you don't need to make globals since you can just call the return directly:
((dispatcher 'inc!))
((dispatcher 'inc!))
((dispatcher 'print)) ; ==> prints 3
Upvotes: 5
Reputation: 18917
You could do something like this:
(define-values (inc-state print-state)
(let ((state 1))
(values
(lambda () ; inc-state
(set! state (+ 1 state)))
(lambda () ; print-state
(display state)
(newline)))))
> (print-state)
1
> (inc-state)
> (print-state)
2
> state
. . state: undefined; cannot reference an identifier before its definition
>
(output is from Racket but I tested it in Chicken Scheme as well)
Upvotes: 4