• No se han encontrado resultados

4.3 Subestación de transformación proyectada

4.3.2 Subestación de transformación

Another interesting example is regular expressions with dependent types that express which predicates over strings particular regexps implement. We can then assign a dependent type to a regular expression matching function, guaranteeing that it always decides the string property that we expect it to decide.

Before defining the syntax of expressions, it is helpful to define an inductive type cap- turing the meaning of the Kleene star. That is, a string s matches regular expression star

e if and only if s can be decomposed into a sequence of substrings that all match e. We use Coq’s string support, which comes through a combination of the Stringlibrary and some parsing notations built into Coq. Operators like ++ and functions like length that we know from lists are defined again for strings. Notation scopes help us control which versions we want to use in particular contexts.

Require Import Ascii String. Open Scope string scope. Sectionstar.

VariableP : string → Prop. Inductivestar : string →Prop :=

|Empty : star "" |Iter : ∀ s1 s2, P s1star s2star (s1 ++ s2). End star.

Now we can make our first attempt at defining aregexptype that is indexed by predicates on strings, such that the index of a regexp tells us which language (string predicate) it recognizes. Here is a reasonable-looking definition that is restricted to constant characters and concatenation. We use the constructor String, which is the analogue of list cons for the type string, where "" is like list nil.

Inductive regexp : (string → Prop) →Set :=

| Char : ∀ ch : ascii,

regexp(fun ss =String ch "")

| Concat: ∀ (P1 P2 : string →Prop) (r1 : regexp P1) (r2 : regexp P2), regexp(fun s ⇒ ∃ s1, ∃ s2, s = s1 ++ s2P1 s1P2 s2).

User error: Large non-propositional inductive types must be in Type

What is a large inductive type? In Coq, it is an inductive type that has a constructor that quantifies over some type of type Type. We have not worked with Type very much to

this point. Every term of CIC has a type, including Setand Prop, which are assigned type Type. The type string → Prop from the failed definition also has type Type.

It turns out that allowing large inductive types in Set leads to contradictions when combined with certain kinds of classical logic reasoning. Thus, by default, such types are ruled out. There is a simple fix for our regexp definition, which is to place our new type in Type. While fixing the problem, we also expand the list of constructors to cover the remaining regular expression operators.

Inductive regexp : (string → Prop) →Type :=

| Char : ∀ ch : ascii,

regexp(fun ss = String ch "")

| Concat: ∀ P1 P2 (r1 : regexp P1) (r2 : regexp P2),

regexp(fun s ⇒ ∃ s1, ∃s2, s = s1 ++ s2P1 s1P2 s2)

| Or : ∀P1 P2 (r1 : regexp P1) (r2 : regexpP2), regexp(fun sP1 sP2 s)

| Star : ∀P (r : regexp P), regexp(star P).

Many theorems about strings are useful for implementing a certified regexp matcher, and few of them are in the String library. The book source includes statements, proofs, and hint commands for a handful of such omitted theorems. Since they are orthogonal to our use of dependent types, we hide them in the rendered versions of this book.

A few auxiliary functions help us in our final matcher definition. The function splitwill be used to implement the regexp concatenation case.

Sectionsplit.

VariablesP1 P2 : string → Prop.

VariableP1 dec : ∀ s,{P1 s} + {¬ P1 s}. VariableP2 dec : ∀ s,{P2 s} + {¬ P2 s}.

We require a choice of two arbitrary string predicates and functions for deciding them. Variables : string.

Our computation will take place relative to a single fixed string, so it is easiest to make it a Variable, rather than an explicit argument to our functions.

The functionsplit’is the workhorse behindsplit. It searches through the possible ways of splittings into two pieces, checking the two predicates against each such pair. The execution of split’ progresses right-to-left, from splitting all of s into the first piece to splitting all of s

into the second piece. It takes an extra argument, n, which specifies how far along we are in this search process.

Definition split’ : ∀ n : nat, n ≤length s

