Unbound variable in Racket-Scheme
I am trying to implement a data structure to store the state (bind variables) of a program. I represent a state with association lists, which are lists of key-value pairs.
Example of associations list: '((x . 3) (y . (1 2)) (z . a))
. In this example, the key 'x
matters 3
, 'y
matters, '(1 2)
and 'z
matters 'a
.
To manage the state, I have two functions:
-
(get-binding state var)
This function returns the var value character associated (assigned) to the state.
-
(set-binding state var val)
This function returns a new state that is the same as the state, except that var is bound to val.
-
empty-state
This variable corresponds to the unbound state. It is defined using define. Note: empty state is not a function.
Example:
> (get-binding (set-binding (set-binding empty-state 'x 3) 'x 4) 'x)
which outputs 4
.
Here is my code:
(define (enclosing-environment env) (mcdr env))
(define empty-state null)
(define (get-binding env var)
(define (env-loop env)
(define (scan vars vals)
(cond [(null? vars)
(env-loop (enclosing-environment env))]
[(eq? var (mcar vars))
(mcar vals)]
[else
(scan (mcdr vars)
(mcdr vals))]))
(if (eq? env empty-state)
(error "Unbound variable:" var)
(let ([frame (first-frame env)])
(scan (frame-variables frame)
(frame-values frame)))))
(env-loop env))
(define (set-binding! env var val)
(define (env-loop env)
(define (scan vars vals)
(cond [(null? vars)
(env-loop (enclosing-environment env))]
[(eq? var (mcar vars))
(set-mcar! vals val)]
[else
(scan (mcdr vars)
(mcdr vals))]))
(if (eq? env empty-state)
(error "Unbound variable -- SET!:" var)
(let ([frame (first-frame env)])
(scan (frame-variables frame)
(frame-values frame)))))
(env-loop env))
But the error message "Unbound variable - SET !: x" appears. How can I prevent this?
source to share
Do you understand that get-binding!
both set-binding
are the same function, except for two lines? I suggest you combine them into one function:
(define (get/set-binding when-found when-unbound env var (val))
(define (env-loop env)
(define (scan vars vals)
(cond [(null? vars)
(env-loop (enclosing-environment env))]
[(eq? var (mcar vars))
(when-found val vals)]
[else
(scan (mcdr vars)
(mcdr vals))]))
(if (eq? env empty-state)
(when-unbound var)
(let ([frame (first-frame env)])
(scan (frame-variables frame)
(frame-values frame)))))
(env-loop env))
Then you can fix the error. The problem is that there is no way to change null
, so you cannot enter the binding by changing the argument. You can only do this through the return value. You can define set-binding!
like this:
(define (set-binding! env var val)
(set/get-binding (λ (val vals)
(set-mcar! vals val)
env)
(λ (var)
(add-binding var val env)) env var val))
To determine add-binding
, I have to make some guesses about the structure env
based on your code. I am assuming that env
is a mlist of frames and that a frame
:
(define-struct frame (variables values))
I also assume that if env
has any frames, you want to add a new variable to the first frame.
If these assumptions are correct, then it add-binding
will be determined as follows:
(require compatibility/mlist)
(define (add-binding var val env)
(if (null? env)
(mlist (make-frame (mlist var) (mlist val)))
(let ((first (mcar env)))
(set-frame-variables! (mcons var (frame-variables first)))
(set-frame-values! (mcons val (frame-values first)))
env)))
Since this version set-binding!
returns a new one env
, if you pass null
for env
, you always need to assign the return value set-binding!
:
Correct use:
(set! env (set-binding! env var val))
Improper use:
;; If env is null, the new binding is immediately garbage-collected.
(set-binding! env var val)
source to share
That's a lot of code ...
What if "environment" is a function looking for a value associated with a name? To get the value we need is to apply the environment to the name:
(define (env-get env name)
(env name))
And then, to extend the environment by creating another, we look up the name, but if we don't find it, we defer the base environment:
(define (env-set env name value)
(lambda (nname)
(if (eq? nname name)
value
(env nname))))
But, if the environment is empty, it's just an error.
(define env-empty
(lambda (nname)
(error "Unbound Variable: " nname)))
Example:
> (define env-1 (env-set env-empty 'x 4))
> (define env-2 (env-set env-1 'y '(a b)))
> (env-get env-2 'x)
4
> (env-get env-2 'y)
(a b)
>
source to share
Premises
-
The error is indicated in the code.
-
It is called when the value
env
passed toset-binding!
iseq?
tonull
, aliased asempty-state
. -
env
is compared tonull
before(var . val)
added toenv
.
Hypothesis
The source environment is empty ... i.e. null
or an alias like empty-environment
.
Decision
Do not treat (eq? env empty-state)
as an error condition ... at least not until initialization.
source to share