定义,设置和设置之间的区别!

And*_*rea 40 scheme

好吧,这是一个相当基本的问题:我下面的SICP视频,我有点困惑之间的差异define,letset!.

1)根据视频中的Sussman,define允许只附加一次值(在REPL中除外),特别是不允许两行定义.然而,Guile愉快地运行了这段代码

(define a 1)
(define a 2)
(write a)
Run Code Online (Sandbox Code Playgroud)

正如预期的那样输出2.事情有点复杂,因为如果我尝试这样做(编辑:在上述定义之后)

(define a (1+ a))
Run Code Online (Sandbox Code Playgroud)

我得到一个错误,而

(set! a (1+ a))
Run Code Online (Sandbox Code Playgroud)

被允许.我仍然不认为这是set!define我之间的唯一区别:我错过了什么?

2)definelet我之间的区别更让我困惑.我知道理论上let用于绑定局部范围内的变量.尽管如此,在我看来,这也是一样的define,例如我可以替换

(define (f x)
    (let ((a 1))
        (+ a x)))
Run Code Online (Sandbox Code Playgroud)

(define (g x)
    (define a 1)
    (+ a x))
Run Code Online (Sandbox Code Playgroud)

fg工作相同:在特定的变量a是未结合的外面g为好.

我可以看到这个有用的唯一方法是let可能具有整个函数定义的更短范围.在我看来,总是可以添加一个匿名函数来创建必要的范围,并立即调用它,就像在javascript中一样.那么,真正的优势是let什么?

Joh*_*nts 32

你的困惑是合理的:'let'和'define'都会创建新的绑定."让"的一个优点是它的含义非常明确; 各种Scheme系统(包括Racket)之间绝对没有关于普通的"让"意味着什么的分歧.

'定义'形式是一个不同的鱼.与'let'不同,它不会用括号围绕主体(绑定有效的区域).此外,它可能意味着顶层和内部的不同事物.不同的Scheme系统对'define'有着截然不同的含义.实际上,Racket最近通过添加可以发生的新上下文来改变'define'的含义.

另一方面,人们喜欢'定义'; 它具有较少的缩进,并且它通常具有"do-what-what-mean-mean"级别的范围,允许递归和相互递归过程的自然定义.事实上,前几天我被这个咬了:).

最后,'设置!'; 比如'let','set!' 非常简单:它改变了现有的绑定.

FWIW,了解DrRacket中这些范围的一种方法(如果您正在使用它)是使用"检查语法"按钮,然后将鼠标悬停在各种标识符上以查看它们的绑定位置.


Yas*_*aev 16

你是说(+ 1 a)而不是(1+ a)?后者在语法上没有效力.

因此,定义的变量范围let与后者相关

(define (f x)
  (let ((a 1))
    (+ a x)))
Run Code Online (Sandbox Code Playgroud)

在语法上是可能的,而

(define (f x)
  (let ((a 1)))
  (+ a x))
Run Code Online (Sandbox Code Playgroud)

不是.

所有变量都必须define在函数的开头d,因此可以使用以下代码:

(define (g x)
  (define a 1)
  (+ a x))
Run Code Online (Sandbox Code Playgroud)

而此代码将生成错误:

(define (g x)
  (define a 1)
  (display (+ a x))
  (define b 2)
  (+ a x))
Run Code Online (Sandbox Code Playgroud)

因为定义之后的第一个表达式意味着没有其他定义.

set!不定义变量,而是用于为变量赋值新值.因此,这些定义毫无意义:

(define (f x)
  (set! ((a 1))
    (+ a x)))

(define (g x)
  (set! a 1)
  (+ a x))
Run Code Online (Sandbox Code Playgroud)

有效用途set!如下:

(define x 12)
> (set! x (add1 x))
> x
13
Run Code Online (Sandbox Code Playgroud)

虽然它不鼓励,因为Scheme是一种功能语言.

  • `1 +`是某些版本的Scheme中的函数,因此调用有效. (11认同)
  • 是的,`1 +`是增量函数:`(define(1+ a)(+ 1 a))` (2认同)
  • 在Racket中,变量可以在函数的任何地方定义,而不是在函数的开头. (2认同)

erj*_*ang 7

John Clements的回答很好.在某些情况下,您可以看到define每个版本的Scheme中的内容,这可能有助于您了解正在发生的事情.

例如,在Chez Scheme 8.0(它有自己的define怪癖,特别是W6 R6RS!)中:

> (expand '(define (g x)
             (define a 1)
             (+ a x)))
(begin
  (set! g (lambda (x) (letrec* ([a 1]) (#2%+ a x))))
  (#2%void))
Run Code Online (Sandbox Code Playgroud)

你看到"顶级"定义变成了一个set!(尽管define在某些情况下只是扩展会改变一些东西!),但是内部定义(即define内部另一个块)变成了一个letrec*.不同的方案将把表达扩展到不同的东西.

MzScheme v4.2.4:

> (expand '(define (g x)
             (define a 1)
             (+ a x)))
(define-values
 (g)
 (lambda (x)
   (letrec-values (((a) '1)) (#%app + a x))))
Run Code Online (Sandbox Code Playgroud)


mic*_*kig 5

您可能可以define多次使用,但这不是惯用的:define暗示您正在向环境添加定义并set!暗示您正在改变某些变量。

我不确定 Guile 以及为什么它会允许,(set! a (+1 a))但如果a尚未定义,则不应该起作用。通常人们会 define用来引入一个新变量,然后才对其进行变异set!

您可以使用匿名函数应用程序而不是let,实际上这通常正是let扩展为的内容,它几乎总是一个宏。这些是等效的:

(let ((a 1) (b 2))
  (+ a b))

((lambda (a b)
   (+ a b))
 1 2)
Run Code Online (Sandbox Code Playgroud)

您使用的原因let是它更清晰:变量名就在值的旁边。

在内部定义的情况下,我不确定 Yasir 是否正确。至少在我的机器上,在 R5RS 模式和常规模式下运行 Racket 允许内部定义出现在函数定义的中间,但我不确定标准说的是什么。无论如何,在 SICP 的后期,内部定义姿势的技巧被深入讨论。第 4 章探讨了如何实现相互递归的内部定义及其对元循环解释器实现的意义。

所以坚持下去!SICP 是一本精彩的书,视频讲座很棒。