Transcript

A Generic Theorem Prover Construction Framework∗

Justin Ward†

Department of ComputerScience

The University of Toronto

[email protected]

Garrin KimmellInformation and

TelecommunicationTechnology Center

The University of Kansas

[email protected]

Perry AlexanderInformation and

TelecommunicationTechnology Center

The University of Kansas

[email protected]

ABSTRACTFormal software engineering methodologies provide a vastarray of languages for specifying correctness properties andassociated tools for property verification. Unfortunately, theimplementation of each verification tool requires an earlycommitment to a particular methodology and language, interms of both high-level semantic concerns and low-level syn-tactic representation of properties and proofs. In this paper,we present Prufrock, a novel approach to constructing auto-mated reasoning systems, which abstracts semantic concernsover entire classes of potential object languages. Prufrockis a modular, generic prover framework written in Haskelltaking advantage of its type class system. It consists of aset of independent logic modules defining the semantics re-quired for proof over entire classes of abstract syntaxes usinggeneric programming techniques. The fundamental contri-bution of Prufrock is that any object language may be usedfor specifying and verifying properties, as long as it providesa semantics consistent with the logic modules required for aproof. The implementation details of the reasoning systemthus remain independent from the structure of the specifica-tion language. This facilitates large-scale reuse of logics aswell as tacticals and proofs themselves when constructing orretargeting automated reasoning tools. At the same time,Prufrock aids in closing the gap between an object languageand verification of objects written in it by operating on itsabstract syntax directly rather than transforming it into aseparate theorem proving language.

1. INTRODUCTIONVerification tools, such as theorem provers, perform theirtasks by operating directly on the internal abstract syntaxof an object language. This syntax encodes the theorems,

∗This material is based upon work supported by theUnited States National Science Foundation under GrantNos. 0209193 and 0350425†This work was completed while a student in the System-Level Design Group at The University of Kansas

properties, and axioms used in the verification process. In-ference and proof are carried out by manipulating this syn-tax in a logically sound fashion. Unfortunately, this ap-proach can be a serious hindrance to modularity and reusedue to the tight coupling between abstract syntax and theproof techniques used to manipulate it.

Users of a given verification tool must either formulate theirspecification of theorems and properties in a syntax com-patible with target verification tools, or convert existingspecifications to a syntax suitable for the tool. The formerapproach results in serious constraints on the formal speci-fication language that may be used, while the latter bringsabout the risk that the translation of a specification may notcorrectly capture all of its information. Even if a specifica-tion’s soundness is preserved, the user faces the frustratingtask of correlating the verification tool’s output, presentedin terms of its own abstract syntax, with the original speci-fication.

While verifying the correctness of a given software systemremains the primary concern throughout the formalized de-velopment process, it intersects with a myriad of troublingpragmatic concerns related to the methodology. Developerswant a formalized logic that accurately captures the seman-tics of the software system. This initial choice of logic repre-sents a serious commitment that will reverberate throughoutthe entire development process. Unfortunately, this choiceis dependent not only on the suitability of a given specifi-cation language, but also on the existence of adequate toolsfor that language.

The magnitude of these language and tool problems in-creases for the designers of specification languages. A givenlanguage’s utility and overall acceptance is severely ham-pered by the lack of viable tools to assist in its use. Thisleaves the designer of a specification language with a choicesimilar to that its user: either write a new suite of toolscompatible with the language, thus bringing new tools tothe language, or provide a means to translate the languageinto a form accepted by existing tools, thus bringing the lan-guage to existing tools. Writing tools requires a great dealof work, much of which requires merely reworking existingproof and verification strategies in a form compatible withthe new language’s basic structure. The converse approach,which involves translating the language into the acceptedstructure of existing tools, may result in the loss of manyfeatures that made the language novel in the first place.

Verification tool implementers are not free from this mire ofconcerns, either. The typical theorem prover is a tightly cou-pled fusion of a variety of design tasks. A standard abstractsyntax is required for representing logical data throughoutthe proof process. Additionally, the proof process usually re-quires a fair amount of bookkeeping and storage that mustbe managed in a consistent fashion. Inference rules are thenformulated within the overarching constraints imposed bythe proof language and the proof execution system.

Our own work in tool development for heterogeneous spec-ification languages provides pragmatic motivation for thePrufrock approach. As examples we cite verification envi-ronments for VSPEC [5], ActiveSPEC [6], and most recentlyRosetta [4]. The VSPEC and ActiveSPEC analysis envi-ronment used a collection of third-party verification toolsincluding PVS [21] and PDL [26]. The vast majority ofour development effort went into the development of trans-formers for VSPEC and ActiveSPEC syntax to PVS andPDL, and largely futile attempts to move verifications re-sults from the object languages back to the specificationlanguages. The latter is necessary to enable users to under-stand and respond to specification issues discovered duringverification. Tools for synthesizing test vectors using SATsolvers [1] and deriving component features using PVS [18]from Rosetta faced similar problems.

2. SEPARATING INFERENCE FROM SYN-TAX

The key to solving the problems associated with retargetingand reusing verification tools is separating inference fromsyntax. This would allow implementation of an inferencesystem and moving it from syntax to syntax. To under-stand this goal, consider a mathematical text in logic. Mosttexts spend a chapter defining the notation and terminologyused throughout, and then assume the reader will mentallytranslate displayed formulae into meaningful logical objects.A particular text’s notation does not, of course, directly af-fect the soundness or basic essence of laws and propertiesgoverning these operations. A successful student can takesemantic information from the text and apply it across dif-ferent logic syntaxes. All that is strictly necessary for asound logical system is a symbolic notation that is bothconsistent and sufficiently detailed to capture and conveythe necessary logical semantics being described by laws andproperties.

