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.
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