Create environment with variables in Scheme

2019-07-28 23:44发布

问题:

I was looking to create something similar to an object or an environment kind of like in OOP

This is what I was thinking:

(define env (variable 'x 10 env))

Where I define an env and create a variable of value 10 in that environment.

I also want to be able to call on the values in that environment. For example

(get-value env 'x)
> 10

The most I can understand is that it involves closures but i'm not sure where to start from there

回答1:

The easiest way of achieving this is using alist. The above variable and get-value can be defined as the following:

(define (variable name value env) (cons (cons name value) env))
(define (get-value name env) (cond ((assq name env) => cdr) (else #f)))

The initial environment is '() if you want to hide the empty list you can also define like this:

(define initial-env '())


回答2:

There are many ways to do this. The classical way is to use an alist:

(define (variable name value env)
  (cons (cons name value) env))

(define (get-value name env)
  (let ((val (assq name env)))
    (if val 
        (cdr val)
        (error "Unbound variable" name)))) ;  for r6rs use raise

;; the empty environment
(define the-empty-environment '())

Al Petrofsky made eiod (Eval in one define) which did it like this:

;; actually called extend
(define (variable name value env)
  (lambda (i) (if (eq? name i) value (env i)))

;; just a wrapper, no need for it since you can just call env with the name
(define (get-value name env)
  (env name))

;; the empty environment
(define (the-empty-environment i) 
  (error "Unbound variable" i))

In SICP you have frames with bindings:

;; actually called extend-environment
(define (variables vars vals env)
  (if (= (length vars) (length vals))
      (cons (make-frame vars vals) env)
      (if (< (length vars) (length vals))
          (error "Too many arguments supplied" vars vals)
          (error "Too few arguments supplied" vars vals))))

;; actualy called lookup-variable-value 
(define (get-value var env)
  (define (env-loop env)
    (define (scan vars vals)
      (cond ((null? vars)
             (env-loop (enclosing-environment env)))
            ((eq? var (car vars))
             (car vals))
            (else (scan (cdr vars) (cdr vals)))))
    (if (eq? env the-empty-environment)
        (error "Unbound variable" var)
        (let ((frame (first-frame env)))
          (scan (frame-variables frame)
                (frame-values frame)))))
  (env-loop env))


;; the empty environment
(define the-empty-environment '())

;; referenced
(define (enclosing-environment env) (cdr env))
(define (first-frame env) (car env))
(define (make-frame variables values)
  (cons variables values))
(define (frame-variables frame) (car frame))
(define (frame-values frame) (cdr frame))

Note that unlike the other ones that adds one this adds a whole set. eg. to bind x to 5 and y to 7 you do:

(variables '(x y)  '(5 7) env)

I'll add my own take on this:

(define (variable var val env)
  (hash-set env var val))

(define (get-value name env)
  (hash-ref env name (lambda () (error "Unbound variable" name))))

;; the empty environment
(define the-empty-environment '#hasheq()) 

The whole point by this answer is that given you have the design choice of both the get-value, variable and the-empty-environment. As long as you can implement these it really doesn't matter how it is done. you can replace one for the other and the interpreter will still work. That is except the SICP one that requires you to do one frame at a time.