Isabelle [22] attempts to solve a similar problem by unifyingthe syntax for proof across logical systems. Isabelle providesan advanced modular prover construction system, requiresthat proofs are stored in a predetermined abstract syntax.As a system for specifying proof systems, Isabelle provides acommon syntax for representing varied logical systems. Thesame syntax is used to specify many different logics andinference systems. Thus, when external tools target Isabellefor there verification models, they are locked into a specificsyntax for proofs and specifications.

While Isabelle defines a common syntax over varied logi-cal systems, Prufrock takes an approach that defines logicalsystems in the form of generic inference rules across variedsyntaxes. We eschew the requirement that properties andmodels are written in a language predetermined early in the

development of the verification tool, and replace it with alooser, more abstract requirement that the tool be capableof dealing with any language so long as it provides an appro-priate logical semantics. We need not be concerned with theexact representation of conjunction in abstract syntax, forexample, so much as we must know that some consistentrepresentation of conjunction exists. After specifying thebare representational requirements for a particular logic, wemay abstract common inference rules and proof techniquesover the entire class of abstract syntaxes providing such rep-resentations.

As an example, consider one of DeMorgan’s laws, ¬(a ∧b) ⇐⇒ ¬a ∨ ¬b. This law could be understood (and putto good use in a traditional automated reasoning system)as a rule for rearranging the symbols ¬, ∨, and ∧, and theexpressions a and b, given that they satisfy an appropriateinitial configuration. However, most logicians will interpretthis statement in more abstract, semantic terms, such as:“the negation of a conjunction of two terms is equivalentto the disjunction of the negations those terms.” Syntaxesfor representing terms, negations, disjunctions and conjunc-tions may vary, but a good logician will map the semantics ofDeMorgan’s laws onto alternative syntaxes as needed. In anutshell, this defines the role Prufrock plays in tool develop-ment – defining semantics that are portable across syntacticsystems.

3. POLYTYPIC PROGRAMMINGDiscussing this separation of syntax and inference is easyfrom a purely theoretical perspective, but we need an actualimplementation of the system we need. To do this, we re-quire a means for abstracting operations on structures fromthe particular structures themselves. This means is suc-cinctly provided through generic or polytypic programming.Polytypic programming [12] is an approach to implementa-tion that separates common operations, such as the map andfold , from the exact structure of the data type over whichthey operate. While map serves as hylomorphism for lists,a polytypic map serves as hylomorphism for an entire classof structures. This is made possible by defining map over amore general set of functions, which may then be instanti-ated for each type over which we wish to use map. Standardpolytypic approaches can derive these functions automati-cally through induction on the structure of datatypes. At abasic conceptual level, we may say that polytypic program-ming is concerned primarily with with a type’s shape thanits exact structure.

The Haskell programming language [24] supports a varietyof extensions that aid in polytypic programming and similartechniques. Jansson and Jeuring’s PolyP language exten-sion facilitates the construction of generic functions throughanalysis of datatypes’ shapes [9, 20]. A similar effect isachieved in Generic Haskell [8]. The “Scrap your boiler-plate” series of papers [15, 16, 17] uses a lightweight ap-proach to generic traversals using dynamic type casts andHaskell’s type class system.

While most polytypic programming systems use datatypes’structural information to automate the formulation of poly-typic functions, another common approach involves defin-ing a set of type classes that provide a common interface.

Polytypic functions then simply program to this interface.This technique has been used by Jansson and Jeuring inthe development of polytypic unification [10] and rewritingsystems [11].

Prufrock is based upon type classes that provide function-ality similar to those already utilized for unification andrewriting. An important difference exists between the tech-niques that we shall present and those commonly consideredpolytypic programming. As we have mentioned, most extantpolytypic programming frameworks are built upon abstrac-tion over shapes of datatypes. This allows many of the req-uisite instances may be derived automatically by inductionover datatypes. In our case, the class-centered programmingtechniques remain the same, but the functions we are defin-ing provide a logical semantics for a given class of type. Suchsemantics are not generally apparent from the structure ofa type alone, but depend rather upon the interpretation ofsome structure.

This is visible in the PropA data type defined below. Theshape of the PropA type is, using the notation of PolyP,(Rec∗Rec)+(Rec∗Rec)+Rec+(Con String). The construc-tors AndA and OrA generate the first two elements of thesum, respectively. However, the shape of both of these con-structors is the same. The only way to distinguish the two(and in turn, provide the appropriate semantics for logicalconjunction and disjunction), is by position in the sum. Thisis not generally feasible using polytypic techniques, so theuser must manually specify instances for most type classes.

To illustrate the techniques that are used in our proverframework, we shall consider our example of DeMorgan’slaw again. A naıve, monotypic function for rewriting theleft side of our rule to the right side might look similar tofigure 1, given the associated abstract syntax for represent-ing propositions. The syntactic manipulations may be re-moved, and replaced with a set of abstract functions, whichare defined for the datatype and then passed as parametersto the new rule, as shown in figure 2.

data PropA = AndA PropA PropA |OrA PropA PropA |NegA PropA |VarA String

deMorgans :: PropA→ PropAdeMorgans (NegA p) =

case p of(AndA p1 p2 )→ (OrA (NotA p1 ) (NotA p2 ))otherwise → (NegA p)

deMorgans p = p

Figure 1: A naıve implementation of DeMorgan’slaw

