如何测试两个函数是否相同?
How to test if two functions are the same?
我在网上找到了一个代码片段somewhere:
(letrec
([id (lambda (v) v)]
[ctx0 (lambda (v) `(k ,v))]
.....
.....
(if (memq ctx (list ctx0 id)) <---- condition always return false
.....
其中 ctx 也是一个函数:
但是我永远无法使测试语句 return 为真。
然后我有以下测试:
(define ctx0 (lambda (v) `(k ,v)))
(define ctx1 (lambda (v) `(k ,v)))
(eq? ctx0 ctx1)
=> #f
(eqv? ctx0 ctx1)
=> #f
(equal? ctx0 ctx1)
=> #f
这让我怀疑两个函数总是不同的,因为它们有不同的内存位置。
但是如果函数可以与其他函数进行比较,我该如何测试两个函数是否相同?如果他们有不同的变量名怎么办?例如:
(lambda (x) (+ x 1))
和 (lambda (y) (+ y 1))
P.S。我使用 DrRacket 来测试代码。
你不能。 函数被视为不透明值:它们仅通过标识进行比较,仅此而已。 这是设计使然。
但是为什么?语言不能实现有意义的方法来比较有时可能有用的函数吗?嗯,不是真的,但有时不详细说明就很难明白为什么。让我们从你的问题中考虑你的例子——这两个函数似乎等价:
(define ctx0 (lambda (v) `(k ,v)))
(define ctx1 (lambda (v) `(k ,v)))
确实如此。但是比较这些函数的相等性会完成什么?毕竟,我们可以很容易地实现另一个功能:
(define ctx2 (lambda (w) `(k ,w)))
就所有意图和目的而言,此函数与前两个函数相同,但它无法通过简单的相等性检查!
为了判断两个值是否相等,我们必须定义一些定义相等性的算法。鉴于我目前提供的示例,这样的算法似乎很明显:当(且仅当)两个函数为 α-equivalent 时,它们才应被视为相等。有了这个,我们现在可以有意义地检查两个函数是否相等!
...对吧?
(define ctx3 (lambda (v) (list 'k v)))
呃,哦。这个函数做了完全相同的事情,但它的实现方式并不完全相同,所以它没有通过我们的相等性检查。当然,我们可以解决这个问题。准引用和使用 list
构造函数几乎相同,因此我们可以将它们定义为在大多数情况下是等价的。
(define ctx4 (lambda (v) (reverse (list v 'k))))
啊!这在操作上也是等价的,但它仍然不符合我们的等价算法。我们怎么可能使这项工作?
事实证明我们做不到,真的。函数是抽象单元 — 就其本质而言,我们 不需要知道它们是如何实现的,只需要知道它们做了什么。这意味着函数相等性实际上只能根据操作等价性来正确定义;也就是说,实现并不重要,只有行为才重要。
这在任何非平凡的语言中都是一个不可判定的问题。不可能确定任何两个函数在操作上是否等效,因为如果可以的话,我们可以解决 halting problem.
理论上,编程语言可以提供一种尽力而为的算法来确定函数等效性,也许使用 α 等效性或某种其他类型的度量。不幸的是,这真的没有用——依赖于函数的实现而不是它的行为来确定程序的语义违反了函数抽象的基本法则,因此任何依赖于这样一个系统的程序都是一个反模式。
函数相等性是一个非常诱人想要解决的问题,因为简单的情况看起来很容易,但大多数语言都采用了正确的方法,甚至不去尝试。这并不是说它不是一个有用的想法:如果可能的话,它会非常有用!但由于它不是,您将不得不使用不同的工具来完成这项工作。
从语义上讲,如果两个函数 f
和 g
对每个输入 一致 ,则它们相等,即如果对所有 x
,我们有 (= (f x) (g x))
。当然,对于 x
.
的 每个 可能的值,无法进行测试
如果您只想合理地确信(lambda (x) (+ x 1))
和(lambda (y) (+ y 1))
相同,那么您可以尝试断言
(map (lambda (x) (+ x 1)) [(-5) (-4) (-3) (-2) (-1) 0 1 2 3 4 5])
和
(map (lambda (y) (+ y 1)) [(-5) (-4) (-3) (-2) (-1) 0 1 2 3 4 5])
在你的单元测试中是一样的。
我在网上找到了一个代码片段somewhere:
(letrec
([id (lambda (v) v)]
[ctx0 (lambda (v) `(k ,v))]
.....
.....
(if (memq ctx (list ctx0 id)) <---- condition always return false
.....
其中 ctx 也是一个函数:
但是我永远无法使测试语句 return 为真。
然后我有以下测试:
(define ctx0 (lambda (v) `(k ,v)))
(define ctx1 (lambda (v) `(k ,v)))
(eq? ctx0 ctx1)
=> #f
(eqv? ctx0 ctx1)
=> #f
(equal? ctx0 ctx1)
=> #f
这让我怀疑两个函数总是不同的,因为它们有不同的内存位置。
但是如果函数可以与其他函数进行比较,我该如何测试两个函数是否相同?如果他们有不同的变量名怎么办?例如:
(lambda (x) (+ x 1))
和 (lambda (y) (+ y 1))
P.S。我使用 DrRacket 来测试代码。
你不能。 函数被视为不透明值:它们仅通过标识进行比较,仅此而已。 这是设计使然。
但是为什么?语言不能实现有意义的方法来比较有时可能有用的函数吗?嗯,不是真的,但有时不详细说明就很难明白为什么。让我们从你的问题中考虑你的例子——这两个函数似乎等价:
(define ctx0 (lambda (v) `(k ,v)))
(define ctx1 (lambda (v) `(k ,v)))
确实如此。但是比较这些函数的相等性会完成什么?毕竟,我们可以很容易地实现另一个功能:
(define ctx2 (lambda (w) `(k ,w)))
就所有意图和目的而言,此函数与前两个函数相同,但它无法通过简单的相等性检查!
为了判断两个值是否相等,我们必须定义一些定义相等性的算法。鉴于我目前提供的示例,这样的算法似乎很明显:当(且仅当)两个函数为 α-equivalent 时,它们才应被视为相等。有了这个,我们现在可以有意义地检查两个函数是否相等!
...对吧?
(define ctx3 (lambda (v) (list 'k v)))
呃,哦。这个函数做了完全相同的事情,但它的实现方式并不完全相同,所以它没有通过我们的相等性检查。当然,我们可以解决这个问题。准引用和使用 list
构造函数几乎相同,因此我们可以将它们定义为在大多数情况下是等价的。
(define ctx4 (lambda (v) (reverse (list v 'k))))
啊!这在操作上也是等价的,但它仍然不符合我们的等价算法。我们怎么可能使这项工作?
事实证明我们做不到,真的。函数是抽象单元 — 就其本质而言,我们 不需要知道它们是如何实现的,只需要知道它们做了什么。这意味着函数相等性实际上只能根据操作等价性来正确定义;也就是说,实现并不重要,只有行为才重要。
这在任何非平凡的语言中都是一个不可判定的问题。不可能确定任何两个函数在操作上是否等效,因为如果可以的话,我们可以解决 halting problem.
理论上,编程语言可以提供一种尽力而为的算法来确定函数等效性,也许使用 α 等效性或某种其他类型的度量。不幸的是,这真的没有用——依赖于函数的实现而不是它的行为来确定程序的语义违反了函数抽象的基本法则,因此任何依赖于这样一个系统的程序都是一个反模式。
函数相等性是一个非常诱人想要解决的问题,因为简单的情况看起来很容易,但大多数语言都采用了正确的方法,甚至不去尝试。这并不是说它不是一个有用的想法:如果可能的话,它会非常有用!但由于它不是,您将不得不使用不同的工具来完成这项工作。
从语义上讲,如果两个函数 f
和 g
对每个输入 一致 ,则它们相等,即如果对所有 x
,我们有 (= (f x) (g x))
。当然,对于 x
.
如果您只想合理地确信(lambda (x) (+ x 1))
和(lambda (y) (+ y 1))
相同,那么您可以尝试断言
(map (lambda (x) (+ x 1)) [(-5) (-4) (-3) (-2) (-1) 0 1 2 3 4 5])
和
(map (lambda (y) (+ y 1)) [(-5) (-4) (-3) (-2) (-1) 0 1 2 3 4 5])
在你的单元测试中是一样的。