→ {∃ s1, ∃s2, length s1ns1 ++ s2 = sP1 s1P2 s2} + {∀ s1 s2, lengths1ns1 ++ s2 = s → ¬ P1 s1 ∨ ¬ P2 s2}. refine (fix F (n : nat) : n ≤ lengths

→{∃ s1, ∃ s2, length s1ns1 ++ s2 = sP1 s1P2 s2} + {∀ s1 s2, length s1ns1 ++ s2 = s →¬ P1 s1 ∨ ¬P2 s2} :=

match n with

| O ⇒ fun ⇒ Reduce (P1 dec "" && P2 dec s)

| S n’ ⇒fun ⇒ (P1 dec (substring0 (Sn’)s) && P2 dec (substring (Sn’) (length s - Sn’)s)) || F n’

end); clear F; crush; eauto 7; match goal with

| [ : length ?S ≤0 ` ] ⇒destruct S

| [ : length ?S’ ≤ S?N ` ]⇒ destruct (eq nat dec (length S’) (SN)) end; crush.

Defined.

There is one subtle point in the split’ code that is worth mentioning. The main body of the function is a match on n. In the case where n is known to be S n’, we write S n’

in several places where we might be tempted to write n. However, without further work to craft proper matchannotations, the type-checker does not use the equality betweenn andS

n’. Thus, it is common to see patterns repeated in match case bodies in dependently typed Coq code. We can at least use a let expression to avoid copying the pattern more than once, replacing the first case body with:

| S n’ ⇒fun ⇒ letn :=S n’ in (P1 dec (substring 0 n s)

&& P2 dec (substring n (length s - n) s)) || F n’

The split function itself is trivial to implement in terms of split’. We just ask split’ to begin its search with n =length s.

Definition split : {∃ s1, ∃ s2, s = s1 ++ s2P1 s1P2 s2} + {∀ s1 s2, s = s1 ++ s2 → ¬ P1 s1 ∨ ¬P2 s2}.

refine (Reduce (split’ (n := lengths) )); crush; eauto. Defined.

End split.

Implicit Arguments split [P1 P2].

One more helper function will come in handy: dec star, for implementing another linear search through ways of splitting a string, this time for implementing the Kleene star. Sectiondec star.

VariableP : string → Prop.

VariableP dec : ∀ s,{P s} + {¬ P s}.

Some new lemmas and hints about the star type family are useful. We omit them here; they are included in the book source at this point.

The function dec star’’ implements a single iteration of the star. That is, it tries to find a string prefix matchingP, and it calls a parameter function on the remainder of the string.

Sectiondec star’’. Variable n : nat.

Variable n is the length of the prefix of s that we have already processed. Variable P’ :string → Prop.

Variable P’ dec : ∀n’ : nat, n’ > n

→{P’ (substring n’ (length s - n’)s)} + {¬ P’ (substring n’ (length s - n’) s)}.

When we use dec star’’, we will instantiate P’ dec with a function for continuing the search for more instances of P in s.

Now we come to dec star’’ itself. It takes as an input a natural l that records how much of the string has been searched so far, as we did for split’. The return type expresses that dec star’’is looking for an index into s that splitss into a nonempty prefix and a suffix, such that the prefix satisfies P and the suffix satisfies P’.

Definition dec star’’ : ∀ l : nat, {∃ l’, S l’l

P (substring n (S l’) s) ∧ P’ (substring (n + S l’) (length s - (n + Sl’)) s)} + {∀ l’,S l’l

→ ¬ P (substring n (S l’) s)

∨ ¬ P’ (substring (n + Sl’) (length s - (n + S l’)) s)}. refine (fixF (l : nat) : {∃ l’, S l’l

P (substringn (Sl’) s)∧ P’ (substring(n + S l’) (length s - (n + S l’))s)} + {∀l’, S l’l → ¬P (substring n (Sl’)s) ∨ ¬P’ (substring (n + S l’) (length s - (n + Sl’)) s)} := match l with | O⇒ | Sl’

(P dec (substring n (S l’)s) && P’ dec (n’ := n + S l’) ) || F l’

end);clear F; crush; eauto 7; match goal with

