定义,让和设置之间的区别!
Posted
技术标签:
【中文标题】定义,让和设置之间的区别!【英文标题】:Difference between define, let and set! 【发布时间】:2011-07-21 08:02:14 【问题描述】:好的,这是一个相当基本的问题:我正在关注 SICP 视频,我对 define
、let
和 set!
之间的区别有点困惑。
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) define
和 let
之间的区别让我更加困惑。我知道理论上let
用于在本地范围内绑定变量。不过,在我看来,这与define
相同,例如我可以替换
(define (f x)
(let ((a 1))
(+ a x)))
与
(define (g x)
(define a 1)
(+ a x))
和f
和g
工作相同:特别是变量a
在g
之外也未绑定。
我认为这很有用的唯一方法是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))
不是。
所有变量都必须在函数开头为define
d,因此可以使用以下代码:
(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 节)都要求这样做,以实现功能定义体,define
s 只能出现在顶部。【参考方案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】:
约翰克莱门茨的回答很好。在某些情况下,您可以看到 define
s 在每个版本的 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))))
【讨论】:
以上是关于定义,让和设置之间的区别!的主要内容,如果未能解决你的问题,请参考以下文章