如何比较两个函数的等效性,如(λx.2* x)==(λx.x+ x)?


72

有没有办法比较两个函数的相等性?例如,(λx.2*x) == (λx.x+x)应该返回true,因为它们显然是等效的。


2
您真的需要算术函数还是只是对比较函数感到好奇?在后一种情况下,请查看类型化的λ结石中的归一化。
lukstafi 2013年

@lukstafi很好奇,但我来看看。
MaiaVictor 2013年

7
您的连接词“ but”不合适,而应该是“ so”。;-)
lukstafi 2013年

1
@lukstafi你是对的。
MaiaVictor

1
@IvanCastellanos听起来不错,直到您想证明两个二进制函数的等效性,然后突然有40亿个域大小变成16位数,而您以前的1分钟测试套件变成了10000年的测试套件。
Daniel Wagner

Answers:


124

众所周知,一般的函数相等性通常是不确定的,因此您必须选择感兴趣的问题的子集。您可能会考虑以下部分解决方案:

  • Presburger算术是一阶逻辑+算术的可确定片段。
  • 宇宙包提供功能有限域总功能相等测试。
  • 您可以检查一堆输入上的函数是否相等,并将其视为未测试输入是否相等的证据。查看QuickCheck
  • SMT求解器会尽力而为,有时会回答“不知道”,而不是“相等”或“不相等”。在Hackage上有多个SMT求解器的绑定;我没有足够的经验,建议最好之一,但托马斯M DuBuisson暗示SBV
  • 关于确定函数相等性和紧凑函数的其他事情,有很多有趣的研究。博客文章中似乎不可能的功能程序描述了该研究的基础。(请注意,紧致性是一种非常强大且非常微妙的条件!这不是大多数Haskell函数所满足的条件。)
  • 如果您知道函数是线性的,则可以找到源空间的基础;那么每个函数都有一个唯一的矩阵表示。
  • 您可以尝试定义自己的表达语言,证明该语言的等效性是可决定的,然后将该语言嵌入Haskell。这是取得进展的最灵活但也是最困难的方法。

8
您确定他不只是在寻找sbv还是quickcheck?使用SBV:prove $ \(x::SInt32) -> 2*x .== x + x成绩为Q.E.D.
Thomas M. DuBuisson 2013年

@ ThomasM.DuBuisson很棒的建议!我将其添加到答案中。
Daniel Wagner 2013年

我实际上是在寻找更深入的问题概述,而这正是Daniel所提供的。
MaiaVictor

42

通常这是无法确定的,但是对于合适的子集,您今天确实可以使用SMT求解器有效地做到这一点:

$ ghci
GHCi, version 8.0.1: http://www.haskell.org/ghc/  :? for help
Prelude> :m Data.SBV
Prelude Data.SBV> (\x ->  2 * x) === (\x -> x + x :: SInteger)
Q.E.D.
Prelude Data.SBV> (\x ->  2 * x) === (\x -> 1 + x + x :: SInteger)
Falsifiable. Counter-example:
  s0 = 0 :: Integer

有关详细信息,请参见:https : //hackage.haskell.org/package/sbv


11

除了在其他答案中给出的实际示例之外,让我们选择在类型化lambda演算中可表达的函数的子集;我们还可以允许乘积和总和类型。尽管检查两个函数是否相等就像将它们应用于变量并比较结果一样简单,但是我们不能在编程语言本身内建立相等函数。

ETA:λProlog是用于操纵(键入的lambda演算)函数的逻辑编程语言。


1
您说:“检查两个函数是否相等可以像将它们应用于变量并比较结果一样简单”。不过,我很难相信这一点。举一个简单的例子,这真的可以证明平等(\x -> 2*x) == (\x -> x*2)吗?
丹尼尔·瓦格纳

“(\ x-> 2 * x)==(\ x-> x * 2)”不一定是正确的,它取决于您如何解释“ *”和“ 2”。例如,您可以将地面术语上的“ ==”定义为对某些术语重写系统取模的身份。
lukstafi '16

9

