Defining two functions sharing state without polluting the
top level with unneccesary definitions.
There are several solutions to this problem.
1. Using
define-values
(define-values (inc dec reset)
(let ([state 0])
(define (inc) (set! state (+ state 1)) state)
(define (dec) (set! state (- state 1)) state)
(define (reset)(set! state 0) state)
(values inc dec reset)))
2. Using modules
(module foo mzscheme
(provide inc dec reset)
(define state 0)
(define (inc) (set! state (+ state 1)) state)
(define (dec) (set! state (- state 1)) state)
(define (reset)(set! state 0) state))
3. Using a let to keep the shared state out of the top level
(define inc 'uninitialized)
(define dec 'uninitialized)
(define reset 'uninitialized)
(let ([state 0])
(define (internal-inc) (set! state (+ state 1)) state)
(define (internal-dec) (set! state (- state 1)) state)
(define (internal-reset) (set! state 0) state)
(set! inc internal-inc)
(set! dec internal-dec)
(set! reset internal-reset))
The first and third solutions uses nothing more than plain R5RS
(
define-values can be defined using syntax-rules).
The second solution is perhaps the most common, when there is no
compatibility concerns.
--
JensAxelSoegaard
Sharing state between two functions - aside from R5RS compliance, what are the relative advantages/disadvantages?
How does each solution work?
How will each solution affect usage?
--
GordonWeakliem - 26 Apr 2004
Solution 1 shows intent most clearly.
Solution 2 is often used if you already are using modules (no extra work)
Solution 3 is the only directly portable.
--
JensAxelSoegaard
Is there an implementation of
define-values available anywhere?
--
AndrewWilcox - 05 Dec 2004
In some Scheme implementations such as
PLT Scheme? it is simply
a primitive. In others one can by simple means define your own.
As an example the following was written by Shiro Kawai for
Gauche?:
(define-syntax define-values
(syntax-rules ()
((_ "gentmp" (tmp ...) () (var ...) expr)
(begin (define var (undefined)) ...
(receive (tmp ...) expr
(set! var tmp) ...
(undefined))))
((_ "gentmp" (tmp ...) (v v2 ...) (var ...) expr)
(define-values "gentmp" (tmp ... tmp1) (v2 ...) (var ...) expr))
((_ (var ...) expr)
(define-values "gentmp" () (var ...) (var ...) expr))
((_ . else)
(syntax-error "malformed define-values" (define-values . else)))
))
It uses
receive , which is defined in it's own
SRFI. It also uses
(undefined) to produce an undefined value. One can use
(if #f 42)
in stead.
See
gauche-init.scm.
--
JensAxelSoegaard - 05 Dec 2004
Thank you.
I notice that in R5RS internal definitions (R5RS 5.2.2) and in module systems such as in Chez Scheme, a series of definitions may be followed by expressions, but further definitions can't follow an expression. Since this
define-values expands into a
receive expression, a
define-values defintion can not be followed by more definitions within a body or a module. I wonder if it is
possible to implement a
define-values macro in a way so that it can be followed by more definitions. I've been looking at this, and I'm beginning to suspect not.
--
AndrewWilcox - 05 Dec 2004
Interesting question. I suspect that is the reason
define-values is a primitive in
PLT Scheme?,
but try asking the same question at
comp.lang.scheme?.
--
JensAxelSoegaard - 05 Dec 2004
I just wrote a version of the above
define-values syntax-rules macro using Scheme48 explicit renaming macros:
,open primitives ,open srfi-8 ,for-syntax ,open scheme srfi-1 destructuring
(define-syntax define-values
(lambda (form r compare)
(define (gensym i)
(r (string->symbol (string-append "x" (number->string i)))))
(destructure (((define-values values expr) form))
(let ((tmp-vars (map gensym (iota (length values)))))
`(,(r 'begin)
,@(map (lambda (name) `(,(r 'define) ,name (,(r 'unspecific)))) values)
(,(r 'receive) ,tmp-vars ,expr
,@(map (lambda (var tmp) `(,(r 'set!) ,var ,tmp)) values tmp-vars)))))))
--
AndreasRottmann - 23 May 2005