【问题标题】:Procedure works only if defined twice程序仅在定义两次时才有效
【发布时间】:2018-01-23 18:49:39
【问题描述】:

我已经定义了一个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}

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

【问题讨论】:

  • 在 MIT-Scheme 中,您的代码运行良好,
  • @GeorgFuss 在 Racket 中运行它。不过很有趣。

标签: scheme r5rs


【解决方案1】:

我无法重现您的问题。具体来说,我在 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)))
    )
))

然后,在交互窗口中,我运行

> (map '(3 4 5) square)

...并得到结果:

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

您能否提供更多信息以帮助重现您的问题? (您对 mcons 的提及清楚地表明您正在使用命令行运行此代码,但我猜您加载和运行代码的方式有些有趣。)

编辑:好的,我现在可以通过将这些表达式一一粘贴到 REPL 中来重现类似的内容。显然我已经很久没有使用顶级 REPL 了。无论您的问题的答案如何,更高级别的答案是:不要将表达式粘贴到 REPL 中。用 Matthew Flatt(Racket 的主要实现者)的话来说,“顶层是没有希望的。”使用 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 的引用在map 处扩展/编译 指的是模块导入,而不是名为 map 的变量。到时候 定义被评估,来不及重定向map的含义 作为对导入的引用。

还有其他的选择,但我不认为会有这样的选择 最终明显更好或更一致。顶层是 无望。

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


此时,您可能会问自己应该如何与 Racket 进行交互。有几个不错的选择:

1) 使用 DrRacket。我不能高度推荐这个。 2) 使用命令行,使用“require”而不是“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)))
    )
))

然后,我启动球拍并 '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 即可避开所有这些问题。

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

【讨论】:

  • 我不会将这些行粘贴到 REPL 中。 :) 我以交互模式将文件加载到球拍中,然后只写我想在 REPL 中运行的表达式。这是我找到的测试代码的最简单方法。
  • 好的,我又添加了一堆文字。如果您还有其他问题,或者我没有回答您的问题,请告诉我!
  • 这确实回答了我的问题,但我已经决定重命名该程序。至少现在我明白了为什么这个错误非常模糊。另外,我不使用 load 语句,Racket 会自动从命令中导入文件。不确定我正在运行的确切命令是什么(我使用别名),我无法检查 atm。
  • 第二个语法(define (map l f) ...) 会改变行为吗?会使用明确的letrec 帮助吗?
【解决方案2】:

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

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

【讨论】:

  • 哦!这确实是问题所在。我没想到只是因为行为很奇怪。
  • R5RS 库函数只有在向后兼容原始函数时才能被覆盖。
  • 这里发生了其他事情。没有理由在定义中使用“地图”不在定义范围内。
  • @JohnClements 是的,这也让我感到困惑。但这似乎是唯一的解释。
  • @JeroenBollen 虽然我的回答基本上是对的,而且排在第一位,但你应该接受约翰的回答,因为它更彻底。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-02-08
  • 1970-01-01
  • 1970-01-01
  • 2012-05-03
  • 2021-12-28
  • 1970-01-01
相关资源
最近更新 更多