The resulting function, deMorgans2 is clumsy at best. Whileit is significantly more polymorphic than deMorgans, accept-ing any type a as input, it also requires six function argu-ments. We can simplify this implementation using Haskell’stype class system. The type class system allows us to as-sociate a set of functions with a class of types, and providespecific instances for a given type when instantiating the

class. The correct functions for a type will be automaticallyselected statically by the Haskell type system. The resultingtype class and rule is shown in figure 3. Note that the typesignature of deMorgans3 specifies that the polymorphic typea must be a member of the DeMorgan class, ensuring thatfunctions defined in the DeMorgan class also exist for a.

class DeMorgan a whereisNeg :: a → BoolisAnd :: a → BoolmkOr :: a → a → amkNeg :: a → afstArg :: a → asndArg :: a → a

deMorgans3 :: (DeMorgan a)⇒ a → adeMorgans3 expr =

if (isNeg expr)then let inner = (firstArg expr)

in if (isAnd inner)then mkOr (mkNeg (firstArg inner))

(mkNeg (secondArg inner))else expr

else expr

Figure 3: deMorgan’s law with type classes

We can then use this rule on our initial abstract syntaxfrom figure 1 by declaring an appropriate instance of theDeMorgan class for the PropA datatype, as shown in fig-ure 4. While we have significantly increased the amount ofcode required to implement a very simple rule, we have alsocompletely separated the structure of the datatype repre-senting terms from all necessary operations on terms. If wedeclare a new datatype, PropB and provide an instance forDeMorgan for it, as shown in figure 5, we can reuse our rulefor DeMorgan’s law without redefining it over the new syn-tax. In this fashion, we write each inference rule only once,without regard to specific term representations. We maythen reuse it for any abstract syntax providing a sufficientlogical semantics that satisfies the DeMorgan class. Thus,we have achieved our basic goal of separating the mechanicsof inference from the details of implementation.

4. PRUFROCK

instance DeMorgan PropA whereisNeg (NegA ) = TrueisNeg = FalseisAnd (AndA ) = TrueisAnd = FalsemkOr = OrAmkNeg = NegAfstArg (AndA a b) = afstArg (OrA a b) = afstArg (NegA a) = asndArg (AndA a b) = bsndArg (OrA a b) = b

Figure 4: Class instance for PropA

deMorgans2 :: (a → Bool)→ (a → Bool)→ (a → a)→(a → a)→ (a → a)→ (a → a) a → a

deMorgans2 isNeg isAnd mkOr mkNeg fstArg sndArg expr =if (isNeg expr)

then let inner = (firstArg expr)in if (isAnd inner)

then mkOr (mkNeg (firstArg inner))(mkNeg (secondArg inner))

else exprelse expr

Figure 2: A (clumsy) reformulation of DeMorgan’s law

data PropB = ConnB ConnectiveB PropB PropB |NegB PropB |VarB Int

data ConnectiveB = AndB | OrB

instance DeMorgan PropB whereisNeg (NegB ) = TrueisNeg = FalseisAnd (ConnB AndB ) = TrueisAnd = FalsemkOr = ConnB OrBmkNeg = NegBfstArg (ConnB a b) = afstArg (NegB a) = asndArg (ConnB a b) = b

Figure 5: The PropB datatype

We have utilized these generic programming techniques inthe development of the Prufrock theorem prover framework.Prufrock is a collection of composable logic modules, whichprovide support for assorted inference systems. Prufrock isbuilt upon a standard sequent calculus, extended by addingsubstitution restrictions. This extension facilitates the useof metavariables in the processing of quantifier rules, whichsignificantly aids the proof search process by postponing in-stantiation decisions until near the end of the proof. A dis-cussion of the underlying proof system used by Prufrockraises another important concern. Thus far, we have onlyconsidered the relation between syntax and inference. Thereis another similar dependency relating the actual mechanicsof inference to the inference rules themselves. For instance,Prufrock’s sequent calculus requires a persistent state thattracks subgoal sequents, as well as backtracking search. Theimplementation that satisfies these requirements may beconsidered orthogonal to the declaration of inference rules,in much the same way that syntax and inference rules maybe deemed separate concerns. Thus, each logic module inPrufrock utilizes a pair of type classes: one that specifies alogical semantics that the proof-carrying syntax must pro-vide, and another that specifies mechanical manipulations ofthe prover state. These manipulations are encapsulated ina monad class, which declares an interface to global storagemaintained by a state monad. The structure of a typicallogic module is depicted in figure 6.

ASTMonadic Implementation

Term Class

Monad Class

Inst

ance

Decla

ratio

n

Inst

ance

Decla

ratio

n

Inference Rules

Global

State Logic

alSe

mantic

s

Tactics

Prover

Logic Module

Figure 6: Structure of a typical Prufrock logic mod-ule

Inference rules are written in terms of the logic module’slogical semantics, provided by a type class that describessyntax; and its state access, provided by a type class thatdescribes monadic state operations. These inference rulesare refined into tactics, using a uniform system, then ex-ported for use in an actual theorem prover. The frameworkconsisting of all exported proof tactics is then instantiatedwith a particular abstract syntax for representing proofs anda particular monadic implementation that carries out theproof process.

Because inference rules are defined over type classes, theirtype signatures reveal exactly what logical and monadicfunctionality they may utilize. This eases the task of ensur-ing the correctness of logic modules, by facilitating conser-vative extension. A module containing rules for quantifiersmay be combined with a module containing a complete set ofrules for propositions to obtain first-order logic. However,the rules for quantifiers may be defined without referenceto the type classes necessary for propositions, and thus theimplementer may rest assured that these rules are a conser-vative extension that will not impact the correctness of thealready defined propositional rules. Composition of modules

