请考虑以下来自SICP的过程:
(define (make-withdraw balance)
(lambda (amount)
(if (>= balance amount)
(begin (set! balance
(- balance amount))
balance)
"Insufficient funds")))
假设我说:
(define x (make-withdraw 100))
make-withdraw
在称为(lambda (amount) ... )
的新环境中返回一个过程(e2
)(将变量balance
的绑定包含在内),并将该过程绑定到{{1 }}。
现在,说我打电话:
x
其中
(f x)
1 。我听说Scheme是按价值传递的。这是否意味着当 (define (f y) (y 25))
创建新环境f
时,它将在e3
上绑定x
值的副本吗?
2 。也就是说,y
(现在)持有的值(输入y
的正文之后)是f
持有的lambda
的副本吗?
3 。因此,我们现在有了两个变量,全局变量x
和x
中的y
,每个变量都包含一个引用e3
内部事物的过程?
4 。如果我是正确的话,e2
和x
持有的程序是否像指向y
的指针一样?
答案 0 :(得分:2)
如果按值传递,则会评估函数的参数并将其值绑定到函数参数。
因此,如果参数是表达式,则将对其求值并将值绑定到参数。如果它是绑定到值的标识符,则该值将绑定到参数。
如果一个值很简单,例如一个整数,那么该整数会“复制”到新环境中分配的某些内存单元中;如果它更复杂,例如一个闭包(一个编译函数),则可以认为将该对象的“引用”复制到新环境中。
答案 1 :(得分:1)
按值传递并不意味着它不是对象的地址,因为它经常是。 C ++是一种按价值传递的语言。这是按值传递的一个属性的示例:
angular
上面的代码永远不会更改(define (test x)
(set! x 10))
(define y 20)
(test y)
,因为y
是一个新绑定,恰好指向与x
相同的值,并且y
使(set! x 10)
指向不同的值。 x
仍将指向原始值y
。
现在20
中test
的值已更改,因此,如果您有其他入口点对x
做过其他工作,则它将作为对象工作。 x
就是这样工作的。
make-withdraw
上面的代码返回一个闭包,它的顶级环境绑定了(define x (make-withdraw 100))
balanced
,当调用它的数量时,它将100
从set!
结束,然后返回除非资金耗尽,否则为新值。
balance
这将使一个环境容纳(f x)
,这是一个封闭的环境,其封闭环境完好无损,并且绑定x
会在调用结束时消失。它不会复制x
,但是在x
启动时,它永远不会依赖f
是什么,因为它已经传递了值而不是名称。因此,x
与始终调用(f x)
相同。没变化!
答案 2 :(得分:1)
我发现思考这一问题的最佳方法是根据绑定进行思考,而不是仅仅作为绑定容器的环境或框架。
绑定是名称和值之间的关联。该名称通常被称为“变量”,而值则是变量的值。绑定的值可以是该语言可以讨论的任何对象。但是,绑定是幕后的事情(有时称为“不是一流的对象”):它们不是可以用语言表示的事物,而是可以用作模型一部分的事物语言的工作方式。所以绑定的值不能是绑定,因为绑定不是一流的:语言不能谈论绑定。
关于绑定有一些规则:
lambda
和define
; set!
; 显然,需要对这些规则进行详尽的阐述(尤其是在全局绑定和前向引用绑定方面),并且要正式,但是这些足以理解发生了什么。特别是,我并不是真的不需要花费很多时间来担心环境:一点代码的环境只是它可以访问的一组绑定,因此,不必担心环境,而只需要担心绑定。
因此,“按值调用”的意思是,当您调用带有变量(绑定)的参数的过程时,传递给它的是变量绑定的 value ,而不是绑定本身。然后,该过程将创建一个具有相同值的 new 绑定。由此得出两件事:
因此,这是这些规则的一些示例。
(define (silly x)
(set! x (+ x 1))
x)
(define (call-something fn val)
(fn val)
val))
> (call-something silly 10)
10
因此,在这里我们为silly
和call-something
创建两个顶级绑定,这两个顶级绑定都具有过程值。 silly
的值是一个过程,当被调用时:
x
,其值是silly
的参数; call-something
的值是一个过程,当被调用时:
fn
,一个名为val
; fn
绑定的值调用val
绑定的值; val
绑定的值。请注意,对fn
的调用无论做什么 ,它都不能更改val
的绑定,因为它无权访问它。因此,通过查看call-something
的定义,您可以知道:如果它完全返回(如果对fn
的调用未返回,则可能不返回) ,它将返回其第二个参数的值。这种保证就是“按值调用”的含义:支持其他调用机制的语言(例如Fortran)不能总是保证这一点。
(define (outer x)
(define (inner x)
(+ x 1))
(inner (+ x 1)))
这里有四个绑定:outer
是一个顶级绑定,其值是一个过程,该过程在被调用时将为x
创建一个绑定,其值是其参数。然后,它创建另一个称为inner
的绑定,其值是另一个过程,该过程在被调用时为x
到其的创建一个 new 绑定。参数,然后返回该绑定的值加1。 outer
然后使用其对x
的绑定值来调用此内部过程。
这里重要的是,在inner
中有x
的两个绑定在词法上可能是可见的,但是最接近的一个-由inner
建立的-之所以会获胜,是因为一次只能访问一个给定名称的绑定。
以下是用显式inner
表示的先前代码(如果lambda
是递归的,则不会等效):
(define outer
(λ (x)
((λ (inner)
(inner (+ x 1)))
(λ (x)
(+ x 1)))))
最后是更改绑定的示例:
(define (make-counter val)
(λ ()
(let ((current val))
(set! val (+ val 1))
current)))
> (define counter (make-counter 0))
> (counter)
0
> (counter)
1
> (counter)
2
因此,这里,make-counter
(是绑定的名称,其值是一个过程,该过程在被调用时)为val
建立新的绑定,然后返回它创建的过程。此过程将创建一个名为current
的新绑定,该绑定捕获val
的当前值,对val
的绑定进行变异并将其添加一个,然后返回该值current
中的。这段代码执行了“如果您能看到绑定,就总是可以看到它”的规则:通过调用val
创建的make-counter
的绑定在返回的过程中一直可见该过程存在(并且只要存在绑定,该过程就存在),并且它还会使用set!
使绑定发生突变。
chapter 3介绍了“环境模型”,其中在任何时候都存在一个由一系列框架组成的环境,每个框架都包含绑定。显然,这是一个很好的模型,但是它引入了三种类型的东西-环境,环境中的框架和框架中的绑定-其中两种是完全无形的。至少对于绑定,您可以通过某种方式来掌握它:可以在代码中看到它的创建过程,也可以看到对其的引用。因此,我不想考虑这两种额外的事情,而这些事情是您永远无法获得的。
但是,这是一个在实践中没有区别的选择:单纯地考虑绑定问题对我有帮助,而考虑环境,框架和绑定方面的思想则可能对其他人有更多帮助。
在接下来的内容中,我将使用速记来讨论绑定,尤其是顶级绑定:
x
是一个过程,...'意味着'x
是绑定的名称,其值是一个过程,当被调用时,...'; y
是...'意味着'y
是绑定的名称,其值是...'; x
调用y
'意味着'用x
命名的绑定值调用y
命名的绑定的值'; < / li>
x
绑定到...'意味着'...创建一个名称为x
且其值为...的绑定''; x
”表示“ x
的值”; 像这样描述绑定是很常见的,因为完全显式的方式很痛苦:我已经尝试过(但可能在某些地方失败了),在上面完全清楚了。
最后,经过漫长的序言,这是您提出的问题的答案。
(define (make-withdraw balance)
(λ (amount)
(if (>= balance amount)
(begin (set! balance (- balance amount))
balance)
"Insufficient funds")))
make-withdraw
将balance
绑定到其参数并返回其执行的过程。此过程称为:
amount
绑定到其参数; amount
与balance
(由于它在创建时就可以看到),因此仍可以看到{1>}; balance
绑定,将其值减amount
绑定的值,然后返回新值; "Insuficient funds"
(但 not 不会使balance
绑定发生突变,因此您可以使用较小的金额重试:真实的银行可能会此时从balance
绑定中吸取一些钱是罚款)。现在
(define x (make-withdraw 100))
为x
创建一个绑定,其值是上述过程之一:在该过程中,balance
最初是100
。
(define (f y) (y 25))
f
是一个过程(是一个绑定的名称,其值是一个过程,当被调用时),它将y
绑定到其参数,然后使用{{1 }}。
25
因此,(f x)
与f
一起调用,x
是(绑定到)上面构造的过程。在x
中,f
绑定到此过程(而不是副本),然后使用参数y
调用此过程。然后,此过程的行为如上所述,结果如下:
25
请注意:
我希望这有道理。
您可能想做的是撤消您帐户上的交易。一种方法是返回以及撤销新余额的过程,该过程将撤消上一次交易。这是执行此操作的过程(此代码在Racket中):
> (f x)
75
> (f x)
50
> (f x)
25
> (f x)
0
> (f x)
"Insufficient funds"
使用此过程创建帐户时,调用它会返回两个值:第一个是新余额,或者是(define (make-withdraw/backout
balance
(insufficient-funds "Insufficient funds"))
(λ (amount)
(if (>= balance amount)
(let ((last-balance balance))
(set! balance (- balance amount))
(values balance
(λ ()
(set! balance last-balance)
balance)))
(values
insufficient-funds
(λ () balance)))))
的值(默认为insufficient-funds
),第二个是一个过程将会撤消您刚刚完成的交易。请注意,它通过显式地退回旧余额来消除它,因为我认为在浮点算术存在的情况下您不一定要依靠"Insufficient funds"
为真。如果您了解这是如何工作的,那么您可能会了解绑定。