let-over-lambda in Scheme?
Asked Answered
D

2

5

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.

Deter answered 21/8, 2016 at 16:54 Comment(0)
N
5

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
Namhoi answered 21/8, 2016 at 17:22 Comment(2)
Thanks. define-values is less ugly than my solution. I'll still probably write a macro to wrap it though.Deter
define-values is specified in R7RS section 5.3.3.Syverson
C
4

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)

Confiding answered 21/8, 2016 at 17:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.