is standardized within the Prufrock framework, drasticallysimplifying module reuse and verification.

5. PROPOSITIONAL LOGICWe shall now present a series of simple logic modules, whichillustrate the power and flexibility of the Prufrock frame-work. Before we present our sample logic modules, however,we must define a few basic types used throughout the prover.The Sequent type is merely a pair of lists joined with theinfix constructor :`. Inference rules are represented as func-tions from a sequent to a list of new sequents resulting fromthe application of the rule. Similarly, decision proceduresare functions from a sequent to a Boolean value indicat-ing whether or not the sequent may be marked as closedand discharged. Note that the Sequent type is parametrizedover a type variable t that represents the type describingthe syntax used in proofs. Inference rules and decision pro-cedures are abstracted over a syntax type t , as well as amonad type m. These types correspond to the syntax typeand monad type used to finally construct the prover. By ab-stracting over them, we may ensure that all inference rulesand decision procedures do not depend on particular syntaxor monad implementations. The return type of an inferencerule is wrapped in the monad m to facilitate monadic actions(such as state access or backtracking) within the inferencerule.

data Sequent t = [t ] :` [t ]

type InferenceRule m t =Sequent t → m [Sequent t ]

type DecisionProcedure m t =Sequent t → m Bool

Figure 7: Basic types for sequents and inferencerules

We can now begin our set of examples by defining a modulefor propositional logic in the cut-free Gentzen-type variantof the sequent calculus. The inference rules associated withthis system are shown in their traditional formulation infigure 8.

In order to implement the rules for propositional logic, wefirst define a term class that allows us to recognize, extract,and create the necessary syntactic structure. In particu-lar, we need to recognize logical negation (¬), and the fourlogical connectives (∧, ∨, →, and ⇐⇒ ). We declare thetype class PropLogic to define the necessary logical seman-tics (figure 9). PropLogic provides a set of recognizer andconstructor functions for the literal Boolean values true andfalse, logical negation, conjunction, disjunction, implication,and bi-implication. Boolean values are recognized with thefunction isBool and interpreted using the getBool function,which returns a Boolean value’s truth value. The other rec-ognizers and constructors are declared in a straightforwardmanner. Finally we declare the functions, getFirstArg andgetSecondArg , that extract the first and second argumentsof an expression made up from one of the four logical con-nectives, and the function getNegArg , which extracts theargument from a negated term.

basicφφ,Γ ` φ,∆

basic>⊥,Γ ` ∆

basic⊥Γ ` >,∆

¬ ` Γ ` φ,∆¬φ,Γ ` ∆

` ¬ φ,Γ ` ∆

Γ ` ¬φ,∆

∧ ` φ, ψ,Γ ` ∆

φ ∧ ψ,Γ ` ∆` ∧ Γ ` φ,∆ Γ ` ψ,∆

Γ ` φ ∧ ψ,∆

∨ ` φ,Γ ` ∆ ψ,Γ ` ∆

φ ∨ ψ,Γ ` ∆` ∨ Γ ` φ, ψ,∆

Γ ` φ ∨ ψ,∆

→` Γ ` φ,∆ ψ,Γ ` ∆

φ→ ψ,Γ ` ∆`→ φ,Γ ` ψ,∆

Γ ` φ→ ψ,∆

⇐⇒ ` Γ ` φ,∆ ψ,Γ ` ∆

φ ⇐⇒ ψ,Γ ` ∆

` ⇐⇒ φ,Γ ` ψ,∆ ψ,Γ ` φ,∆Γ ` φ ⇐⇒ ψ,∆

Figure 8: Propositional inference rules for the se-quent calculus

class PropLogic t whereisBool :: t → BoolgetBool :: t → BoolmkBool :: Bool → tisNot :: t → BoolisAnd :: t → BoolisOr :: t → BoolisImp :: t → BoolisBicond :: t → BoolmkNot :: t → tmkAnd :: t → t → tmkOr :: t → t → tmkImp :: t → t → tmkBicond :: t → t → tgetFirstArg :: t → tgetSecondArg :: t → tgetNegaArg :: t → t

Figure 9: A term class for propositional logic

Propositional logic does not require additional requirementsfor global storage, but we will use the existing MonadNondetclass, taken from Hinze’s work [7] on backtracking monadtransformers, to implement backtracking.

A monad that is a member of MonadNondet provides, at aminimum, functions msum, mcommit , and mzero. These areused, respectively, to specify non-determinism (backtrack-ing), choice (“cuts” in the backtracking tree), and failure.We will only show the implementation for the basic, ¬, and∧ rules. The implementation of the other rules is straightfor-ward, proceeding in the same fashion. We use a set of utilityfunctions, shown in figure 10, to handle repeated tasks in theinference rules and thus make the interesting parts more ap-parent. ConnL and ConnR take a predicate over terms, anda function that takes a term and a sequent and returns alist of sequents. They then produce an inference rule thatattempts to find a term satisfying the given predicate on theappropriate side of sequent, then applies the SequentChangefunction to the sequent and the selected term, returning alist of new sequents. If no term can be found to satisfy thepredicate, the resulting rule returns mzero to indicate fail-ure. The utility functions are used to define the inferencerules presented in figure 11. Note that most can be derivedin a straightforward fashion from their abstract represen-tations in Figure 8. The basic axioms are represented asa single decision procedure that encompasses all three ax-ioms. The other rules are implemented in a straightforwardmanner.

basicRule :: (PropLogic t)⇒DecisionProcedure m t

