1

set!您将如何在 Scheme 中实现自己的功能?函数是一种set!破坏性过程,它会更改考虑到先前值而定义的值。

4

6 回答 6

7

如评论中所述,set!是 Scheme 中的一个原语,必须由实现提供。同样,您无法=在大多数编程语言中实现赋值运算符 。在 Common Lisp 中,setf可以扩展(使用setf-expanders)以允许(setf form value)处理新类型的表单。

因为Schemeset!只修改变量绑定(如Common Lisp 的setq),所以仍然值得询问我们如何实现类似set-car!和其他结构修饰符的函数。在某种意义上,这可以看作是对变量赋值的一种概括,但是因为词法变量(连同闭包)足以表示任意复杂的结构,所以它也可以看作是一种更特殊的情况。在 Scheme 中(除了像数组这样的内置原语),对象字段的变异是一种特殊化,因为对象可以通过词法闭包来实现,并根据以下方式实现set!. 这是一个典型的练习,用于展示如何单独使用词法闭包来实现结构,例如 cons 单元。这是一个显示单值可变单元格实现的示例:

(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)))

给定这些定义,我们可以创建一个以 value 开头的单元格,将 value4更新为8使用 our set-value!,然后检索新值:

(let ((c (make-cell 4)))
  (set-value! c 8)
  (get-value c))
=> 8
于 2013-05-24T12:31:12.007 回答
6

如前所述,set!它是一个原语,不能作为一个过程来实现。要真正理解它是如何工作的,我建议你看一下 Lisp 解释器的内部工作原理。这是一个很好的开始:SICP 中的元循环评估器特别是标题为“分配和定义”的部分。以下是与问题相关部分的摘录:

(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))

最后,set!操作只是环境中绑定值的突变。因为这个级别的修改对于“正常”程序是禁止的,所以它必须作为一种特殊的形式来实现。

于 2013-05-24T13:46:12.843 回答
4

不能,不能,不能。大家都很消极!你绝对可以在 Racket 中做到这一点。您需要做的就是定义自己的“lambda”宏,它引入可变单元格代替所有参数,并为所有这些参数引入标识符宏,以便当它们用作常规 varrefs 时,它们可以正常工作。还有一套!防止这些标识符宏扩展的宏,以便它们可以被变异。

小菜一碟!

于 2013-05-24T17:04:19.720 回答
2

放!修改符号和位置之间的绑定(真的)。编译器和解释器会处理 set!不同。

解释器将有一个环境,它是符号和值之间的映射。严格规定!将符号第一次出现的值更改为指向第二个操作数的计算结果。在许多实现中,您无法设置!尚未绑定的东西。在 Scheme 中,预计变量已经绑定。阅读SICPLisp 小片段并使用示例将使您成为解释器的主要实现者。

在编译器情况下,您实际上并不需要符号表。您可以将评估的操作数保存在堆栈中并设置!需要更改堆栈位置指向的内容,或者如果它是闭包中的自由变量,则可以使用赋值转换。例如,它可以装箱到一个盒子或一个缺点。

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

可以翻译成:

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

您可能想阅读高阶语言的控制流分析,其中此方法与大量有关编译器技术的信息一起使用。

于 2013-05-24T17:32:43.300 回答
0

如果您正在实现一个简单的解释器,那么这并不难。您的环境将从标识符映射到它们的值(或语法关键字到它们的转换器)。标识符-> 值映射需要考虑可能的值变化。像这样:

(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!)))
...

另外两个更改是,当您将标识符绑定到值(例如在 aletlambda应用程序中)时,您需要使用以下内容扩展环境:

  (env-extend name (cell value) env)

并且在检索值时您需要使用cell-value.

当然,只有可变标识符需要一个单元格,但是对于一个简单的解释器来说,为所有标识符值分配一个单元格就可以了。

于 2013-05-24T20:09:26.900 回答
0

在方案中有 2 个“全局”环境。

有一个环境可以存储最高定义的符号,还有另一个环境可以存储原始函数和表示系统参数的全局变量。

当你写类似的东西(set! VAR VAL)时,解释器会寻找VAR.

你不能使用设置!在未绑定的变量上。绑定是通过定义或通过 lambda 或由系统为原始运算符完成的。

绑定意味着在某个环境中分配一个位置。所以绑定函数有签名symbol -> address

回到set!,解释器将在VAR绑定的环境中查找。首先,它在本地环境(由 创建lambda)中查找,然后在其本地父环境中查找,依此类推,然后在全局环境中,然后在系统环境中(按此顺序),直到找到包含 的绑定的环境框架VAR

于 2020-04-27T10:15:54.030 回答