我正在通过SICP工作,我正在进行的练习要求返回列表中最后一个元素的过程。我实现了程序last-pair
来执行此操作,但我很困惑为什么它返回列表而不是数字:
(define (last-pair alist)
(cond ((null? (cdr alist))
(car alist)) ; still happens if this is just "car alist)"
(else
(last-pair (cdr alist)))))
当我在1到5的整数列表上调用它时,我得到输出'(5):
> (last-pair (list 1 2 3 4 5))
'(5)
我期待5
,就像(car (list 1 2 3 4 5))
返回1
而不是'(1)
的方式一样。
为什么我会'(5)
而不是5
?
我正在使用DrRacket 5.3.3和Racket Scheme。
编辑1:MIT-Scheme似乎没有这样做。 last-pair
返回5
而不是'(5)
。哪个是对的?!?
编辑2:有趣的是,在DrRacket(不在MIT-Scheme中),如果第二行(cond ((null? (cdr alist))
缩进两个空格,则在调用过程时,它返回'(5)
。但是,当第二行没有缩进时,它返回5
。这是一个小故障吗?我相信所有那些Scheme解释器应该遵循括号,对吗? 德尔>
编辑3:我开始认为这是DrRacket的一个小故障。当我将过程定义放在定义窗口(通常是顶层编辑器窗格)中时,无论是否有缩进,该过程都将返回5
。但是,如果我在界面窗口中定义它,,缩进会影响结果,如编辑2中所述。(编辑4),无论缩进如何,它都将返回{ {1}}。
< 用一些关于缩进差异的代码剪掉了流行的部分;现在的问题是定义过程的位置,请参阅编辑4 >
编辑4:好的我已经简化了问题。
'(5)
返回(last-pair (list 1 2 3 4 5))
,其中5
定义如上。无论缩进。last-pair
过程,然后单击“运行”时,last-pair
将返回(last-pair (list 1 2 3 4 5))
。无论缩进。5
过程时,last-pair
'(5)。无论缩进。这是一个截图:
答案 0 :(得分:3)
由于(list 1 2 3 4 5)
返回(cons 1 (cons 2 (cons 3 (cons 4 (cons 5 '())))))
,最后一对为(cons 5 '())
。
在你的函数中,chnage ((null? (cdr alist)) (car alist))
到((null? (cdr alist)) alist)
以便重新调整最后一对(而不是最后一对的汽车。
编辑:
这解释了您在定义和交互窗口中看到的结果之间的差异。造成混淆的主要原因是内置了last-pair
。如果您使用名称my-last-pair
,您将在两个窗口中看到相同的结果。
在定义窗口中,(define (last-pair ...
被解释为意味着您要重新定义内置函数。因此,last-pair
递归地引用您自己的last-pair
定义。这最终会在您的示例中给出结果5
在交互窗口中,对last-pair
的递归调用是指内置版本。因此,当使用列表last-pair
调用(2 3 4 5)
时,内置版本会返回最后一对,即(cons 5 '())
,此值将打印为(5)
。
简而言之:混淆是由于在交互窗口中重新定义了内置函数。在定义窗口中按预期处理重新定义。虽然令人困惑但是交互窗口的行为方式背后有原因(解决这个问题,反过来又会引起其他地方的混乱)。
答案 1 :(得分:0)
最好不要使用内置名称last-pair
。我建议使用更符合您期望的内容,例如last-elem
。
重命名功能时,请务必重命名;即,不仅在定义站点更改函数的名称,而且在每个调用站点更改函数的名称,当然包括在其正文中,它被称为递归。必须勤勉地重命名,否则很容易引入新的错误。
关于REPL的奇怪行为。我的猜测是,当你进入
(define (last-pair alist) ;; definition
(cond ((null? (cdr alist))
(car alist))
(else
(last-pair (cdr alist))))) ;; call
在REPL中,呼叫站点的last-pair
仍然引用“外部”环境中的内置定义,因此此调用不是递归。如果是这种情况,REPL确实重新定义了内置函数,只是调用不是递归的。
我希望使用显式letrec
制作内部定义应该修复它,即使在REPL中输入时也是如此:
(define (last-pair alist)
(letrec ((last-pair (lambda (alist) ;; internal definition
(cond ((null? (cdr alist))
(car alist))
(else
(last-pair (cdr alist))))))) ;; recursive call
(last-pair alist))) ;; first call
因为第一个调用现在显式调用递归内部版本,位于letrec
形式内。或者也许它会以某种方式被搞砸,但如果它真的那么真的感到惊讶。 :)将没有内部定义的define
翻译成简单的lambda
形式是一回事;在明确的letrec
内搞乱是另一回事。
如果这确实有效,那就意味着Racket REPL将简单定义(如(define (f x) ...body...)
)转换为简单lambda
形式,(define f (lambda(x) ...body...))
,而不是letrec
形式,{{ 1}}。而且,Racket REPL中的(define f (letrec ((f (lambda(x) ...body...))) f))
不会改变全局环境中的旧绑定,但会在旧的阴影旧绑定之上添加一个新绑定。< / p>
这表明另一种在REPL“修复它”的方法 - define
:
set!