basicRule (hs :` cs) = return decisionwhere

decision = (or (map isFalse hs)) ∨(or (map isTrue cs)) ∨(or [h ≡ c | h ← hs, c ← cs ])

isTrue x = (isBool x ) ∧ (getBool x )isFalse x = (isBool x ) ∧ (¬ (getBool x ))

notLeftRule :: (MonadNondet m,PropLogic t)⇒InferenceRule m t

notLeftRule = connL isNot notLeftRule ′

wherenotLeftRule ′ t (hs :` cs) =

[hs :` ((getFirstArg t) : cs)]

andRightRule :: (MonadNondet m,PropLogic t)⇒InferenceRule m t

andRightRule = connR isAnd andRightRule ′

whereandRightRule ′ t (hs :` cs) =

[hs :` ((getFirstArg t) : cs),hs :` ((getSecondArg t) : cs)]

Figure 11: Encoding of inference rules for proposi-tional logic

6. PREDICATE CALCULUSNow that we turn to an implementation of the quantifierrules of predicate calculus. Our implementation of the pred-icate calculus relies on the notion of variables and meta-

variables, which allow decisions about the universal-strengthinstantiation rules ` ∀ and ∃ ` to be postponed until furtherproof information is available.

Rather than choosing a specific instantiation for the con-structs associated with these rules, we replace the quan-tified variable with a meta-variable. Quantified variablesof existential-strength (i.e. the ones manipulated by ` ∃and ∀ `) are replaced by parameters. In order to preventvariable capture, we manage a list of forbidden substitu-tions, in which every parameter introduced after a meta-variable is a forbidden substitution for that meta-variable.This satisfies the provisos that are usually associated withuniversal-strength quantifiers [23]. The resulting inferencerules operate over the restricted sequent calculus [14]. Aset of forbidden substitutions, denoted by R, are carriedwith each sequent in this variant of the sequent calculus,which appears in figure 12. In figure 12 we use addMetam

and addParamy to denote the addition of the meta-variablem or the parameter y to the restrictions R in the fashiondescribed.

∀ `[φ]mx , ∀x.φ,Γ ` ∆ ‖ addMetam(R)

∀x.φ,Γ ` ∆ ‖ R

` ∀Γ ` [φ]yx,∆ ‖ addParamy(R)

Γ ` ∀x.φ,∆ ‖ R

∃ `[φ]yx,Γ ` ∆ ‖ addParamy(R)

∃x.φ,Γ ` ∆ ‖ R

` ∃Γ ` ∃x.φ, [φ]mx ,∆ ‖ addMetam(R)

Γ ` ∃x.φ,∆ ‖ R

Figure 12: Inference rules for predicates in the re-stricted sequent calculus

Now that we have described the basic design strategy forpredicate calculus, we will define the necessary term andmonad classes. We begin by describing the necessary rec-ognizers and constructors for predicates, parameters, andmeta-variables, and augment these with functions that ex-tract the quantified variable and the body from a quantifiedexpression. The resulting class is shown in figure 13.

class PropLogic t ⇒ PredLogic t whereisForall :: t → BoolisExists :: t → BoolisParam :: t → BoolisMeta :: t → BoolmkForall :: t → t → tmkExists :: t → t → tmkParam :: String → tmkMeta :: String → tgetVar :: t → tgetBody :: t → t

Figure 13: A term class for predicate calculus

Next, we define a monad class for predicate logic. We needto manage a list of restrictions, as well as generate fresh

connL :: (MonadNondet m)⇒(t → Bool)→ SequentChange t → InferenceRule m t

connL p f seq@(hs :` cs) =let mkNewSeq (t , ts) = f t (ts :` cs)in maybe mzero (λs → return (mkNewSeq s)) (findRemove p hs)

connR :: (MonadNondet m)⇒(t → Bool)→ SequentChange t → InferenceRule m t

connR p f seq@(hs :` cs) =let mkNewSeq (t , ts) = f t (hs :` ts) sin maybe mzero (λs → return (mkNewSeq s)) (findRemove p cs)

type SequentChange t = t → Sequent t → [Sequent t ]

findRemove :: (t → Bool)→ [t ]→ Maybe (t , [t ])findremove p xs = findRemove ′ p xs [ ]findRemove ′ p [ ] ns = NothingfindRemove ′ p (y : ys) ns | p y = Just (y ,ns ++ ys)

| otherwise = findRemove p ys (y : ns)

Figure 10: Utility functions for propositional logic

meta-variables and parameters during instantiation. ThePredState class, shown in figure 14, provides this function-ality. The getNextVar and getNextParam functions returnfresh meta-variables and parameters. The incNextVar andincNextParam functions freshen the currently stored meta-variable or parameter to ensure that the next call to the cor-responding get functions will result in a new meta-variableor parameter. Functions getForbiddens and setForbiddensprovide a way to access and alter the a current set of re-strictions, stored in a ForbiddenList . ForbiddenList man-ages forbidden substitutions as an association list of termsand lists of terms. The first element of each pair is a meta-variable, while the second element is a list of parametersthat cannot be substituted for this meta-variable. As an ex-ample, the ForbiddenList : [(m1 , [p1 , p2 ]), (m2 , [p3 ])] indi-cates that m1 7→ p1 , m1 7→ p2 , and m2 7→ p3 are forbiddensubstitutions.

class (PredLogic t ,BasicState m t)⇒ PredState m t wheregetNextVar :: m tincNextVar :: m ()getNextParam :: m tincNextParam :: m ()getForbiddens :: m (ForbiddenList t)setForbiddens :: ForbiddenList t → m ()

type ForbiddenList t = [(t , [t ])]

Figure 14: A monad class for predicate calculus

