1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391
|
From Coq Require Import Init.Nat List.
Import ListNotations.
From QuickChick Require Import QuickChick.
Import QcNotation. Import QcDefaultNotation.
Require Import Coq.Strings.Ascii.
Open Scope qc_scope.
Open Scope nat_scope.
(* First of all, we need to import the proof-related modules, as they
are not exported by default. *)
From QuickChick Require Import EnumProofs CheckerProofs.
(** Regexp Matching *)
(* This example is taken from the Logical Foundations Volume of Software Foundations textbook *)
Definition string := list ascii.
(* We start with an inductive definion of the regular expression data type *)
Inductive reg_exp (T : Type) : Type :=
| EmptySet
| EmptyStr
| Char (t : T)
| App (r1 r2 : reg_exp T)
| Union (r1 r2 : reg_exp T)
| Star (r : reg_exp T).
(* We can then derive an enumerator for inhabitants of this data type *)
Derive EnumSized for reg_exp.
(* Prints: EnumSizedreg_exp is defined *)
About EnumSizedreg_exp.
(* EnumSizedreg_exp : forall {T : Type}, Enum T -> EnumSized (reg_exp T) *)
(* The [EnumSizedreg_exp] definition is a function that for each type
T that can be enumerated (reflected in the typeclass constrain
[Enum T]), returns an Instance of the typeclass [EnumSized]. *)
(* Let's look more closely at these two typeclasses.
First, what is an enumerator?
The enumerator monad
--------------------
Inductive EnumType (A : Type) : Type :=
MkEnum : (nat -> LazyList A) -> EnumType A
The type of enumerators is [EnumType] (or for short, just [E]).
This type encapsulates a function of type [nat -> LazyList A].
The [nat] parameter is an upper bound for the enumeration process.
The return type is a lazy list that contains all inhabitants of
type A up to the given size.
The Enum typeclass
------------------
Class Enum (A : Type) : Type := { enum : E A }
The [Enum] typeclass then is the class of all types that have an enumerator.
The EnumSized typeclass
-----------------------
The [EnumSized] typeclass is similar.
Class EnumSized (A : Type) := { enumSized : nat -> E A }.
The difference is that the enumerators of the EnumSized class have an
additional [nat] parameter that is commonly used to bound the recursion
depth.
From EnumSized to Enum
----------------------
We can go from [EnumSized] to [Enum] using the [sized] combinator
that internalizes the size parameter of [EnumSized]. Given an
[EnumSized] instance, we can automatically derive an [Enum]
instance with typeclass resolution.
*)
(* After automatically generating the [EnumSized] instance, we can
generate correctness proofs. To do this proof we first define a
"set-of-outcomes" semantics for our enumerator.
In particular, the combinator [semEnumSize], with signature:
semEnumSize : forall {A : Type}, E A -> nat -> set A
maps an enumerator of type A to set indexed by a size. This is the
set of element that can be generated for each value of the internal
size parameter. We will use this to state properties of enumerators.
Before generating correctness we need some crucial monotonicity
properties.
Monotonicity in the external size parameter
--------------------------------------
First, we prove that the enumerator is monotonic in the external size
parameter. That is,
forall s s1 s2 : nat,
s1 <= s2 -> semProdSize (enumSized s) s1 \subset (enumSized s) s2
This property is captured by the [SizeMonotonic] typeclass. Again.
we automatically instances of these typeclass.
*)
#[local] Instance EnumSizedreg_exp_SizedMonotonic T {_ : Enum T} :
SizedMonotonic (@enumSized _ (@EnumSizedreg_exp T _)).
Proof. derive_enum_SizedMonotonic. Qed.
(* Monotonicity in the internal size parameter
--------------------------------------
We also prove that the enumerator is monotonic in the internal size
parameter. That is,
forall s s1 s2 : nat,
s1 <= s2 -> semEnumSize (enumSized s1) s \subset semEnumSize (enumSized s2) s
This property is captured by the [SizeMonotonic] typeclass. We automatically
derive instances of this typeclass using our Ltac2 proofscript
[derive_enum_SizedMonotonic].
*)
#[local] Instance EnumSizedreg_exp_SizeMonotonic T `{EnumMonotonic T}:
forall s, SizeMonotonic (@enumSized _ (@EnumSizedreg_exp T _) s).
Proof. derive_enum_SizeMonotonic. Qed.
(* Correctness
-----------
We use the two monotonicity properties to prove correctness.
For simple enumerators like this one, correctness states:
{ x | exists s s', x \in semEnumSize (enumSized s) s' } <--> [set: A]
That is, the set of elements that belongs to [semEnumSize (enumSized s) s']
for some size parameters s (external) and s' (internal), is exactly the set
of elements of type A (denoted [set: A]). The notation <--> denotes set
equality.
*)
#[local] Instance EnumSizedreg_expCorrect T `{EnumMonotonicCorrect T}:
CorrectSized (@enumSized _ EnumSizedreg_exp).
Proof. derive_enum_Correct. Qed.
(* Now that we have seen how enumeration of simple data types works,
we can move on to enumeration and checking of inductive relations.
We focus on regular expression matching.
*)
Reserved Notation "s =~ re" (at level 80).
Definition app := @app.
Arguments EmptySet {T}.
Arguments EmptyStr {T}.
Arguments Char {T} _.
Arguments App {T} _ _.
Arguments Union {T} _ _.
Arguments Star {T} _.
(* The following inductive relation holds whenever a string of
characters drawn from a set [T] matches a regular expression.
*)
Inductive exp_match {T: Type} : list T -> reg_exp T -> Prop :=
| MEmpty : [] =~ EmptyStr
| MChar x : [x] =~ (Char x)
| MApp s1 re1 s2 re2 :
s1 =~ re1 ->
s2 =~ re2 ->
s1 ++ s2 =~ (App re1 re2)
| MUnionL s1 re1 re2 :
s1 =~ re1 ->
s1 =~ (Union re1 re2)
| MUnionR re1 s2 re2 :
s2 =~ re2 ->
s2 =~ (Union re1 re2)
| MStar0 re : [] =~ (Star re)
| MStarApp s1 s2 re :
s1 =~ re ->
s2 =~ (Star re) ->
s1 ++ s2 =~ (Star re)
where "s =~ re" := (exp_match s re).
(** Checkers *)
(* First, we can generate a checker for [exp_match]. *)
(*
Instance EnumTest (y : nat) : EnumSizedSuchThat _ (fun x => eq x y).
Admitted.
Instance Dec (x y : nat) : DecOpt (x = y).
Admitted.
*)
Derive DecOpt for (exp_match l e).
(* DecOptexp_match is defined *)
Derive EnumSizedSuchThat for (fun l => exp_match l e).
About DecOptexp_match.
(* DecOptexp_match :
forall {T : Type},
Dec_Eq T ->
Enum T ->
forall (l : list T) (e : reg_exp T), DecOpt (l =~ e) *)
(* That is, for all types [T] that have decidable equality and are
enumerable (this constraint is always present even though the
checker does now always use it), for all inputs [l] and [e], we can
decide weather the string [l] matches the regular expression [e].
Checkers are described with the DecOpt typeclass.
The DecOpt typeclass
--------------------
Class DecOpt (P : Prop) := { decOpt : nat -> option bool }.
This typeclass describes [Prop]'s that have a semi-decision
procedures of type [nat -> option bool]. The natural number
is the fuel that bounds the recursion depth of the generated
procedure. Conceptually, the procedure can decide the validity
of [Prop]'s whose proof derivation have depth less or equal to
this number.
*)
(* We can now prove correctness of the derived checker.
Monotonicity
------------
As before, we need to show monotonicity. In particular, we show that
if the validity of a proposition has been decided, then the decision
will not change by providing more fuel. In particular:
forall (s1 s2 : nat) (b : bool),
s1 <= s2 -> decOpt s1 = Some b -> decOpt s2 = Some b
This is capture by the [DecOptSizeMonotonic] typeclass. As before, an instance
is derived automatically.
*)
#[local] Instance DecOptexp_match_monotonic {T} `{_ : Dec_Eq T} `{_ : EnumMonotonic T} (m : list T) n :
DecOptSizeMonotonic (exp_match m n).
Proof. derive_mon. Qed.
(* Soundness and Completeness
--------------------------
Using monotonicity we can prove soundness and completeness.
Soundness states:
forall (P : Prop) (H : DecOpt P) (s : nat), decOpt s = Some true -> P
That is, is [decOpt s] is [true] for some [s], then [P] holds.
It is captured by the [DecOptSoundPos] typeclass.
Completeness states:
forall (P : Prop) (H : DecOpt P), P -> exists s : nat, decOpt s = Some true
That is, if P holds then there exists some fuel [s] such that [decOpt s] is true.
Again, we derive these instances automatically. *)
#[local] Instance DecOptexp_match_sound {T} `{_ : Dec_Eq T} `{_ : EnumMonotonicCorrect T} (m : list T) n :
DecOptSoundPos (exp_match m n).
Proof. derive_sound. Qed.
#[local] Instance DecOptexp_match_complete {T} `{_ : Dec_Eq T} `{_ : EnumMonotonicCorrect T} (m : list T) n :
DecOptCompletePos (exp_match m n).
Proof. derive_complete. Qed.
(* Enumeration for the Eq predicate (TODO move) (required by [exp_match]) *)
Derive EnumSizedSuchThat for (fun n => eq x n).
#[local] Instance EnumSizedSuchThateq_SizedMonotonic X {_ : Enum X} {_ : Dec_Eq X} (n : X) :
SizedMonotonicOptFP (@enumSizeST _ _ (EnumSizedSuchThateq n)).
Proof. derive_enumST_SizedMonotonicFP. Qed.
#[local] Instance EnumSizedSuchThateq_SizeMonotonic X `{_ : EnumMonotonic X} {_ : Dec_Eq X} (n : X) :
forall s, SizeMonotonicOptFP (@enumSizeST _ _ (EnumSizedSuchThateq n) s).
Proof. derive_enumST_SizeMonotonicFP. Qed.
(* TODO: FIX:
#[local] Instance EnumSizedSuchThateq_Correct X `{_ : EnumMonotonicCorrect X} `{_ : Dec_Eq X} (n : X) :
CorrectSizedST (fun m => eq n m) (@enumSizeST _ _ (EnumSizedSuchThateq n)).
Proof. derive_enumST_Correct. Qed.
*)
(* We can also derive an enumerator that given a regular expression, enumerates
all strings that match the regular expression.
*)
Derive EnumSizedSuchThat for (fun l => exp_match l e).
(* EnumSizedSuchThatexp_match is defined. *)
About EnumSizedSuchThatexp_match.
(*
EnumSizedSuchThatexp_match :
forall {T : Type},
Dec_Eq T ->
Enum T ->
forall e : reg_exp T, EnumSizedSuchThat (list T) (fun l : list T => l =~ e)
*)
(* For all types [T] that have decidable equality and are enumerable, and for all
input regular expressions [e], we derive an enumerator.
The [EnumSizedSuchThat] typeclass
---------------------------------
[EnumSizedSuchThat] is similar to [EnumSized] but it is also parameterized by
a predicate that the enumerated elements must satisfy.
Class EnumSizedSuchThat (A : Type) (P : A -> Prop) := { enumSizeST : nat -> E (option A) }
The type of the enumerator is [nat -> E (option A)]. Again, it has a [nat] parameter
that bounds the recursion depth. The type of the enumerator is [E (option A)].
The semantics of [None] in the output of enumerator is that enumeration is not
complete and there might be more elements that satisfy the predicate.
*)
(* As with the simple enumeration, before deriving correctness, we need to derive
monotonicity. *)
#[local] Instance EnumSizedSuchThatexp_match_SizedMonotonic {T} `{_ : Dec_Eq T} `{_ : EnumMonotonic T} e:
SizedMonotonicOptFP (@enumSizeST _ _ (EnumSizedSuchThatexp_match e)).
Proof. derive_enumST_SizedMonotonicFP. Qed.
#[local] Instance EnumSizedSuchThatexp_match_SizeMonotonic {T} `{_ : Dec_Eq T} `{_ : EnumMonotonic T} e :
forall s, SizeMonotonicOptFP (@enumSizeST _ _ (EnumSizedSuchThatexp_match e) s).
Proof. derive_enumST_SizeMonotonicFP. Qed.
(* Correctness
-----------
Correctness of enumerators states that all elements that are
generated satisfy the predicate (soundness) and that all elements
that satisfy the predicate can be enumerated. Using the set of outcomes
semantics, this can be states as:
{ x | exists s s', Some x \in semEnumSize (enumSizeST s) s' } <--> { x | P x }
This is captured by the [CorrectSizedST] typeclass. We derive this instance automatically.
*)
(* TODO: Fix:
#[local] Instance EnumSizedSuchThatexp_match_Correct {T} `{_ : Dec_Eq T} `{_ : EnumMonotonicCorrect T} e :
CorrectSizedST (fun l => exp_match l e) (@enumSizeST _ _ (EnumSizedSuchThatexp_match e)).
Proof. derive_enumST_Correct. Qed.
*)
(* Νote that we can also generate a enumerator for all the regular
expressions that can match a string (i.e., the input is the string
and the output is the regular expression)
*)
Derive EnumSizedSuchThat for (fun e => exp_match l e).
|