Practical Idiomatic Considerations for Checkable Meta-Logic in Experimental Functional Programming
aa r X i v : . [ c s . P L ] A ug Practical Idiomatic Considerations forCheckable Meta-Logic in ExperimentalFunctional Programming
Baltasar Tranc´on y Widemann and Markus Lepper semantics GmbH, Berlin
Abstract.
Implementing a complex concept as an executable model ina strongly typed, purely functional language hits a sweet spot betweenmere simulation and formal specification. For research and education it isoften desirable to enrich the algorithmic code with meta-logical annota-tions, variously embodied as assertions, theorems or test cases. Checkingframeworks use the inherent logical power of the functional paradigmto approximate theorem proving by heuristic testing. Here we proposeseveral novel idioms to enhance the practical expressivity of checking,namely meta-language marking, nominal axiomatics, and constructiveexistentials. All of these are formulated in literate Haskell’98 with somecommon language extensions. Their use and impact are illustrated byapplication to a realistic modeling problem.
Keywords:
Executable modeling; property-based testing; reified logic
This paper discusses general programming methodology in terms of a particularimplementation in Haskell. Thus it is provided as a literate Haskell [10] program. Purely functional programming has arguably a friendlier relationship to meta-logic, the discipline of formal reasoning about program properties, than conven-tional state-based paradigms [1]. This has been exploited in a number of waysthat differ greatly in their pragmatic context.At one end of the spectrum, strongly normalizing languages and the types-as-propositions approach, ultimately based on the Brouwer–Heyting–Kolmogorovinterpretation of constructive logic, have led to the unification of algorithmicprogramming and constructive theorem proving . The practice has evolved frombasic models such as the Calculus of Constructions [6] to full-blown languagesand interactive programming environments such as Agda [9]. The basic approach A full and self-contained source archive for practical evaluation is publicly availableat http://bandm.eu/download/purecheck/ . s that a program is statically validated with respect to a type signature thatencodes the desired meta-logical property, if and only if it truly possesses thatproperty. This approach does evidently not scale to Turing-complete languages. Thus, for meta-logic over complete programming languages, it is not sufficientto demonstrate inhabitation of a type to obtain a proof, but it must also standthe test of successful evaluation .At the other end of the spectrum, freedom from side effects allows for liber-ally sprinkling program code with online assertions , that is, computations whosevalues the program depends on not for its outcome, but for ensuring its cor-rect operation. Some care must be exercised when timing and strictness detailsmatter [2], but otherwise the technique is just as powerful as for conventionalprogramming paradigms [4], minus the need for a pure assertion sublanguage.The middle ground is covered by offline checking , that is, evaluation ofmeta-logical properties as a separate mode of program execution. Offline check-ing is of course less rigorous than theorem proving, and may involve incompleteand heuristic reasoning procedures. On the other hand, it is more abstract andstatic than online assertions; thus cases that are not reached during online evalu-ation can be covered, and the checking effort can be shifted to convenient pointsin the software lifecycle. Offline checking fills the same role as conventional unittesting procedures, although the focus is a bit different: checking purely func-tional programs is commonly both simpler in control, due to the lack of state ofthe unit under test that needs to be set up and observed, and more complex indata, due to the pervasiveness of higher-order functions.There are various popular offline checking frameworks for functional pro-gramming languages, such as QuickCheck, (Lazy) SmallCheck, SmartCheck,ScalaCheck or PropEr, and we assume the reader is familiar with their generaldesign and operation, for instance with the seminal QuickCheck [3] for Haskell. The field of executable modeling, that is, the construction of experimental pro-grams that embody theoretical concepts of systems and processes, and imbuethem with practically observable behavior, poses specific challenges. In particu-lar, some mechanism is needed to validate the implementation, that is, establishtrust in its faithful representation of the concepts under study. Since executablemodels are designed to exceed behavioral a-priori intuition (otherwise their con-tent were trivial)[5], it is intrinsically hard to differentiate bugs from features.In a naive idealistic sense, model programs should be derived and proved rig-orously. However, that presupposes a complete, computable and operationalizedtheory. For the two scenarios where a theory exists but is not fully operational-ized, and where models are used inductively as approximations to a future theory, Consider the “constructive” logical reading of the type of a generic recursion opera-tor, ( α → α ) → α ; it says literally that begging the question , α → α , is a valid proofmethod for any proposition α . Thus named here for clear contrast with the alternatives, but largely synonymouswith property-based testing [7].
2e consider less rigorous approaches, and offline checking in particular, the moreviable validation procedure. It may even be educational to both check and runmodels that behave evidently wrong.
The checking idioms to be proposed in the following have been developed in thecontext of an experimental checking framework, PureCheck, implemented as aplain Haskell library. The design of PureCheck largely follows the paradigm ofpopular frameworks such as QuickCheck[3] or SmallCheck[ ? ], with some notabledeviations.Like other frameworks, PureCheck leverages the internal logical language offunctional programming, and type-directed generation of test data for universalpropositions. PureCheck prioritizes purity and non-strictness; text execution isrigidly non-monadic, and thus equally suitable for both offline checks and onlineassertions. Unlike QuickCheck, test data are generated by deterministic ratherthan randomized combinatorial procedures. Unlike SmallCheck, sample sizes canbe bounded precisely, without risk of combinatorial explosion. Test data setsare pessimistically assumed to be possibly insufficient, and thus the direction oflogical approximation is significant; evaluation may yield false positives, resultingfrom undiscovered counterexamples, but never false negatives.The contribution of the present paper is a collection of three novel and exper-imental idioms for offline checking. The definitions and an example applicationare given in the following two main sections, respectively. These features havebeen implemented in Haskell for PureCheck, but are theoretically compatiblewith other frameworks and host languages. PureCheck Basics
At the heart of the framework is an encapsulation forheuristic checks. newtype
Check = Check { perform :: Int → Bool } The heuristic is parameterized with an
Int called the confidence parameter.Because of monotonicity, higher values may require more computational effort,but can only improve the test accuracy by eliminating more false positives.The propositions that can be encapsulated in this way come in various shapes;thus we define a type class with an ad-hoc polymorphic encapsulation operation. class
Checkable α where check :: Meta α → Check
The wrapper
Meta should be ignored for now; it shall be discussed in due detailin the following section. The base case is a propositional constant. instance
Checkable Bool where check ( Meta b ) =
Check ( const b ) Checks bear the obvious conjunctive monoid structure. Since the aggregate con-fidence in the truth of a conjunction can be no higher than the individual con-fidence in any of its clauses, the parameter is copied clause-wise.3 nstance
Monoid Check where mempty = Check ( λ n → True ) mappend ( Check c ) (
Check d ) =
Check ( λ n → c n ∧ d n ) instance Checkable () where check ( Meta ()) = mempty instance ( Checkable α, Checkable β ) ⇒ Checkable ( α, β ) where check ( Meta ( p , q )) = check ( Meta p ) ‘ mappend ‘ check ( Meta q ) instance ( Checkable α ) ⇒ Checkable [ α ] where check ( Meta ps ) = mconcat ( map ( check ◦ Meta ) ps ) For quantified universals, a generator for representative samples of the argu-ment space is required. The confidence parameter is taken as the recommendedmaximum sample size (unlike SmallCheck, where the parameter is a depth tobe exhausted, such that sample size may be only exponentially related). Unlikein the conjunctive case, nested universal quantifiers are not simply dealt withrecursively. Instead, it is recommended to use uncurried forms quantified overtuples to ensure proper weight-balancing between argument samples. checkWith :: Generator α → Meta ( α → Bool ) → CheckcheckWith g ( Meta p ) =
Check ( λ n → all p ( generate g n )) instance ( Some α ) ⇒ Checkable ( α → Bool ) where check = checkWith some Test data generators are wrapped pure functions, and thus deterministicin the size parameter n . Useful generators return at most (preferably approxi-mately) n elements (preferably distinct and with commensurate internal variety). newtype Generator α = Generator { generate :: Int → [ α ] } A type class provides default generators for its instance types. class
Some α where some :: Generator α Generators for simple types are straightforward, for instance: instance
Some Bool where some = Generator $ flip take [ False , True ] Generator combinators for complex types need to consider the issues of weightbalancing between dimensions and of infinite enumerations; the details are outof scope here. The principle of types as propositions in a functional programming language isa two-sided coin. On the upside, the internal logical language is automatically Implementations can be found in the full source. We propose that, for both education and engineering, it is a wise move todelimit the parts of the codebase that are intended as meta-logical vocabularyexplicitly. To this end, we introduce a generic wrapper type. data
Meta α = Meta { reflect :: α } Then the codebase is manifestly stratified into four layers:
Operational definitions do not use the
Meta type/value constructor.
Assertive definitions use the
Meta constructor in root position.
Tactical definitions use the
Meta constructor in non-root position.
Transcendent definitions are polymorphic over a type (constructor) variable thatadmits some
Meta α (or Meta itself, respectively) as an instance . The effect of this stratified marking discipline is that, contrarily to the patho-logical foldl example presented above, the intented reading of type signaturesbecomes clear. For instance: – Meta Bool is the type of atomic assertive meta-expressions that are expected toevaluate straightforwardly to
True ; definitions of this type incur a singleton staticchecking obligation, that is a test case . – Meta Int is a type of meta-expressions without a truth-value, let alone an expectedone; definitions of this type incur no static checking obligation. – Meta ( A → Bool ) is the type of quantified assertive meta-expressions that areexpected to evaluate to True for all parameter values of type A ; definitions of thistype incur a static checking obligation for some values (preferably a representativeset). – A → Meta Bool is the type of tactics that can construct such assertions fromparameter values of type A ; definitions of this type incur no static checking obli-gation, but may implement an aspect of a test strategy . – Meta A → Meta B is the type of tactics that can transform an assertion of type A to an assertion of type B ; definitions of this type incur no checking obligation,but may implement an aspect of a test strategy. – Meta A → Bool is the type of tactics that can evaluate a meta-property of anassertion of type A ; definitions of this type incur no checking obligation, but mayimplement an aspect of a test strategy. – Meta ( α → Bool ) → Meta ([ α ] → Bool ) is the type of parametrically polymorphicpredicate transformers that lifts an assertion meta-expression quantified over anarbitrary element type α to one quantified over the corresponding list type [ α ]. The reader is invited to contemplate for example the variety of possible higher-orderlogical meanings of the following specialization of a well-known Haskell Preludefunction: foldl :: (
Foldable τ ) ⇒ ( Bool → α → Bool ) → Bool → τ α → Bool Note the discourse-level meta-variable A for a monomorphic Haskell type, insteadof an object-level type variable α . Meta data constructor. By contrast, the reverse trans-port using the projection reflect is discouraged except for certain idiomatic cases.Evidently level marking makes no contribution to algorithmic computations.That it is pragmatically valuable documentation nevertheless is demonstratedby the explicit meta-logical universal quantifier: foreach :: ( α → Meta β ) → Meta ( α → β ) foreach f = Meta ( λ x → reflect ( f x )) If f is a predicate that is used pointwise to form meta-expressions, then foreach f is a singular meta-expression that quantifies over all points. For example, Meta ◦ even :: Int → Meta Bool is clearly a predicate intended to be used pointwise since the alternative reading,“all integers are even”, is blatantly false. By contrast, foreach ( Meta ◦ even ◦ ( ∗ Meta ( Int → Bool ) is a (true) universal assertion quantified over all (non- ⊥ ) values of type Int .As a more relevant example, consider a preorder of meta-logical interest, saya semantic approximation relation, on some data type A . ( ⊑ ) :: A → A → Meta Bool
This is directly usable as a binary predicate that characterizes the relationshipof two particular elements. By converting one quantifier, we obtain a unarypredicate that characterizes a particular element as globally minimal: minimal :: A → Meta ( A → Bool ) minimal x = foreach ( x ⊑ ) By converting the other quantifier also, we obtain a nullary predicate that char-acterizes the preorder as trivial: trivial :: Meta ( A → A → Bool ) trivial = foreach minimal The final conversion to the recommended uncurried type
Meta (( A , A ) → Bool )can be performed explicitly (left as an exercise to the reader), or implicitly by asuitable instance of
Checkable .This style ensures that higher-order functions and meta-logical reading areorthogonal means of expressivity.All checking ultimately involves the evaluation of an expression of type
Meta Bool . The denotational semantics of this Haskell type has four meaningfulvalues, namely: 6 alue Verdict:
The checked property
Issue Type
Meta True . . . holds —
Meta False . . . does not hold logical falsehood
Meta ⊥ . . . cannot be decided logical error ⊥ . . . cannot be stated tactical error Semantic ⊥ values occuring intermediately, such as in tactical computationsor test data generation, are not constrained by our framework. To the contrary,non-strictness can be exploited in useful ways to manipulate complex meta-logical constructs. For instance, consider a form of bounded quantification, wherean explicit sample generator is provided: data For α β = For { bound :: Generator α, body :: α → β } instance Checkable ( For α Bool ) where check ( Meta ( For g p )) = checkWith g ( Meta p ) Nested bounded quantifications of the form
For g ( λ x → For h ( λ y → p ))cannot be merged or transposed straightforwardly, because a lambda abstractionintervenes. However, semantics can be exploited if h is independent of, and thusnon-strict in x . qmerge :: For α ( For β γ ) → For ( α, β ) γ qmerge ( For g k ) = let h = bound ( k ⊥ ) in For ( gpair g h ) ( λ ( x , y ) → body ( k x ) y ) Here gpair forms a Cartesian sample product for marginal generators g and h . In a types-as-propositions approach to meta-logic of functional programs, a prop-erty of interest is encoded as a dependent type, and holds if the type can bedemonstrated to be inhabited in a constructive semantics.By contrast, checking approaches are empirical : Properties of interest aretested by computable functions, and thus collapse to the result type
Bool , ofwhich only the value
True is accepted. A seemingly trivial, but practically sig-nificant consequence is that type signatures are not helpful to prevent accidentalconfusion of structurally similar properties.This issue is compounded, quite paradoxically, by abstraction mechanisms.Often a proposition can be stated in concise generic form by abstraction fromvalues, types or type class instances. The actual checking then operates on aparticular concretization (by application in the former and type inference in thelatter two cases, respectively).In this context, misreference or omission errors are easy to commit and hardto detect. Hence it is of some practical importance to organize the meta-logicalpropositions attached to a particular reusable program part clearly and account-ably. Adequate solutions appear to depend heavily on the programming style; thefollowing guidelines should thus be understood as both flexible and incomplete. The implementation of gpair is explained in detail in the full source. heory Type Classes A substantial part of model-ish functional programs isabout the algebra of data structures. For structures organized in the idiomaticHaskell way as type classes, the associated meta-logic can conveniently be orga-nized as a companion type class with default implementations. This bundles thelaws and makes them accessible to simultaneous instantiation, and to automaticenumeration via meta-programming (which is not discussed here).For example, consider the implied laws of the Prelude type class
Monoid : class ( Monoid α ) ⇒ MonoidTheory α where monoid left unit :: ( Eq α ) ⇒ Meta ( α → Bool ) monoid left unit = Meta ( λ x → mempty ⋄ x ≡ x ) monoid right unit :: ( Eq α ) ⇒ Meta ( α → Bool ) monoid right unit = Meta ( λ x → x ⋄ mempty ≡ x ) monoid assoc :: ( Eq α ) ⇒ Meta (( α, α, α ) → Bool ) monoid assoc = Meta ( λ ( x , y , z ) → ( x ⋄ y ) ⋄ z ≡ x ⋄ ( y ⋄ z )) Note that there is some design leeway with respect to type class contexts. For il-lustration, we have distinguished here between the “essential” context
Monoid α ,declared on the type class and hence detected upon instantiation, and the “ac-cidental” context Eq α , declared on each method and hence detected upon use.The distinction may or may not be ambiguous in practice, however. Type-Level Ad-Hoc Programming
For more ad-hoc data structures, whereoperations are not organized as methods of a type class, but rather passed explic-itly to higher-order functions, or where extra laws are assumed locally, a likewiselooser style of meta-logic appears more adequate. Fortunately, there is no needto relinquish the assistance of the Haskell type and context checker altogether.A type class can be used to map symbolic names of laws, defined as constructorsof ad-hoc datatypes, to their logical content. class
Axiom α π | α → π where axiomatic :: α → Meta π In line with the previous example, an extra law that is not reflected by aHaskell type class can be defined and made referable by a singleton polymorphicdatatype. data
MonoidCommute α = MonoidCommute instance ( Monoid α, Eq α ) ⇒ Axiom ( MonoidCommute α ) (( α, α ) → Bool ) where axiomatic MonoidCommute = Meta ( λ ( x , y ) → x ⋄ y ≡ y ⋄ x ) A law for an ad-hoc data structure with explicitly passed operations is analo-gously defined as a record-like datatype. For instance consider a law of monoidactions (also cf. the type signature of foldl ): type RAction α β = β → α → β data RActionUnit α β = RActionUnit ( RAction α β ) nstance ( Monoid α, Eq β ) ⇒ Axiom ( RActionUnit α β ) ( β → Bool ) where axiomatic ( RActionUnit ( ⊳ )) = Meta ( λ x → x ⊳ mempty ≡ x ) data RActionCompose α β = RActionCompose ( RAction α β ) instance ( Monoid α, Eq β ) ⇒ Axiom ( RActionCompose α β ) ( β → α → α → Bool ) where axiomatic ( RActionCompose ( ⊳ )) = Meta ( λ x y z → x ⊳ ( y ⋄ z ) ≡ ( x ⊳ y ) ⊳ z ) For richer classification, type subclasses can be used to create ad-hoc subsetsof “axiom space”. This both adds to the documentation value of actual meta-level code, and protects against misuse of tactics. For instance, consider a classof
Int -parameterized meta-level expressions that need only be checked for non-negative parameter values: class ( Axiom α ( Int → β )) ⇒ NonNegAxiom α β
This subclass can be accompanied with an axiom-level operator, by giving aconstructor type and corresponding operational lifting: data
NonNeg α = NonNeg α instance ( NonNegAxiom α β ) ⇒ Axiom ( NonNeg α ) ( Int → β ) where axiomatic ( NonNeg a ) =
Meta ( reflect ( axiomatic a ) ◦ abs ) The restriction of the instance context to the subclass
NonNegAxiom ensuresapplication of this (generally unsafe) tactic only to axioms that have an explicitmembership declaration, which can serve as an anchor for individual justification,be it prose reasoning or checkable lemmata.
The natural logical reading of the type operator ( → ) is universal quantification.But existential quantification also often arises in formulas, either explicitly or byDeMorgan’s laws, when universal quantification occurs in a negative position,such as under negation or on the left hand side of implication.Checking existential quantification with the same sampling-based mecha-nisms as universal quantification would break the monotonicity of heuristics:For universal quantifiers, only false positives can arise if counterexamples existbut are not present in the sample. As such, confidence can only improve when thesample size is increased. By contrast, for existential quantifiers, false negativescan arise when witness exist but are not present in the sample. False negativesare at best annoying when they occur at the top level and raise false alarms, butat worst, when arising negatively nested in a complex formula, they can makeoverall confidence decrease with increasing sample size.Therefore we propose to treat existential quantification as entirely distinct,and in the true spirit of constructive logic, by effective Skolemization. To makean existential assertion checkable, a witness must be provided in an effectivelycomputable fashion. 9 lass Witness α β | α → β where witness :: α → Maybe β Here α is a data type that encodes the meta-logical predicate to quantify, and β is the domain to quantify over. The ad-hoc polymorphic operation witness mayyield Nothing to indicate that no witness could be found for the given predicateinstance. The extraction of a witness can then be composed with a payloadpredicate to form bounded existential quantifications. exists :: (
Witness α β ) ⇒ α → ( β → Bool ) → Boolexists p q = case witness p of Just x → q xNothing → False
Note that the exists itself quantifier is not marked with
Meta , as it is perfectlysuitable for use in the operational codebase layer as well. existsSome :: (
Witness α β ) ⇒ α → BoolexistsSome p = exists p ( const True ) existsOrVacuous :: ( Witness α β ) ⇒ α → ( β → Bool ) → BoolexistsOrVacuous p q = case witness p of Just x → q xNothing → True
We illustrate the use and impact of the checking idioms described above by ap-plying them to a conceptual problem arising from real-world software engineeringresearch: An algebraic theory of compositional patching.The generic level of the theory studies non-Abelian groups of patches actingpartially on some arbitrary state space. As a simple but illuminating exampleinstance, we consider the particular space of ordinary character strings, and agroup generated by atomic insert and delete operations and their evident seman-tics. Establishing the decidability of the word problem of this group is already anon-trivial modeling task, where the expressivity gained by our proposed check-ing idioms comes in handy for rapid validation.
The theoretical background for a type of patches π is its (right) action, a partialfunction on some state space σ . class Patch σ π where action :: σ → π → Maybe σ Application of patches can also be reverted. class ( Patch σ π ) ⇒ InvPatch σ π where undo :: σ → π → Maybe σ This should be an inverse operation where defined:10 ata
PatchInvert σ π = PatchInvert instance ( InvPatch σ π, Eq σ ) ⇒ Axiom ( PatchInvert σ π ) ( σ → π → Bool ) where axiomatic PatchInvert = Meta ( λ s p → case action s p of Nothing → TrueJust s ′ → undo s ′ p ≡ Just s ) If the patch type has a polarity , that is some internal form of inversion, thenthe forward direction action suffices to imply the backward direction undo . class Polar α where inv :: α → α instance ( Patch σ π,
Polar π ) ⇒ InvPatch σ π where undo x p = action x ( inv p ) The most important forms of patch types are group words , made up frompolarized primitives: data
Polarity = Positive | Negative instance
Polar Polarity where inv Positive = Negativeinv Negative = Positive data
Literal α = Literal Polarity α instance Polar ( Literal α ) where inv ( Literal b x ) =
Literal ( inv b ) x instance ( InvPatch σ α ) ⇒ Patch σ ( Literal α ) where action s ( Literal Positive p ) = action s paction s ( Literal Negative p ) = undo s p
Group words are essentially lists that polarize elementwise, but also reverse theirorder in the process, to accomodate for non-commutative groups. newtype
Word α = Word [ Literal α ] instance Polar ( Word α ) where inv ( Word w ) =
Word ( reverse ( map inv w )) They act in the obvious way by folding, strictly over the
Maybe monad. instance ( InvPatch σ α ) ⇒ Patch σ ( Word α ) where action s ( Word w ) = foldM action s w
As an example instance of the generic theory, consider the editing of a characterstring. Suitable partial invertible atomic edit operations are:11
Inserting a given character at a given position if that does not exceed theend of the string, and inversely – deleting a given character at a given position if it occurs there. data EditOp = Insert | Delete instance
Polar EditOp where inv Insert = Deleteinv Delete = Insert data
Edit = Edit { op :: EditOp , pos :: Int , arg :: Char } instance Polar Edit where inv ( Edit f i x ) =
Edit ( inv f ) i x The operational semantics are modeled effectively by a type class that interpretsthe two operations, giving rise to an action on some state space. class
Editable α where insert :: α → Int → Char → Maybe α delete :: α → Int → Char → Maybe α instance ( Editable σ ) ⇒ Patch σ Edit where action s ( Edit Insert i x ) = insert s i xaction s ( Edit Delete i x ) = delete s i x
The instance for the datatype
String implements the above informal intuition. instance
Editable String where insert s x = return ( x : s ) insert [ ] i x = Nothinginsert ( y : t ) i x = liftM ( y :) ( insert t ( i − x ) delete [ ] i x = Nothingdelete ( y : t ) 0 x = guard ( x ≡ y ) >> return tdelete ( y : t ) i x = liftM ( y :) ( delete t ( i − x ) Group words only form a free monoid, in the obvious way inherited from the listtype [ ], but partial applications of flip action induce a proper group of partialbijections on the state space. The extensional equality of induced group elements,and thus the word problem of the group presentation encoded in the action, isuniversally quantified, and thus hard to decide for large or even infinite statespaces. A heuristic evaluation would be sufficient as a meta-expression in simpleoffline checks, but not in negative positions, nor for online assertions, nor evenin the operational layer of the codebase, such as in model animations.This situation can be improved substantially by giving a semantic modelin the form of an algebraic datatype with inductively derived equality, which12s fully abstract in the sense that it admits a normal form where extensionallyequal semantic functions are represented by the same data value.For the example theory considered here, there is such a normal form of string-transducing automata. Because these automata do not require circular transi-tions, they can be modeled by a family of mutually linearly recursive datatypes,and evaluated by straightforward recursion. data
Editor = Try Insertion | Fail data
Insertion = Ins String Consumption data
Consumption = Skip Insertion | Del Char Insertion | Return
The operational idea is to apply each operator node to a position in an inputstring, advancing left to right. The detailed meaning of operators is as follows:
Fail
Applies to no string at all, immediately reject.
Try
Applies to some strings, begin processing at start position.
Ins
Insert zero or more characters before the position.
Skip
Advance the position over one character if available, otherwise reject.
Del
Remove the next character if available and matched, otherwise reject.
Return
Stop processing and accept, returning the remainder of the string as is.
The sorting of operators into different data types ensures that insertion andconsumption alternate properly.Note that, unlike random-access
Edit terms, subsequent operator nodes areonly ever applied to the original input string, not to the output of their pre-decessors. This is also the cause for the
Skip and
Fail operators which do notappear in the
Edit language; they arise from attempting to delete a previouslyinserted character consistently and inconsistently, respectively.The type
Insertion is not to be constructed directly, but by the followingsmart constructor that avoids a degenerate corner case: Namely, insertions be-fore and after a deletion are operationally indistinguishable. This ambiguity isavoided by avoiding insertions after a deletion, lumping adjacent insertions to-gether beforehands, which preserves the desired normal form. ins :: String → Consumption → Insertionins pre ( Del y ( Ins fix next )) =
Ins ( pre ++ fix ) ( Del y ( Ins [ ] next )) ins prefix next = Ins prefix next
The semantic model type
Editor covers the middle ground between the syn-tactic encoding
Word Edit and the semantic state space
String , in the sensethat it instantiates both
Editable and
Patch String , giving the respective effec-tive connections. The latter is the simpler one of the pair, and implements theintuition stated above. instance
Patch String Editor where action s Fail = Nothing ction s ( Try steps ) = action s steps instance
Patch String Insertion where action s ( Ins prefix next ) = do t ← action s nextreturn ( prefix ++ t ) instance Patch String Consumption where action s Return = return saction s ( Skip rest ) = do ( x , t ) ← uncons su ← action t restreturn ( x : u ) action s ( Del y rest ) = do ( x , t ) ← uncons su ← action t restguard ( x ≡ y ) return u The instantiation of
Editable essentially amounts to splicing a single editoperation into an automaton while preserving the normal form. The technicaldetails are too gruesome to be presented here in full. instance
Editable Editor · · · instance
Editable Insertion · · · instance
Editable Consumption · · ·
This instantiation implies an instance of
Patch Editor Edit , which can be liftedto group words by folding over the
Maybe monad. semantics :: Word Edit → Editorsemantics = fromMaybe ◦ foldM action done ◦ toListdone :: Insertiondone = Ins [ ]
ReturnfromMaybe :: Maybe Insertion → EditorfromMaybe = maybe Fail Try The adequacy of the semantics can be stated concisely in terms of two propo-sitions for soundness and full abstraction, respectively. semantics sound :: Meta ( Word Edit → String → Bool ) semantics sound = foreach ( λ x → patch eq x ( semantics x )) semantics abstract :: Meta (( Editor , Editor ) → Bool ) semantics abstract = foreach ( uncurry cons eq ) The former uses extensional equivalence under action in positive position, hencethe universal quantifiers can be nested, and sampled together at checking time. patch eq :: (
Patch σ α,
Patch σ β, Eq σ ) ⇒ α → β → Meta ( σ → Bool ) patch eq x y = Meta ( λ s → action s x ≡ action s y ) By contrast, the latter conceptually uses extensional equivalence in negativeposition: “if two automata are extensionally equivalent then they are equal”.This form of quantification cannot be approximated monotonically by sampling.14ence a constructive solution for the DeMorganized corresponding existential isrequired; if two automata are extensionally inequivalent, then a witness state forwhich they fail to coincide must be found. cons eq :: ( Eq α, Witness ( Diff α ) β ) ⇒ α → α → Meta Boolcons eq t u = Meta ( t ≡ u ∨ existsSome ( t : u )) The operator : of constructive logic is conveniently defined as the constructorof a new datatype Diff , since it requires an ad-hoc instance of
Witness to holdeach construction algorithm. data
Diff α = (: ) α α It turns out that a straightforward algorithm requires three auxiliary construc-tive predicates, which bear witness that an automaton accepts some input, thatan automaton rejects some input, and that one automaton accepts an inputwhereas another one rejects it, respectively. data
Def α = Def α data Undef α = Undef α data DefUndef α = (: > ) α α The implementations are too complex to be discussed here in detail. instance
Witness ( Diff Editor ) String · · · instance
Witness ( Def Editor ) String · · · instance
Witness ( Undef Editor ) String · · · instance
Witness ( DefUndef Editor ) String · · ·
However, the intended semantics can be specified precisely, and checked, in aself-application of the meta-logical language. def sound complete :: Meta ( Editor → Bool ) def sound complete = Meta ( λ x → x ≡ Fail ∨ exists ( Def x ) ( λ s → isJust ( action s x ))) undef sound complete :: Meta ( Editor → Bool ) undef sound complete = Meta ( λ x → isTotal x ∨ exists ( Undef x ) ( λ s → ¬ ( isJust ( action s x )))) def undef sound :: Meta (( Editor , Editor ) → Bool ) def undef sound = Meta ( λ ( x , y ) → existsOrVacuous ( x : > y ) ( λ s → isJust ( action s x ) ∧¬ ( isJust ( action s y )))) diff sound complete :: Meta (( Editor , Editor ) → Bool ) diff sound complete = Meta ( λ ( x , y ) → x ≡ y ∨ exists ( x : y ) ( λ s → action s x action s y )) Note that the target semantics abstract is a consequence of diff sound complete a fortiori already, but there is no obvious way to exploit that logical relationshipin a checking framework. 15ow we can operationalize the word problem by comparing automata, ( ∼ =) :: Word Edit → Word Edit → Boolx ∼ = y = semantics x ≡ semantics y and conclude for instance that fromList [ Edit Insert ’a’ , Edit Delete ’b’ ] ∼ = fromList [ Edit Delete ’b’ , Edit Insert ’a’ ]. The above examples let us have a glimpse at the power of recursive tactics avail-able in an embedded higher-order logical language: Equational reasoning aboutthe data to be modeled is reduced to the word problem of a group presenta-tion, an extensional property quantified over all possible inputs that can onlybe checked heuristically and positively. For broader checkability, this problemis factored through a normalizable automaton representation. The soundnessand full abstraction of this semantics, and thus the equivalence of its equa-tional reasoning, are checkable properties that contain existential quantification,for which constructive witnesses are given. The correctness and completenessof these constructions are universally quantified properties again, which can bechecked heuristically.Note that this does not mean we are going in circles; the correctness of thesemantics needs only to be established once, and can be used as a shortcut fordeciding equations of the original model henceforth. Yet the same language, toolsand workflow are used for all phases.
We have proposed three advanced features of meta-logical language for offlinechecking of functional programs, namely meta-level marking, nominal axiomat-ics and constructive existentials. We have shown their implementation in theHaskell checking framework PureCheck, and demonstrated their use and inter-action by means of a nontrivial executable modeling problem. Other aspects ofPureCheck, such as ensuring the efficiency of deterministic sampling, are bothwork in progress and out of scope here, and shall be discussed in a forthcomingcompanion paper.Dialectically, the stylistic ideal that underlies our experiments is contraryto the one employed in the construction of this paper: The checking paradigmexpresses reasoning about the program in (a marked level of) the code, as op-posed to the prose embellishment of the literate paradigm. We are hopeful thata thorough synthesis of the two can be demonstrated as synergetic and useful inthe future.Expressive offline checking language is an important step towards the reifica-tion of the algebraic concepts that pervade functional program design; considerthe ubiquitous informal equational theory associated with Haskell type classes.16arking the
Meta level explicitly has not only the demonstrated advantagesfor the human reader, but may also serve as an anchor for meta-programmingprocedures, such as automatic test suite extraction without magic names.The concept of constructive existentials is an explicitly controlled counter-part to implicit search strategies provided by the logical programming paradigm.Unlike SmartCheck, where constructive existentials are dismissed for often be-ing hard to find in practice, we contend that in a (self-)educational contextsuch as executable modeling, the understanding gained by implementing theconstruction witnesses for existential meta-logical properties of interest is re-warding rather than onerous. Furthermore foresee interesting potential in thetransfer of our ideas to a functional–logic language such as Curry [8] with built-in encapsulated search capabilities, but leave the exploration for future work.
References
1. John Backus. “Can Programming Be Liberated From the von Neumann Style?A Functional Style and Its Algebra of Programs”. In: Comm. ACM 21.8 (1978),pp. 613–641.2. Olaf Chitil and Frank Huch. “A Pattern Logic for Prompt Lazy Assertions inHaskell”. In: IFL 2006. Vol. 4449. Lecture Notes in Computer Science. Springer,2007. doi : 10.1007/978-3-540-76637-7 4.3. Koen Claessen and John Hughes. “QuickCheck: A Lightweight Tool for RandomTesting of Haskell Programs”. In: SIGPLAN Not. 35.9 (Sept. 2000), pp. 268–279. doi : 10.1145/357766.351266.4. Lori A. Clarke and David S. Rosenblum. “A historical perspective on runtime asser-tion checking in software development”. In: ACM SIGSOFT Software EngineeringNotes 31.3 (2006), pp. 25–37. doi : 10.1145/1127878.1127900.5. Jonathan Cooper, Jon Olav Vik, and Dagmar Waltemath. “A call for virtual ex-periments: accelerating the scientific process”. In: PeerJ PrePrints (2014). doi :10.7287/peerj.preprints.273v1.6. Thierry Coquand and G´erard Huet. “The calculus of constructions”.In: Information and Computation 76 (2–3 1988), pp. 95–120. doi :10.1016/0890-5401(88)90005-3.7. George Fink and Matt Bishop. “Property-Based Testing; A New Approach toTesting for Assurance”. In: Software Engineering Notes 22.4 (1997), pp. 74–80. doi : 10.1145/263244.263267.8. Michael Hanus. Curry: A Truly Integrated Functional Logic Language. 2014. url : .9. Ulf Norell. “Towards a practical programming language based on dependent typetheory”. PhD thesis. Chalmers University of Technology, 2007.10. Simon Peyton Jones. “Literate comments”. In: The Haskell 98 Report. 2002. url : .11. Colin Runciman, Matthew Naylor, and Fredrik Lindblad. “Smallcheck and lazysmallcheck: automatic exhaustive testing for small values”. In: Haskell ’08: Pro-ceedings of the first ACM SIGPLAN symposium on Haskell. 2008, pp. 37–48. doi :10.1145/1411286.1411292.:10.1145/1411286.1411292.