Now we may define the actual inference rules for the predi-cate calculus, again we will use the helper functions shownin figure 16 to perform tasks related to managing forbid-den lists. The addParam function adds a new parame-ter to the restriction list of every existing meta-variable,and the addMeta function adds a new meta-variable to theForbiddenList with an empty set of restrictions. This func-tionality preserves the necessary list of restrictions for therestricted sequent calculus, as described above. We will

also use mConnL and mConnR, which are simply alter-nate versions of connL and connR from the propositionallogic module that allow the sequent transformer function tobe monadic. We show the implementations of the ∀ rulesonly. The ∃ rules follow the same general form. The sam-ple rule definitions are shown in figure 15. Note that wewill eventually need a unifier to remove metavariables froma proof. Prufrock’s unifier is based heavily on Jansson andJeuring’s existing work [10] on polytypic unification, andthus is not presented in this paper. To avoid a detailed dis-cussion of the unification system, we shall just assume thatthe applySubst function substitutes its second argument forits first throughout the term specified by its third argument.

forallLeftRule :: (PredLogic t ,PredState m t ,MonadNondet m)→ InferenceRule m t

forallLeftRule = mConnL isForall transformerwhere transformer t (hs :` cs) =

do v ← getNextVarfs ← getForbiddensincNextVarsetForbiddens (addMeta v fs)let t ′ = applySubst (getVar t) v (getBody t)return [((t ′ : hs) :` cs)]

forallRightRule :: (PredLogic t ,PredState m t ,MonadNondet m)→ InferenceRule m t

forallRightRule = mConnR isForall transformerwhere transformer t (hs :` cs) =

do p ← getNextParamfs ← getForbiddensincNextParamsetForbiddens (addParam p fs)let t ′ = applySubst (getVar t) p (getBody t)return [(hs :` (t ′ : cs))]

Figure 15: Encoding of the inference rules for pred-icate calculus

addParam :: (PredLogic t)⇒t → ForbiddenList t → ForbiddenList t

addParam y fs = map (λf @(m, fs ′)→ (m, y : fs ′)) fs

addMeta :: (PredLogic t)⇒t → ForbiddenList t → ForbiddenList t

addMeta m fs = (m, [ ]) : fs

mConnR :: (MonadNondet m)⇒(t → Bool)→ (t → InferenceRule m t)→

InferenceRule m tmConnR p f seq@(hs :` cs) =

let mkNewSeq (t , ts) = f t (hs :` ts)in maybe mzero mkNewSeq (findRemove p cs [ ])

mConnL :: (MonadNondet m)⇒(t → Bool)→ (t → InferenceRule m t)→

InferenceRule m tmConnL p f seq@(hs :` cs) =

let mkNewSeq (t , ts) = f t (ts :` cs)in maybe mzero mkNewSeq (findRemove p hs [ ])

Figure 16: Utility functions for predicate calculusrules

7. INFERENCE RULES TO TACTICSWe now illustrate how the Prufrock framework converts ab-stract inference rules from various logic modules into uni-form, composable tactics. We must begin with a discussionof the basic state that Prufrock uses to store intermediateproof data, as even the simplest proofs require some globalstate. This state stores and manage two elements: the maingoal for the overall proof, and a set of current subgoals. Foreach of these elements we provide a pair monadic functions:an accessor that returns the state’s currently stored value,and a mutator that stores a new value in the state.

class (Term t)⇒ BasicState m t wheregetMainGoal :: m (Sequent t)setMainGoal :: (Sequent t)→ m ()getSubGoals :: m (RTree (Sequent t))setSubGoals :: RTree (Sequent t)→ m ()

Figure 17: The BasicState class

The RTree type is an implementation of rose trees, indexedby type Key , which are used to store the proof’s subgoals.The applyRule and applyDecProc functions, shown in figure18 provide the basic interface between inference rules andtactics. These functions guarantee a uniform access of theprover’s basic state, freeing logic module implementers fromlow-level details relating to the prover’s actual manipulationof goals. The tryLookup and trySplice functions simply ma-nipulate the rose tree of subgoals, attempting to look up agiven key, or splice a set of subgoals into the tree at a givennode1. All of the rule application functions return tactics,which are defined as monadic actions with type m ().

Now we can define a collection of tacticals that combinetactics using monadic operations. Figure 19 implements a

1Note that splicing [ ] is equivalent to deleting a node.

applyRule :: (BasicState m t ,MonadNondet m,MonadIO m)⇒

InferenceRule m t → Key → m ()applyRule rule k =

do subs ← getSubGoalssub ← tryLookup subs knewSubs ← rule subsubs ′ ← trySplice subs k newSubssetSubGoals subs ′

‘catchError ‘ λe → liftIO (putStrLn (show e))

applyDecProc :: (BasicState m t ,MonadNondet m,MonadIO m)⇒

DecisionProcedure m t → Key → m ()applyDecProc dp k =

do subs ← getSubGoalssub ← tryLookup subs kdec ← (dp sub)if dec

then do subs ′ ← trySplice subs k [ ]setSubGoals subs ′

liftIO (putStrLn("QED (" ++ (show k) ++ ")"))

else mzero‘catchError ‘λe → liftIO (putStrLn (show e))

tryLookup t k =maybe (throwError

(strError "Subgoal not found"))return (lookupEltRT t k)

trySplice t k es =maybe (throwError

(strError "Subgoal not found"))return (spliceRT t k es)

Figure 18: The apply∗ functions

