How to compare two functions for equivalence, as in (λx.2*x) == (λx.x+x)?

后端 未结 6 1692
忘掉有多难
忘掉有多难 2020-11-30 21:11

Is there a way to compare two functions for equality? For example, (λx.2*x) == (λx.x+x) should return true, because those are obviously equivalent.

相关标签:
6条回答
  • 2020-11-30 21:32

    2 years have passed, but I want to add a little remark to this question. Originally, I asked if there is any way to tell if (λx.2*x) is equal to (λx.x+x). Addition and multiplication on the λ-calculus can be defined as:

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

    Now, if you normalize the following terms:

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

    You get:

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

    For both programs. Since their normal forms are equal, both programs are obviously equal. While this doesn't work in general, it does work for many terms in practice. (λx.(mul 2 (mul 3 x)) and (λx.(mul 6 x)) both have the same normal forms, for example.

    0 讨论(0)
  • 2020-11-30 21:35

    This is undecidable in general, but for a suitable subset, you can indeed do it today effectively using SMT solvers:

    $ 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
    

    For details, see: https://hackage.haskell.org/package/sbv

    0 讨论(0)
  • 2020-11-30 21:35

    Proving two functions equal is undecidable in general but one can still prove functional equality in special cases as in your question.

    Here's a sample proof in Lean

    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
    

    One can do the same in other dependently typed language such as Coq, Agda, Idris.

    The above is a tactic style proof. The actual definition of foo (the proof) that gets generated is quite a mouthful to be written by hand:

    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))))))
    
    0 讨论(0)
  • 2020-11-30 21:41

    It's pretty well-known that general function equality is undecidable in general, so you'll have to pick a subset of the problem that you're interested in. You might consider some of these partial solutions:

    • Presburger arithmetic is a decidable fragment of first-order logic + arithmetic.
    • The universe package offers function equality tests for total functions with finite domain.
    • You can check that your functions are equal on a whole bunch of inputs and treat that as evidence for equality on the untested inputs; check out QuickCheck.
    • SMT solvers make a best effort, sometimes responding "don't know" instead of "equal" or "not equal". There are several bindings to SMT solvers on Hackage; I don't have enough experience to suggest a best one, but Thomas M. DuBuisson suggests sbv.
    • There's a fun line of research on deciding function equality and other things on compact functions; the basics of this research is described in the blog post Seemingly impossible functional programs. (Note that compactness is a very strong and very subtle condition! It's not one that most Haskell functions satisfy.)
    • If you know your functions are linear, you can find a basis for the source space; then every function has a unique matrix representation.
    • You could attempt to define your own expression language, prove that equivalence is decidable for this language, and then embed that language in Haskell. This is the most flexible but also the most difficult way to make progress.
    0 讨论(0)
  • 2020-11-30 21:56

    In addition to practical examples given in the other answer, let us pick the subset of functions expressible in typed lambda calculus; we can also allow product and sum types. Although checking whether two functions are equal can be as simple as applying them to a variable and comparing results, we cannot build the equality function within the programming language itself.

    ETA: λProlog is a logic programming language for manipulating (typed lambda calculus) functions.

    0 讨论(0)
  • 2020-11-30 21:58

    In a language with symbolic computation like Mathematica:

    Or C# with a computer algebra library:

    MathObject f(MathObject x) => x + x;
    MathObject g(MathObject x) => 2 * x;
    
    {
        var x = new Symbol("x");
    
        Console.WriteLine(f(x) == g(x));
    }
    

    The above displays 'True' at the console.

    0 讨论(0)
提交回复
热议问题