程序仅在定义两次时才有效

Procedure works only if defined twice

我定义了一个 map 过程和一个 square 过程。 square 过程工作正常,但 map 过程只有在定义两次时才有效。

给定以下代码:

; Squares a number.
(define square (lambda (n)
    (* n n)
))


; Applies function f on all elements of l.
(define map (lambda (l f)
    (cond
        ((null? l) '())
        (else (cons (f (car l)) (map (cdr l) f)))
    )
))

此程序在执行时崩溃:

> (map '(1 2 3) square)
; mcar: contract violation
;   expected: mpair?
;   given: #<procedure:square>
; [,bt for context]

然而,给定以下代码,该程序按预期运行。唯一的区别是 map 现在被定义了两次。

; Squares a number.
(define square (lambda (n)
    (* n n)
))


; Applies function f on all elements of l.
(define map (lambda (l f)
    (cond
        ((null? l) '())
        (else (cons (f (car l)) (map (cdr l) f)))
    )
))

(define map (lambda (l f)
    (cond
        ((null? l) '())
        (else (cons (f (car l)) (map (cdr l) f)))
    )
))

这个版本工作正常:

> (map '(1 2 3) square)
{1 4 9}

是什么导致了这个问题,应该如何解决?

map 是一个标准的 Scheme 函数。当您第一次定义该函数时,它显然是在尝试调用标准函数,而不是您对它的重新定义。由于标准函数以相反的顺序接受参数 (map function list),因此会出错。第二次定义它时,它会在环境中找到你的功能,所以一切正常。

最好的解决方案是使用不与标准函数冲突的不同名称。

我无法重现您的问题。具体来说,我在 DrRacket 中 运行 这个程序:

#lang r5rs

; Squares a number.
(define square (lambda (n)
    (* n n)
))


; Applies function f on all elements of l.
(define map (lambda (l f)
    (cond
        ((null? l) '())
        (else (cons (f (car l)) (map (cdr l) f)))
    )
))

然后,在互动中 window,我 运行

> (map '(3 4 5) square)

...并得到结果:

(mcons 9 (mcons 16 (mcons 25 '())))

您能否提供更多信息以帮助重现您的问题? (你提到 mcons 清楚地表明你正在 运行 使用 racket,使用命令行来编写这段代码,但我猜你的加载方式和 运行宁你的代码。)

编辑:好的,我现在可以通过将这些表达式一个一个地粘贴到 REPL 中来重现这样的东西。显然已经很久没有使用顶级REPL了。无论您的问题的答案如何,更高级别的答案是:不要将表达式粘贴到 REPL 中。用 Matthew Flatt(Racket 的主要实现者)的话来说,"The top level is hopeless." 使用 DrRacket 是解决这个问题的最简单方法。

编辑 2:如我所料,TL; DR:1)顶层是没有希望的。 2) 将所有代码放入模块中。

我在 Racket-Users 列表的帖子中总结了一些这种混淆。具体来说,基本问题:绑定的右侧如何不在绑定本身的范围内?

马修的回答摘录如下:


这就是问题的本质。哪些东西在a的范围内 顶级定义?

例如,在 f 的绑定范围内引用 f

(define (g x) (f x))
(define (f x) x)

?

怎么样
(begin
  (define (g x) (f x))
  (define (f x) x))

或在

(expand '(define (f x) x))
(define (g x) (f x))

(begin
 (expand '(define (f x) x))
 (define (g x) (f x)))

?

Racket 中的规则是顶级 define 不会改变 标识符的绑定,直到 define 被评估。所以,在

(define (map x) ... map ...)

map 的引用是 expanded/compiled 在 map 的位置 指的是模块导入,而不是名为 map 的变量。到时候 定义被评估,重定向 map 的含义为时已晚 作为对导入的引用。

还有其他选择,但我认为没有其他选择 最终变得更好或更一致。顶层是 没希望了。

模块的表现明显更好,部分原因是 a 的范围 定义很明确:从模块的开始到结束。


此时,您可能会振振有词地问 应该如何与 Racket 交互。有几个不错的选择:

1) 使用 DrRacket。我怎么推荐都不为过。 2) 使用命令行和 "require" 而不是 "load"。 "load",不要把它说得太细,是该语言旧版本的半损坏保留。

所以,例如,我可以将这段代码放在一个名为 'a.rkt':

的文件中
#lang r5rs

; Squares a number.
(define square (lambda (n)
    (* n n)
))


; Applies function f on all elements of l.
(define map (lambda (l f)
    (cond
        ((null? l) '())
        (else (cons (f (car l)) (map (cdr l) f)))
    )
))

然后,我启动racket和'require'这个模块,然后用',enter'进入模块:

hardy:/tmp clements> racket
Welcome to Racket v6.11.0.6.
> (require "a.rkt")
> ,enter "a.rkt"
"a.rkt"> (map '(3 4 5) square)
(mcons 9 (mcons 16 (mcons 25 '())))
"a.rkt"> 

让我再重复一遍,您只需使用 DrRacket 就可以避免所有这些问题。

所以...我今天学到了很多东西!谢谢!