collection of tacticals taken from Isabelle [22]. Performingone tactic after another is already provided by the standardMonad class’s sequence operator, and may be done in astraightforward manner, using Haskell’s standard do nota-tion for sequencing monads. Choosing between two possibletactics is accomplished using the orRule combinator, whichjoins the tactics with msum to indicate non-determinism,and then applies mcommit to the result, forcing a cut in thebacktracking tree at this point. Removing mcommit andthus allowing backtracking to occur amongst the possiblechoices results in the eitherRule combinator, which providesa non-deterministic, backtracking selection between two tac-tics. We may generalize these two combinators using foldrto obtain firstF and anyF , which operate on lists of tactics.

Tacticals are implemented in Prufrock as programs in theHaskell host language. Thus, we can add additional tacticalsfor specific problems or to provide more sophisticated proofheuristics by simply writing Haskell implementations. Theonly restriction is that a tactical may never manipulate thestate outside the inference rules.

The role of backtracking in the inference process should nowbe clearer. Backtracking is not included to support the infer-ence rules defining a logic, but the tacticals that are requiredto search the sequent. In effect, backtracking provides anaugmented search capability as a part of the inference im-plementation as is commonly seen in other theorem provers.

orRule :: (MonadNondet m)⇒ m a → m a → m aorRule r1 r2 = mcommit (r1 ‘mplus‘ r2 )

eitherRule :: (MonadNondet m)⇒ m a → m a → m aeitherRule r1 r2 = r1 ‘mplus‘ r2

firstF :: (MonadNondet m)⇒ [m a ]→ m afirstF rules = mcommit (foldr orRule mzero rules)

anyF :: (MonadNondet m)⇒ [m a ]→ m aanyF rules = foldr orRule mzero rules

repeatRule :: (MonadNondet m)⇒ m ()→ m ()repeatRule r = do r

((repeatRule r) ‘mplus‘ return ())

depthFirst :: (MonadNondet m)⇒m Bool → m a → m ()

depthFirst pred tac = do predResult ← predif predResult

then return ()else do tac

depthFirst pred tac

Figure 19: Tacticals

Using these basic tacticals, we may now define more com-plex search strategies. We may wish, for instance to re-peat a given tactic until it is no longer applicable. TherepeatRule tactical accomplishes this. A similar tactical isdepth first search. The function depthFirst applies the giventactic until the specified search-termination predicate is sat-isfied. If the given tactic is made up of several tactics joinedby an eitherRule, then this tactical will produce a depth-first

search function with each tactic as a possible expansion foreach proof state.

8. CONCLUSION AND FUTURE WORKPrufrock provides a robust set of modules and features ex-tending well beyond the scope of this paper. In additionto full implementations of propositional logic and predicatecalculus, it features a unification system; a polytypic termrewriting system, which can be used to rewrite equalities inproofs; and simplification of arithmetic expressions and con-ditional expressions using decision procedures. The proverframework also provides many useful interactive features,including a detailed logging system that tracks proof steps(even inside of large tactics such as depth-first search) andallows the user to save and replay proofs. The user mayalso pretty-print proofs to TEXfiles. This functionality isimplemented generically, so that any language’s proofs maybe printed with no additional overhead. Prufrock’s full logicmodules are accessible by interactive commands that maybe composed along with the modules to create an interactiveshell supporting predefined logics.

The framework has been successfully used in several con-texts, primarily in the development of the Rosetta languagefor system-level design [3, 2]. In particular, Prufrock hasbeen used for rapid-prototyping of type systems for the Rosettaevaluator [13], and for verifying Rosetta models [27]. In thecase of type-checking, Prufrock has proved useful due to thesimplicity with which type rules may be encoded and exe-cuted in the prover environments. A set of class instancesfor the Rosetta expression language has already been imple-mented, and work to extend these instances to facets, whichmay represent entire specifications, is underway. This makesit possible for an entire Rosetta specification to appear di-rectly in proofs as a first-class object that may be manipu-lated according to its logical semantics. In addition to theRosetta-project, Prufrock has been tested on problems fromthe TPTP library [25], which it processes natively in theTPTP syntax.

Prufrock also implements an interface into the ZChaff SAT-solver [19], which may be used as a decision procedure. Be-cause the logical semantics required to clausify sequents isspecified entirely by the first-order logic modules, any lan-guage that provides an interface to Prufrock’s first-orderlogic capabilities may be used in ZChaff without any addi-tional work. This is accomplished by defining the clausifierand translation functions generically. We are currently ex-ploring the possibility of using the Prufrock framework as anintermediary between various existing tools, such as ZChaff,and arbitrary abstract syntaxes. In addition, several toolscan be used on the same abstract syntax, provided thatthe required translation functions were defined generically.Unfortunately, linking to external tools requires syntactictranslation, a problem that Prufrock was designed to avoid.

The primary feature of the Prufrock framework, however, re-mains its flexibility. Prufrock allows implementers to designseparate modules independent of prover internals and syn-tactic concerns, then compose these modules into a workingprover for any appropriate abstract syntax. This amelio-rates the gap between tools and specification languages, byallowing a language’s proof tools to evolve with the language

itself. Furthermore, once a logic module has been refinedand finalized, its functionality remains the same, even asthe language representing proofs and the execution detailsof the prover evolve.

9. REFERENCES[1] S. Akkipeddi, P. Alexander, K. Ranganathan, and

P. Chawla. Generating test vectors from systemsrequirements. In Proceedings of AutoTestCon’01,Washington, DC, August 2001.

[2] P. Alexander, D. Barton, and C. Kong. Rosetta UsageGuide. The University of Kansas Information andTelecommunications Technology Center.

[3] P. Alexander, D. Barton, C. Kong, and C. Menon.The Rosetta Strawman. Technical report, TheUniversity of Kansas, 2002.

