web-dev-qa-db-fra.com

Comment comparer deux fonctions d'équivalence, comme dans (λx.2 * x) == (λx.x + x)?

Existe-t-il un moyen de comparer deux fonctions pour l'égalité? Par exemple, (λx.2*x) == (λx.x+x) devrait retourner vrai, car ceux-ci sont évidemment équivalents.

71
MaiaVictor

Il est assez bien connu que l'égalité générale des fonctions est indécidable en général, vous devrez donc choisir un sous-ensemble du problème qui vous intéresse. Vous pourriez envisager certaines de ces solutions partielles:

  • arithmétique Presburger est un fragment décidable de logique + arithmétique de premier ordre.
  • Le package nivers propose des tests d'égalité de fonction pour les fonctions totales à domaine fini.
  • Vous pouvez vérifier que vos fonctions sont égales sur tout un tas d'entrées et les traiter comme une preuve d'égalité sur les entrées non testées; consultez QuickCheck .
  • Les solveurs SMT font de leur mieux, répondant parfois "ne sait pas" au lieu de "égal" ou "non égal". Il existe plusieurs liaisons aux solveurs SMT sur Hackage; Je n'ai pas assez d'expérience pour en proposer un meilleur, mais Thomas M. DuBuisson suggère sbv .
  • Il existe une ligne de recherche amusante sur la décision de l'égalité des fonctions et d'autres choses sur les fonctions compactes; les bases de cette recherche sont décrites dans le blog Programmes fonctionnels apparemment impossibles . (Notez que la compacité est une condition très forte et très subtile! Ce n'est pas une condition que la plupart des fonctions Haskell satisfont.)
  • Si vous savez que vos fonctions sont linéaires, vous pouvez trouver une base pour l'espace source; alors chaque fonction a une représentation matricielle unique.
  • Vous pouvez essayer de définir votre propre langage d'expression, prouver que l'équivalence est décidable pour ce langage, puis intégrer ce langage dans Haskell. C'est le moyen le plus flexible mais aussi le plus difficile de progresser.
124
Daniel Wagner

C'est indécidable en général, mais pour un sous-ensemble approprié, vous pouvez en effet le faire aujourd'hui efficacement en utilisant des solveurs 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

Pour plus de détails, voir: https://hackage.haskell.org/package/sbv

42
alias

En plus des exemples pratiques donnés dans l'autre réponse, prenons le sous-ensemble de fonctions exprimables en calcul lambda typé; nous pouvons également autoriser les types de produits et de sommes. Bien que vérifier si deux fonctions sont égales peut être aussi simple que les appliquer à une variable et comparer les résultats , nous ne pouvons pas construire la fonction d'égalité dans le langage de programmation lui-même .

ETA: λProlog est un langage de programmation logique pour la manipulation de fonctions (calcul lambda typé).

11
lukstafi

2 ans se sont écoulés, mais je veux ajouter une petite remarque à cette question. À l'origine, j'ai demandé s'il y avait un moyen de savoir si (λx.2*x) est égal à (λx.x+x). L'addition et la multiplication sur le λ-calcul peuvent être définies comme:

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

Maintenant, si vous normalisez les termes suivants:

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

Vous recevez:

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

Pour les deux programmes. Puisque leurs formes normales sont égales, les deux programmes sont évidemment égaux. Bien que cela ne fonctionne pas en général, cela fonctionne pour de nombreux termes dans la pratique. (λx.(mul 2 (mul 3 x)) et (λx.(mul 6 x)) les deux ont par exemple les mêmes formes normales.

9
MaiaVictor

Dans un langage à calcul symbolique comme Mathematica:

enter image description here

Ou C # avec une bibliothèque d'algèbre informatique :

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

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

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

Ce qui précède affiche "True" sur la console.

2
dharmatech

Prouver deux fonctions égales est indécidable en général mais on peut toujours prouver l'égalité fonctionnelle dans des cas particuliers comme dans votre question.

Voici un exemple de preuve en 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

On peut faire de même dans d'autres langages typés dépendants tels que Coq, Agda, Idris.

Ce qui précède est une preuve de style tactique. La définition réelle de foo (la preuve) qui est générée est une bouchée à écrire à la main:

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
Slavomir Kaslev