A short introduction to Higher-Order Logic Programming
Logic Programming in Prolog
Facts and Rules
%% A quick introduction to Prolog % Prolog is a programming language rooted in classical logic. % Its most distinguishing features are *search* and *unification* % A Prolog program consists of *predicates*. A predicate defines a relation between its arguments. % % A predicate has % - a name (atom) % - 0..n arguments that are Prolog terms % % A predicate consist of an arbitrary number of clauses. These denote /alternatives/: % If any clause is true, the whole predicate is true. % % In its simplest form, a predicate relates *atoms*: mentor(sokrates, plato). mentor(plato, aristotle). mentor(aristotle, alexander-the-great). % Terms related by a predicate may also be *variables*. % A variable starts with an upper-case letter or `_` (if the variable is unused) % % So far we've only seen *facts*. A fact is a clause with an empty body. % When the body is non-empty, the clause is called a *rule*. % A rule is a clause of the form % % Head :- Body. mortal(X) :- human(X). % In fact we can think of facts as special cases of rules, using the builtin 0-ary predicate `true`: mentor(parmenides, zeno) :- true. % is equivalent to % mentor(parmenides, zeno). % Interaction with a Prolog program is done via *queries*. % A query is a Prolog term that may contain variables. Variables in a query are existentially quantified. % % In response to a query, Prolog answers with a logically equivalent answer: % % ?- mentor(sokrates, plato). % true. % % ?- mentor(parmenides, X). % X = zeno. % It is worthwhile to examine what exactly Prolog means by a term like `X = zeno`: % `=` is a binary predicate that holds iff both its arguments can be *unified* % % ?- sokrates = plato. % false. % % ?- plato = X. % X = plato. % % If multiple clauses unify with a goal, the alternatives are tried on *backtracking* human(sokrates). human(plato). human(aristotle). % ?- mortal(X). % X = sokrates ; % X = plato ; % X = aristotle. % Predicates may also be recursive: influenced(X, Y) :- mentor(X,Y). influenced(X, Y) :- mentor(X, Z), influenced(Z, Y). % Note how variables in the body of a clause are existentially quantified. % ?- influenced(sokrates, X). % X = plato ; % X = aristotle ; % X = alexander-the-great. % A simple representation of directed graphs: adj(a,b). adj(b,c). adj(c,a). adj(c,d). path(X, Y) :- adj(X, Z), path(Z, Y). % Some additional examples append([], Y, Y). append([X | Xs], Ys, [X | Zs]) :- append(Xs, Ys, Zs). member(X, [X |_]). member(X, [_ | L]) :- member(X, L).
Resolution and Unification
%% Logical Foundations % ==================== % % Prolog is based on the logic of first-order horn clauses. % % A clause is a disjunction of literals (atoms their negations), e.g.: % % a ∨ b ∨ (¬ c) ∨ (¬ b) % % Equivalently in set notation: % % { a, b, ¬ c, ¬ b } % % A Prolog program is a conjunction of clauses, i.e. a formula in % Clause Normal Form (CNF). rainy. cold. snowy :- rainy, cold. % This Program can be translated into a formula in CNF: % We interpret `:-` as an implication ← % % rainy ∧ cold ∧ (snowy ← (rainy ∧ cold)) % We swap ← for → % rainy ∧ cold ∧ ((rainy ∧ cold) → snowy) % We apply material implication % rainy ∧ cold ∧ (¬ (rainy ∧ cold) ∨ snowy) % De Morgan % rainy ∧ cold ∧ (¬ rainy ∨ ¬ cold ∨ snowy) % We get a set of clauses: % 1. { ¬ rainy , ¬ cold , snowy } % 2. { rainy } % 3. { cold } % One very important property presents itself here: All clauses of a % Prolog program have at most one positive (non-negated) literal. % A clause that observes that property is called a horn clause. % % To evaluate a query ⇔ prove that a query is satisfiable relative to % a program, Prolog uses a resolution calculus. % % A resolution calculus is a proof calculus that works on formulae in CNF. % In its most basic form it looks like this: % % A ∨ C (¬ B) ∨ D σ = mgu(A,B) % ================================== % σ(C) ∨ σ(D) % % Horn clauses interact particulary nicely with this rule: % When two horn clauses are resolved, the resolvent is again a horn clause. % % An example proof: % We want to prove the goal % ?- snowy. % % Resolution is a refutation calculus. To prove a goal, we show that % its negation is unsatisfiable. % % We add the negated goal as a clause: % { ¬ snowy } % % Now we do resolution: % % { ¬ snowy } + { ¬ rainy, ¬ cold, snowy } ⇒ { ¬ rainy, ¬ cold } % { rainy } + { ¬ rainy, ¬ cold } ⇒ { ¬ cold } % { cold } + { ¬ cold } ⇒ ∅ % We have reached the empty clause, meaning our proof is done % Another example: % % Goal: % ?- mortal(X). % % Variables in a Query are existentially quantified. % That means our goal corresponds to the following formula: % % ∃ X. mortal(X) % % We need to negate and translate this into CNF: % % ¬ ∃ X. mortal(X) % [ V₁ / X ] ¬ mortal(X) % where V₁ ∉ free(mortal(X)) % ¬ mortal(V₁) % % We get a goal clause: % % { ¬ mortal(V₁) } % % Program Clauses: % % 1. ∀ X. human(X) → mortal(X) % ⇒ [ V₂ / X ] human(X) → mortal(X) % ⇒ human(V₂) → mortal(V₂) % ⇒ (¬ human(V₂)) ∨ mortal(V₂) % ̂= { ¬ human(X), mortal(X) } % 2. { human(sokrates) } % 3. { human(plato) } % 4. { human(aristotle) } % % Resolution: % % { ¬ mortal(V₁) } + { ¬ human(V₂), mortal(V₂) } [ V₂ / V₁ ] ⇒ { ¬ human(V₂) } % { ¬ human(V₂) } + { human(sokrates) } [ sokrates / V₂ ] ⇒ ∅ % % At this point, Prolog presents us with a satisfying substitution, namely % X = sokrates. % % If we force backtracking, we get all other satisfying substitutions: % { ¬ human(V₂) } + { human(plato) } [ plato / V₂ ] ⇒ ∅ % { ¬ human(V₂) } + { human(aristotle) } [ aristotle / V₂ ] ⇒ ∅ % % If we backtrack again we are left with a set of clauses that doesn't % allow us to derive the empty clause. % % { ¬ human(X) } % % Since nothing can be resolved, Prolog tells us that our goal is not satisfiable: % false.
Graduating to λProlog
Typed Logic Programming
% From Prolog to λProlog % λProlog adds some extensions to normal Prolog: % - polymorphic typing % - higher-order programming % - hypothetical reasoning % % That makes the logic corresponding to λProlog that of higher-order hereditary harrop formulae. %% Polymorphic Typing: kind person type. type sokrates person. type plato person. type zeno person. type aristotle person. pred mentor o:person o:person. % this is roughly equivalent to % `type mentor person -> person -> o.` % where `o` is the type of propositions mentor sokrates plato. mentor plato aristotle. pred influenced o:person o:person. influenced X Y :- mentor X Y. influenced X Y :- mentor X Z, influenced Z Y. pred age o:person o:int. age sokrates 70. age plato 23. age zeno 90. pred older o:person o:person. older X Y :- age X N, age Y M, N > M. kind node type. pred adj o:node o:node. pred path o:node o:node. path X Y :- adj X Z, path Z Y. % Type constructors are also allowed: kind mylist type -> type.
Higher-Order Programming
%% Higher-Order Programming: % % So far we've only seen a typed version of Prolog. % λProlog adds another term constructor: lambda abstractions % e.g. % % Id = x\ x pred mentor-f i:(person -> person) o:person. mentor-f F X :- mentor (F X) X. pred map o:(A -> B) o:list A o:list B. map _F nil nil. map F (X :: L) ((F X) :: L1) :- map F L L1. pred fold o:(A -> B -> B) o:list A o:B o:B. fold _F nil B B. fold F (X :: L) B (F X R) :- fold F L B R.
Hypothetical Reasoning
%% Hypothetical Reasoning % λProlog allows scoped assumptions % % `rule => body` adds `rule` to the top of the program, then runs `body`. % once `body` terminates `rule` is removed from the context % Concrete Syntax: kind lam type. type lam-abs string -> lam -> lam. type lam-app lam -> lam -> lam. type lam-var string -> lam. % de Bruijn: kind db type. type db-var int -> db. type db-abs db -> db. type db-app db -> db -> db. pred depth o:int o:string. pred lam->db o:lam i:int o:db. lam->db (lam-app L R) D (db-app L1 R1) :- lam->db L D L1, lam->db R D R1. lam->db (lam-abs V B) D (db-abs B1) :- D1 is D + 1, depth D V => lam->db B D1 B1. lam->db (lam-var V) D (db-var I) :- depth N V, I is D - N - 1.
Scoped Quantification
%% Scoped Quantification % λProlog adds the term constructors `pi` and `sigma`, for universal/existential quantification. % They are implemented as higher-order term constructors: % % pi x\ mentor sokrates x % % sigma y\ mentor y plato pred all i:(A -> o) i:list A. all _P nil. all P (X :: L) :- P X, all P L. pred some i:(A -> o) i:list A. some P (X :: _L) :- P X. some P (_X :: L) :- some P L. %%% Explicit Quantification: % All of the following are equivalent: path X Y :- adj X Z, path Z Y. path X Y :- sigma z\ adj X z, path z Y. pi x\ pi y\ (path x y :- sigma z\ adj x z, path z y). %%% HOAS kind tm, ty type. type app tm -> tm -> tm. % A lambda abstraction is just a function: type abs (tm -> tm) -> tm. type arrow ty -> ty -> ty. % The typing rules showcase higher-order unification: pred of i:tm o:ty. of (app M N) T :- of M (arrow U T), of N U. of (abs R) (arrow T U) :- pi x\ of x T => of (R x) U.
Elpi for Metaprogramming in Coq
Interacting with the Coq environment
(* Elpi as an embedded meta-programming language for Coq *) From elpi Require Import elpi. Elpi Program introspection "". (* We can query the coq environment from within elpi *) Elpi Query lp:{{ coq.locate "nat" GR, coq.locate "S" GRS }}. Definition x := 2. Elpi Query lp:{{ coq.locate "x" GR, coq.env.typeof GR Ty, GR = const C, coq.env.const C (some Body) TyC }}. (* We can also reference Coq Terms from elpi directly *) Elpi Query lp:{{ X = {{1}}, N = {{nat}} }}. (** HOAS of Gallina Terms *) (** `app` *) (* type app list term -> term *) Elpi Query lp:{{ X = app [{{S}}, {{O}}] }}. (** `fun` *) (* type fun name -> term -> (term -> term) -> term *) Definition foo := fun x => x + 1. Elpi Query lp:{{ coq.locate "foo" (const F), coq.env.const F (some Body) _ }}. Elpi Query lp:{{ fun `foo` T B = fun `bar` T B }}. (** `prod` *) (* type prod name -> term -> (term -> term) -> term *) Definition P := forall (n : nat), Prop. Elpi Query lp:{{ coq.locate "P" (const P), coq.env.const P (some Body) _ }}.
Embedded Logic Programming
(* * A loose reimplementation of the `Logic Programming` chapter of cpdt using coq-elpi. *) From elpi Require Import elpi. (* Addition as an inductive Property: *) Inductive plusR : nat -> nat -> nat -> Prop := | PlusO : forall m, plusR O m m | PlusS : forall n m r, plusR n m r -> plusR (S n) m (S r). (* Notice how this mirrors the definition in {λ-,}Prolog: *) Elpi Program plus " kind nat type. type zero nat. type succ nat -> nat. pred plus o:nat o:nat o:nat. plus zero M M. plus (succ N) M (succ R) :- plus N M R. ". Elpi Typecheck. Elpi Tactic add. Elpi Accumulate " pred add_p o:term o:term o:term o:term. add_p {{O}} N N {{PlusO lp:N}}. add_p {{S lp:N}} M {{S lp:P}} {{PlusS lp:N lp:M lp:P lp:SubProof}} :- add_p N M P SubProof. solve (goal _Ctx _ {{plusR lp:N lp:M lp:P}} _ _ as G) GS :- add_p N M P Proof, refine Proof G GS. solve _ _ :- coq.ltac.fail _ ""not an addition"". ". Elpi Typecheck. Goal exists x, plusR 4 x 7. eexists. elpi add. Qed. Goal exists x y, plusR x y 4. repeat eexists. elpi add. Qed. Require Import List. Import ListNotations. Section lengthR. Variable A : Set. Inductive lengthR : list A -> nat -> Prop := | lengthNil : lengthR nil O | lengthCons : forall X Xs N, lengthR Xs N -> lengthR (cons X Xs) (S N). End lengthR. Print lengthR. Elpi Tactic length. Elpi Accumulate " pred length i:term o:term o:term o:term. length A {{nil}} {{O}} {{lengthNil lp:A}}. length A {{cons lp:X lp:Xs}} {{S lp:N}} {{lengthCons lp:A lp:X lp:Xs lp:N lp:P}} :- length A Xs N P. solve (goal _Ctx _ {{lengthR lp:A lp:L lp:N}} _ _ as G) GS :- length A L N P, refine P G GS. solve _ _ :- coq.ltac.fail _ ""could not unify"". ". Elpi Typecheck. Elpi Query lp:{{ length {{nat}} L {{3}} P }}. Goal exists l, lengthR nat l 4. eexists. elpi length. Unshelve. all: constructor. Qed. Elpi Tactic length_choose. Elpi Accumulate " pred choose i:term o:term. choose {{nat}} {{O}}. choose {{nat}} {{S lp:N}} :- choose {{nat}} N. choose _T _ :- coq.ltac.fail _ ""Unsupported type "". pred length-choose i:term o:term o:term o:term. length-choose A {{nil}} {{O}} {{lengthNil lp:A}}. length-choose A {{cons lp:X lp:Xs}} {{S lp:N}} {{lengthCons lp:A lp:X lp:Xs lp:N lp:P}} :- choose A X, length-choose A Xs N P. solve (goal _Ctx _ {{lengthR lp:A lp:L lp:N}} _ _ as G) GS :- length-choose A L N P, refine P G GS. solve _ _ :- coq.ltac.fail _ ""could not unify"". ". Elpi Typecheck. Goal exists l, lengthR nat l 4. eexists. elpi length_choose. Qed. Section appendR. Variable A : Set. Inductive appendR : list A -> list A -> list A -> Prop := | appendNil : forall L, appendR nil L L | appendCons : forall X Xs Ys Zs, appendR Xs Ys Zs -> appendR (cons X Xs) Ys (cons X Zs). End appendR. Elpi Tactic append. Elpi Accumulate " pred append i:term o:term o:term o:term o:term. append A {{nil}} L L {{appendNil lp:A lp:L}}. append A {{cons lp:X lp:Xs}} Ys {{cons lp:X lp:Zs}} {{appendCons lp:A lp:X lp:Xs lp:Ys lp:Zs lp:P}} :- append A Xs Ys Zs P. solve (goal _Ctx _ {{appendR lp:A lp:Xs lp:Ys lp:Zs}} _ _ as G) GS :- append A Xs Ys Zs P, refine P G GS. solve _ _ :- coq.ltac.fail _ ""could not unify"". ". Elpi Typecheck. Goal forall L, appendR nat nil L L. intros. elpi append. Qed. Goal exists L, appendR nat [1 ; 2] L [1 ; 2 ; 3 ; 4]. eexists. elpi append. Qed. Goal exists L X, appendR nat L [X] ([1 ; 2 ; 3 ]) /\ X = 3. eexists. eexists. split. - elpi append. - reflexivity. Qed. Elpi Tactic length_append. Elpi Accumulate " pred append i:term o:term o:term o:term o:term. append A {{nil}} L L {{appendNil lp:A lp:L}}. append A {{cons lp:X lp:Xs}} Ys {{cons lp:X lp:Zs}} {{appendCons lp:A lp:X lp:Xs lp:Ys lp:Zs lp:P}} :- append A Xs Ys Zs P. pred length i:term o:term o:term o:term. length A {{nil}} {{O}} {{lengthNil lp:A}}. length A {{cons lp:X lp:Xs}} {{S lp:N}} {{lengthCons lp:A lp:X lp:Xs lp:N lp:P}} :- length A Xs N P. solve (goal _Ctx _ {{appendR lp:A lp:Xs lp:Ys lp:Zs}} _ _ as G) GS :- append A Xs Ys Zs P, refine P G GS. solve (goal _Ctx _ {{lengthR lp:A lp:L lp:N}} _ _ as G) GS :- length A L N P, refine P G GS. solve _ _ :- coq.ltac.fail _ ""could not unify"". ". Elpi Typecheck. Inductive exp : Set := | Const : nat -> exp | Var : exp | Plus : exp -> exp -> exp. Inductive eval (var : nat) : exp -> nat -> Prop := | EvalConst : forall n, eval var (Const n) n | EvalVar : eval var Var var | EvalPlus : forall e1 e2 n1 n2, eval var e1 n1 -> eval var e2 n2 -> eval var (Plus e1 e2) (n1 + n2). Elpi Tactic eval. Elpi Accumulate " pred eval o:term o:term o:term o:term. eval V {{Const lp:N}} N {{EvalConst lp:V lp:N}}. eval V {{Var}} V {{EvalVar lp:V}}. eval V {{Plus lp:E1 lp:E2}} {{lp:N1 + lp:N2}} {{EvalPlus lp:V lp:E1 lp:E2 lp:N1 lp:N2 lp:P1 lp:P2}} :- eval V E1 N1 P1, eval V E2 N2 P2. solve (goal _Ctx _ {{eval lp:V lp:T lp:N}} _ _ as G) GS :- eval V T N P, refine P G GS. ". Elpi Typecheck. Elpi Query lp:{{ eval V {{(Plus Var (Plus (Const 8) Var))}} {{(lp:V + (8 + lp:V))}} P }}. Example eval1 : forall var, eval var (Plus Var (Plus (Const 8) Var)) (var + (8 + var)). intros. elpi eval. Qed.