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

Posted

技术标签:

【中文标题】定义,让和设置之间的区别!【英文标题】:Difference between define, let and set! 【发布时间】:2011-07-21 08:02:14 【问题描述】:

好的,这是一个相当基本的问题:我正在关注 SICP 视频,我对 defineletset! 之间的区别有点困惑。

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

(define a 1)
(define a 2)
(write a)

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

(define a (1+ a))

我得到一个错误,而

(set! a (1+ a))

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

2) definelet 之间的区别让我更加困惑。我知道理论上let 用于在本地范围内绑定变量。不过,在我看来,这与define 相同,例如我可以替换

(define (f x)
    (let ((a 1))
        (+ a x)))

(define (g x)
    (define a 1)
    (+ a x))

fg 工作相同:特别是变量ag 之外也未绑定。

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

【问题讨论】:

你应该在f的定义中正确缩进(+ a x),因为它在let的范围内。 你说得对,谢谢 看你使用的scheme系统。 直接来自大师(杰拉尔德·苏斯曼):youtu.be/dO1aqPBJCPg?t=1080 @pakman 该视频说,define 是 let 的语法糖。所以,它仍然没有说明为什么我们需要定义和让,或者在什么情况下你会使用一个而不是另一个。 【参考方案1】:

您的意思是 (+ 1 a) 而不是 (1+ a) 吗?后者在语法上无效。

let 定义的变量范围绑定到后者,因此

(define (f x)
  (let ((a 1))
    (+ a x)))

在语法上是可能的,而

(define (f x)
  (let ((a 1)))
  (+ a x))

不是。

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

(define (g x)
  (define a 1)
  (+ a x))

虽然这段代码会产生错误:

(define (g x)
  (define a 1)
  (display (+ a x))
  (define b 2)
  (+ a x))

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

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

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

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

set! 的有效用法如下:

(define x 12)
> (set! x (add1 x))
> x
13

虽然不鼓励这样做,因为 Scheme 是一种函数式语言。

【讨论】:

1+在Scheme的某些版本中是一个函数,所以调用是有效的。 好吧,define 只允许在函数开头的事实很有趣。这排除了取消匿名函数以创建函数范围的可能性,至少在某些情况下是这样。 @Jeremiah:它是否与 Racket 中的 add1 相同,该函数会增加其参数? 是的,1+是增量函数:(define (1+ a) (+ 1 a)) 在 Racket 中,变量可以定义在函数中的任何位置,而不需要在函数的开头。【参考方案2】:

您可以多次使用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)

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

在内部定义的情况下,我不确定 Yasir 是 正确的。至少在我的机器上,在 R5RS 模式下运行 Racket 常规模式允许内部定义出现在 函数定义,但我不确定标准是怎么说的。在任何 案例,在 SICP 很晚的时候,内部定义姿势的技巧是 深入讨论。第 4 章,如何实现互递归 探索内部定义及其对实施的意义 元循环解释器。

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

【讨论】:

"...但我不确定标准是怎么说的。" R5RS 方案(第 5.2 节)和 R6RS 方案(第 11.2 节)都要求这样做,以实现功能定义体,defines 只能出现在顶部。【参考方案3】:

您的困惑是合理的:“让”和“定义”都创建了新的绑定。 “让”的一个优点是它的含义非常明确。各种 Scheme 系统(包括 Racket)对于普通的“让”的含义绝对没有分歧。

“定义”形式是一锅不同的鱼。与 'let' 不同,它不会用括号包围正文(绑定有效的区域)。此外,它在顶层和内部可能意味着不同的事情。不同的 Scheme 系统对“定义”的含义截然不同。事实上,Racket 最近通过添加可能出现的新上下文来改变“定义”的含义。

另一方面,人们喜欢“定义”;它的缩进较少,并且通常具有“按我的意思做”级别的范围界定,允许递归和相互递归过程的自然定义。事实上,我就在前几天被这个咬了:)。

最后,“设置!”;像'让','设置!非常简单:它改变了现有的绑定。

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

【讨论】:

谢谢;我已经编辑了这个问题,以明确我使用的是set! sfter 定义。该段中的要点是,我可以 set! 变量取决于其先前的值,但我不能 define 变量取决于其先前的值。 对;这是因为“定义”的范围包括绑定本身的右侧。这很重要,因为它允许“定义”创建递归过程,而“让”无法做到这一点(除非您使用类似 Y 组合器的技巧)。 在鸡计划中,设置!也可以用来引入新的绑定。这让我在探索性编程时绊倒了一次。例如, (define (foo a) (set!ba)) 将愉快地在环境的新运行上工作: csi -e '(define (foo a) (set!b (* a 2))) (foo 42) (显示 b)'。好像就这么定了!将环境从大多数本地遍历到全局,直到找到命名变量。我正在使用 4.9.0.1。【参考方案4】:

约翰克莱门茨的回答很好。在某些情况下,您可以看到 defines 在每个版本的 Scheme 中变成了什么,这可能有助于您了解发生了什么。

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

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

您会看到“***”定义变成了 set!(尽管在某些情况下只是扩展 define 会改变事情!),但是内部定义(即在另一个块中的 define)变成letrec*。不同的 Scheme 会将该表达式扩展为不同的事物。

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

【讨论】:

以上是关于定义,让和设置之间的区别!的主要内容,如果未能解决你的问题,请参考以下文章

style.left 与offsetLeft之间的区别

setValue和push方法之间的区别[重复]

自定义 UIStoryboardSegue 和 UIViewController 转换之间的区别

pxemrem三者之间的区别

“定义”和“声明”之间的区别[重复]

雄辩:定义模型和迁移中的关系之间的区别