2年过去了,但是我想对这个问题再说一点。最初,我问是否有任何方法可以确定是否(λx.2*x)等于(λx.x+x)。可以将λ微积分上的加法和乘法定义为:

add = (a b c -> (a b (a b c)))
mul = (a b c -> (a (b c)))

现在,如果您将以下术语归一化:

add_x_x = (λx . (add x x))
mul_x_2 = (mul (λf x . (f (f x)))

你得到:

result = (a b c -> (a b (a b c)))

对于这两个程序。由于它们的正规形式相等,因此两个程序显然相等。尽管这通常不起作用,但实际上它在许多方面都起作用。(λx.(mul 2 (mul 3 x))例如,(λx.(mul 6 x))它们都具有相同的范式。


1
有一个名为“supercompilation”技术(我推荐这个纸)。我猜一个成熟的超级编译器可以统一您的功能,即使它们是通过递归和模式匹配定义的。
user3237465 2015年

1
@ user3237465提供的链接不再起作用。该研究论文可在这里找到:academia.edu/2718995/Rethinking_supercompilation
Stephane Rolland

4年过去了,我想补充一点:虽然这种情况在这种情况下可行,但这种情况在大多数情况下是例外。函数可以以完全不同的方式定义,并且仍然等效,因此,手动操作等式的方法很有用。
MaiaVictor

3

在像Mathematica这样的具有符号计算的语言中:

在此处输入图片说明

或带有计算机代数库的C#:

MathObject f(MathObject x) => x + x;
MathObject g(MathObject x) => 2 * x;

{
    var x = new Symbol("x");

    Console.WriteLine(f(x) == g(x));
}

上面在控制台上显示“ True”。


但是,尽管如此(x \[Function] x + x) == (y \[Function] 2 y),它甚至没有尝试。
tfb

0

证明两个函数相等通常是不可确定的,但是在特殊情况下仍然可以证明一个函数相等。

这是精益中的样本证明

def foo : (λ x, 2 * x) = (λ x, x + x) :=
begin
  apply funext, intro x,
  cases x,
  { refl },
  { simp,
    dsimp [has_mul.mul, nat.mul],
    have zz : ∀ a : nat, 0 + a = a := by simp,
    rw zz }
end

一个人可以用其他依赖类型的语言(例如Coq,Agda,Idris)执行相同的操作。

以上是战术风格的证明。所foo生成的(证明)的实际定义相当繁琐,可用手写:

def foo : (λ (x : ℕ), 2 * x) = λ (x : ℕ), x + x :=
funext
  (λ (x : ℕ),
     nat.cases_on x (eq.refl (2 * 0))
       (λ (a : ℕ),
          eq.mpr
            (id_locked
               ((λ (a a_1 : ℕ) (e_1 : a = a_1) (a_2 a_3 : ℕ) (e_2 : a_2 = a_3), congr (congr_arg eq e_1) e_2)
                  (2 * nat.succ a)
                  (nat.succ a * 2)
                  (mul_comm 2 (nat.succ a))
                  (nat.succ a + nat.succ a)
                  (nat.succ a + nat.succ a)
                  (eq.refl (nat.succ a + nat.succ a))))
            (id_locked
               (eq.mpr
                  (id_locked
                     (eq.rec (eq.refl (0 + nat.succ a + nat.succ a = nat.succ a + nat.succ a))
                        (eq.mpr
                           (id_locked
                              (eq.trans
                                 (forall_congr_eq
                                    (λ (a : ℕ),
                                       eq.trans
                                         ((λ (a a_1 : ℕ) (e_1 : a = a_1) (a_2 a_3 : ℕ) (e_2 : a_2 = a_3),
                                             congr (congr_arg eq e_1) e_2)
                                            (0 + a)
                                            a
                                            (zero_add a)
                                            a
                                            a
                                            (eq.refl a))
                                         (propext (eq_self_iff_true a))))
                                 (propext (implies_true_iff ℕ))))
                           trivial
                           (nat.succ a))))
                  (eq.refl (nat.succ a + nat.succ a))))))
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.