| [H : ?X ≤S ?Y ` ] ⇒destruct (eq nat dec X (S Y));crush

end. Defined. Enddec star’’.

The work of dec star’’ is nested inside another linear search bydec star’, which provides the final functionality we need, but for arbitrary suffixes ofs, rather than just for s overall.

Definition dec star’ : ∀n n’ : nat, length s - n’n

→ {star P (substringn’ (length s - n’) s)} + {¬ star P (substringn’ (length s - n’) s)}. refine (fix F (n n’ : nat) : length s - n’n

→{star P (substring n’ (length s - n’)s)} + {¬ star P (substring n’ (length s - n’)s)} := match n with

| O ⇒ fun ⇒ Yes

| S n’’ ⇒ fun ⇒

le gt dec(length s) n’

|| dec star’’ (n :=n’) (star P)

(fun n0 ⇒ Reduce (F n’’ n0 )) (length s - n’) end); clear F; crush; eauto;

match goal with

| [H : star ` ] ⇒ apply star substring inv in H; crush; eauto end;

match goal with

| [H1 : < - ,H2 : ∀ l’ : nat, ≤ - → ` ] ⇒

generalize (H2 (lt le S H1));tauto end.

Defined.

Finally, we have dec star, defined by straightforward reduction fromdec star’. Definition dec star: {star P s} + {¬ star P s}.

refine (Reduce (dec star’ (n := length s) 0 )); crush. Defined.

End dec star.

With these helper functions completed, the implementation of our matches function is refreshingly straightforward. We only need one small piece of specific tactic work beyond what crush does for us.

Definition matches : ∀ P (r : regexp P) s, {P s} + {¬P s}. refine (fixF P (r : regexp P) s : {P s} + {¬ P s} :=

match r with

| Char ch ⇒string dec s (String ch "")

| Concat r1 r2 ⇒ Reduce (split (F r1) (F r2) s)

| Or r1 r2F r1 s || F r2 s

| Star r ⇒dec star end);crush;

match goal with

| [ H : ` ] ⇒ generalize (H (eq refl )) end; tauto.

Defined.

It is interesting to pause briefly to consider alternate implementations of matches. De- pendent types give us much latitude in how specific correctness properties may be encoded with types. For instance, we could have made regexp a non-indexed inductive type, along the lines of what is possible in traditional ML and Haskell. We could then have implemented

a recursive function to mapregexps to their intended meanings, much as we have done with types and programs in other examples. That style is compatible with the refine-based approach that we have used here, and it might be an interesting exercise to redo the code from this subsection in that alternate style or some further encoding of the reader’s choice. The main advantage of indexed inductive types is that they generally lead to the smallest amount of code.

Many regular expression matching problems are easy to test. The reader may run each of the following queries to verify that it gives the correct answer. We use evaluation strategy hnfto reduce each term tohead-normal form, where the datatype constructor used to build its value is known. (Further reduction would involve wasteful simplification of proof terms justifying the answers of our procedures.)

Examplea star :=Star (Char "a"%char). Eval hnf in matches a star "".

Eval hnf in matches a star "a". Eval hnf in matches a star "b". Eval hnf in matches a star "aa".

Evaluation inside Coq does not scale very well, so it is easy to build other tests that run for hours or more. Such cases are better suited to execution with the extracted OCaml code.

Chapter 9

Dependent Data Structures

Our red-black tree example from the last chapter illustrated how dependent types enable static enforcement of data structure invariants. To find interesting uses of dependent data structures, however, we need not look to the favorite examples of data structures and algo- rithms textbooks. More basic examples like length-indexed and heterogeneous lists come up again and again as the building blocks of dependent programs. There is a surprisingly large design space for this class of data structure, and we will spend this chapter exploring it.

Documento similar