[4] P. Alexander and C. Kong. Rosetta: Semantic supportfor model-centered systems-level design. IEEEComputer, 34(11):64–70, November 2001.

[5] P. Baraona, J. Penix, and P. Alexander. Vspec: Adeclarative requirements specification language forvhdl. In J.-M. Berge, O. Levia, and J. Rouillard,editors, High-Level System Modeling: SpecificationLanguages, volume 3 of Current Issues in ElectronicModeling, chapter 3, pages 51–75. Kluwer AcademicPublishers, Boston, MA, 1995.

[6] D. Dieckman, P. Alexander, and P. A. Wilsey.Activespec: A framework for the specification andverification of active network services and securitypolicies. In Proceedings of Formal Methods in SecurityProtocols, Indianapolis, IN, June 1998.

[7] R. Hinze. Deriving backtracking monad transformers.ACM SIGPLAN Notices, 35(9):186–197, 2000.

[8] R. Hinze and J. Jeuring. Generic Haskell: Practice andtheory. In R. C. Backhouse and J. Gibbons, editors,Generic Programming, volume 2793 of Lecture Notesin Computer Science, pages 1–56. Springer, 2003.

[9] P. Jansson and J. Jeuring. PolyP—A polytypicprogramming language extension. In Conf. Record24th ACM SIGPLAN-SIGACT Symp. on Principles ofProgramming Languages, POPL’97, Paris, France,15–17 Jan 1997, pages 470–482. ACM Press, NewYork, 1997.

[10] P. Jansson and J. Jeuring. Polytypic Unification.Journal of Functional Programming, 8(5):527–536,1998.

[11] P. Jansson and J. Jeuring. A framework for polytypicprogramming on terms, with an application torewriting. In WGP. Utrecht University, 2000.UU-CS-2000-19.

[12] J. Jeuring and P. Jansson. Polytypic programming. InJ. Launchbury, E. Meijer, and T. Sheard, editors,Tutorial Text 2nd Int. School on Advanced FunctionalProgramming, Olympia, WA, USA, 26–30 Aug 1996,volume 1129, pages 68–114. Springer-Verlag, Berlin,1996.

[13] E. Komp, G. Kimmell, J. Ward, and P. Alexander.The Raskell Evaluation Environment. Technicalreport, The University of Kansas Information andTelecommunications Technology Center, November2003.

[14] R. Kumar, T. Kropf, and K. Schneider. Integrating aFirst-Order Automatic Prover in the HOLEnvironment. In M. Archer, J.J. Joyce, K.N. Levitt,and P.J. Windley, editors, International Workshop onHigher Order Logic Theorem Proving and itsApplications, pages 170–176, Davis, California, 1991.IEEE Computer Society Press.

[15] R. Lammel and S. Peyton Jones. Scrap yourboilerplate: a practical design pattern for genericprogramming. ACM SIGPLAN Notices, 38(3):26–37,Mar. 2003. Proceedings of the ACM SIGPLANWorkshop on Types in Language Design andImplementation (TLDI 2003).

[16] R. Lammel and S. Peyton Jones. Scrap moreboilerplate: reflection, zips, and generalised casts. InProceedings of the ACM SIGPLAN InternationalConference on Functional Programming (ICFP 2004),pages 244–255. ACM Press, 2004.

[17] R. Lammel and S. Peyton Jones. Scrap yourboilerplate with class: extensible generic functions. InProceedings of the ACM SIGPLAN InternationalConference on Functional Programming (ICFP 2005),pages 204–215. ACM Press, Sept. 2005.

[18] B. Morel and P. Alexander. Spartacas: Automatingcomponent reuse and adaptation. IEEE Transactionson Software Engineering, 30(9):587–600, September2004.

[19] M. W. Moskewicz, C. F. Madigan, Y. Zhao, L. Zhang,and S. Malik. Chaff: Engineering an Efficient SATSolver. In Proceedings of the 38th Design AutomationConference (DAC’01), 2001.

[20] U. Norell and P. Jansson. Polytypic programming inhaskell. In P. W. Trinder, G. Michaelson, andR. Pena, editors, IFL, volume 3145 of Lecture Notes inComputer Science, pages 168–184. Springer, 2003.

[21] S. Owre, J. Rushby, and N. Shankar. PVS: APrototype Verification System. In D. Kapur, editor,Proc. of 11th International Conference on AutomatedDeduction, volume 607 of Lecture Notes in ArtificialIntelligence, pages 748–752, Saratoga, NY, June 1992.Springer–Verlag.

[22] L. C. Paulson. Isabelle: A Generic Theorem Prover.Springer, 1994. LNCS 828.

[23] L. C. Paulson. ML for the Working Programmer,chapter 10, pages 397–444. Cambridge UP, 2 edition,2000.

[24] S. Peyton Jones, L. Augustsson, B. Boutel, F. Burton,J. Fairbairn, J. Fasel, A. Gordon, M. Guzmn,K. Hammond, P. Hudak, R. Hughes, T. Johnsson,M. Jones, R. Kieburtz, R. Nikhil, W. Partain, andP. Wadler. Cambridge University Press, 2003.

[25] G. Sutcliffe and C. Suttner. The TPTP ProblemLibrary: CNF Release v1.2.1. Journal of AutomatedReasoning, 21(2):177–203, 1998.

[26] R. Vemuri, R. Mandayam, and V. Meduri.Performance modeling using PDL. Computer,29(4):44–53, April 1996.

[27] J. Ward and G. Kimmell. Rosetta Theorem Prover.Technical report, The University of KansasInformation and Telecommunications TechnologyCenter, June 2003.