How is set! defined in scheme?

后端 未结 6 1144
囚心锁ツ
囚心锁ツ 2020-12-20 05:45

How would you implement your own set! function in Scheme? A set! function is a destructive procedure that changes a value that is defined taking into account th

相关标签:
6条回答
  • 2020-12-20 06:19

    set! modifies a binding between a symbol and a location (anyting really). Compilers and Interpreters would treat set! differently.

    An interpreter would have an environment which is a mapping between symbols and values. Strictly set! changes the value of the first occurrence of the symbol to point to the result of the evaluation of the second operand. In many implementations you cannot set! something that is not already bound. In Scheme it is expected that the variable is already bound. Reading SICP or Lisp in small pieces and playing with examples would make you a master implementer of interpreters.

    In a compilers situation you don't really need a symbol table. You can keep evaluated operands on a stack and set! need either change what the stack location pointed to or if it's a free variable in a closure you can use assignment conversion. E.g. it could be boxed to a box or a cons.

    (define (gen-counter start delta)
      (lambda ()
         (let ((cur start))
             (set! start (+ cur delta))
             cur)))
    

    Might be translated to:

    (define (gen-counter start delta)
      (let ((start (cons start '()))
         (lambda ()
             (let ((cur (car start)))
                (set-car! start (+ cur delta))
                cur)))))
    

    You might want to read Control-Flow Analysis of Higher-Order Languages where this method is used together with lots of information on compiler technique.

    0 讨论(0)
  • 2020-12-20 06:21

    In scheme there are 2 "global" environments.

    There is an environment where you store your highest defined symbols and there is another environment where are stored the primitive functions and the global variables that represent parameters of the system.

    When you write something like (set! VAR VAL), the interpreter will look for the binding of VAR.

    You cannot use set! on a variable that was not bound. The binding is done either by define or by lambda or by the system for the primitive operators.

    Binding means allocating a location in some environment. So the binding function has the signature symbol -> address.

    Coming back to set!, the interpreter will look in the environment where VAR is bound. First it looks in local environment (created by lambda), then it its local parent environment, and so on, then in global environment, then in system environment (in that order) until it finds an environment frame that contains a binding for VAR.

    0 讨论(0)
  • 2020-12-20 06:28

    If you are implementing a simple interpreter, then it is not all that hard. Your environment will map from identifiers to their values (or syntactic keywords to their transformers). The identifier->value mapping will need to account for possible value changes. As such:

    (define (make-cell value)
      `(CELL ,value))
    
    (define cell-value cadr)
    (define (cell-value-set! cell value)
      (set-car! (cdr cell) value))
    
    ...
    ((set-stmt? e)
     (let ((name  (cadr e))
           (value (interpret (caddr e) env)))
       (let ((cell (env-lookup name)))
         (assert (cell? cell))
         (cell-value-set! cell value)
         'return-value-for-set!)))
    ...
    

    With two other changes being that when you bind an identifier to a value (like in a let or lambda application) you need to extend the environment with something like:

      (env-extend name (cell value) env)
    

    and also when retrieving a value you'll need to use cell-value.

    Of course, only mutable identifiers need a cell, but for a simple interpreter allocating a cell for all identifier values is fine.

    0 讨论(0)
  • 2020-12-20 06:35

    As has been mentioned, set! is a primitive and can't be implemented as a procedure. To really understand how it works under the hood, I suggest you take a look at the inner workings of a Lisp interpreter. Here's a great one to start: the metacircular evaluator in SICP, in particular the section titled "Assignments and definitions". Here's an excerpt of the parts relevant for the question:

    (define (eval exp env)
      (cond ...
            ((assignment? exp) (eval-assignment exp env))
            ...
            (else (error "Unknown expression type -- EVAL" exp))))
    
    (define (assignment? exp)
      (tagged-list? exp 'set!))
    
    (define (eval-assignment exp env)
      (set-variable-value! (assignment-variable exp)
                           (eval (assignment-value exp) env)
                           env)
      'ok)
    
    (define (set-variable-value! var val env)
      (define (env-loop env)
        (define (scan vars vals)
          (cond ((null? vars)
                 (env-loop (enclosing-environment env)))
                ((eq? var (car vars))
                 (set-car! vals val))
                (else (scan (cdr vars) (cdr vals)))))
        (if (eq? env the-empty-environment)
            (error "Unbound variable -- SET!" var)
            (let ((frame (first-frame env)))
              (scan (frame-variables frame)
                    (frame-values frame)))))
      (env-loop env))
    

    In the end, a set! operation is just a mutation of a binding's value in the environment. Because a modification at this level is off-limits for a "normal" procedure, it has to be implemented as a special form.

    0 讨论(0)
  • 2020-12-20 06:35

    Can't, can't, can't. Everyone is so negative! You can definitely do this in Racket. All you need to do is to define your own "lambda" macro, that introduces mutable cells in place of all arguments, and introduces identifier macros for all of those arguments, so that when they're used as regular varrefs they work correctly. And a set! macro that prevents the expansion of those identifier macros, so that they can be mutated.

    Piece of cake!

    0 讨论(0)
  • 2020-12-20 06:36

    As noted in the comments, set! is a primitive in Scheme that must be provided by the implementation. Similarly, you can't implement the assignment operator, =, in most programming languages. In Common Lisp, setf can be extended (using setf-expanders) to allow (setf form value) to work on new kinds of forms.

    Because Scheme's set! only modifies variable bindings (like Common Lisp's setq), it is still worth asking how can we implement functions like set-car! and other structural modifiers. This could be seen, in one sense, as a generalization of assignment to variables, but because lexical variables (along with closures) are sufficient to represent arbitrarily complex structures, it can also be seen as a more specialized case. In Scheme (aside from built in primitives like arrays), mutation of object fields is a specialization, because objects can be implemented by lexical closures, and implemented in terms of set!. This is a typical exercise given when showing how structures, e.g., cons cells, can be implemented using lexical closures alone. Here's an example that shows the implementation of single-value mutable cells::

    (define (make-cell value)
      (lambda (op)
        (case op
          ((update)
           (lambda (new-value)
             (set! value new-value)))
          ((retrieve)
           (lambda ()
             value)))))
    
    (define (set-value! cell new-value)
      ((cell 'update) new-value))
    
    (define (get-value cell)
      ((cell 'retrieve)))
    

    Given these definitions, we can create a cell, that starts with the value 4, update the value to 8 using our set-value!, and retrieve the new value:

    (let ((c (make-cell 4)))
      (set-value! c 8)
      (get-value c))
    => 8
    
    0 讨论(0)
提交回复
热议问题