From e101b1a20c58268afd6cc27e5d4724470e823457 Mon Sep 17 00:00:00 2001 From: stefan-aws Date: Thu, 14 Dec 2023 02:36:15 +0000 Subject: [PATCH 01/62] draft --- ...-semantics-of-regular-expressions.markdown | 351 ++++++++++++++++++ .../Expressions.dfy | 28 ++ .../Languages.dfy | 222 +++++++++++ .../Semantics.dfy | 244 ++++++++++++ 4 files changed, 845 insertions(+) create mode 100644 _posts/2024-01-10-semantics-of-regular-expressions.markdown create mode 100644 assets/src/semantics-of-regular-expressions/Expressions.dfy create mode 100644 assets/src/semantics-of-regular-expressions/Languages.dfy create mode 100644 assets/src/semantics-of-regular-expressions/Semantics.dfy diff --git a/_posts/2024-01-10-semantics-of-regular-expressions.markdown b/_posts/2024-01-10-semantics-of-regular-expressions.markdown new file mode 100644 index 0000000..688d869 --- /dev/null +++ b/_posts/2024-01-10-semantics-of-regular-expressions.markdown @@ -0,0 +1,351 @@ +--- +layout: post +title: "Well-Behaved (Co)algebraic Semantics of Regular Expressions in Dafny" +date: 2024-01-10 18:00:00 +0100 +author: Stefan Zetzsche, Wojciech Rozowski +--- + +## Introduction + +[Regular expressions](https://en.wikipedia.org/wiki/Regular_expression) are one of the most ubiquitous formalisms of theoretical computer science. Commonly, they are understood in terms of their [*denotational* semantics](https://en.wikipedia.org/wiki/Denotational_semantics), that is, through the formal languages — the [*regular* languages](https://en.wikipedia.org/wiki/Regular_language) — that they induce. This view is *inductive* in nature: two primitives are equivalent if they are *constructed* in the same way. Alternatively, regular expressions can be understood in terms of their [*operational* semantics](https://en.wikipedia.org/wiki/Operational_semantics), that is, through the [finite automata](https://en.wikipedia.org/wiki/Finite-state_machine) they induce. This view is *coinductive* in nature: two primitives are equivalent if they are *deconstructed* in the same way. It is is hinted at by [Kleene’s famous theorem](http://www.dlsi.ua.es/~mlf/nnafmc/papers/kleene56representation.pdf) that both views are equivalent: regular languages are precisely the formal languages accepted by finite automata. In this blogpost, we utilise Dafny’s built-in inductive and coinductive reasoning capabilities to show that the two semantics of regular expressions are *[well-behaved](https://homepages.inf.ed.ac.uk/gdp/publications/Math_Op_Sem.pdf)*, in the sense they are in fact one and the same, up to pointwise [bisimulation](https://en.wikipedia.org/wiki/Bisimulation). + +## Denotational Semantics + +In this section, we define regular expressions and formal languages, introduce the concept of bisimilarity, formalise the *denotational* semantics of regular expressions as a function from regular expressions to formal languages, and prove that the latter is an algebra homomorphism. + +### Regular Expressions as Datatype + +We define the set of regular expressions parametric in an alphabet `A` as an inductive [`datatype`](https://dafny.org/latest/DafnyRef/DafnyRef#sec-inductive-datatypes): + +``` +datatype Exp = Zero | One | Char(A) | Plus(Exp, Exp) | Comp(Exp, Exp) | Star(Exp) +``` + +The definition captures that a regular expression is either a primitive character `Char(a)`, a non-deterministic choice between two regular expressions `Plus(e1, e2)`, a sequential iteration of two regular expressions `Comp(e1, e2)`, a finite number of self-iterations `Star(e)`, or one of the constants `Zero` (the unit of `Plus`) and `One` (the unit of `Comp`). On a more high-level, above defines `Exp` as the *smallest* algebraic structures that is equipped with two constants, contains all elements of type `A`, and is closed under two binary and one unary operation. + +### Formal Languages as Codatatype + +We define the set of formal languages parametric in an alphabet `A` as an coinductive [`codatatype`](https://dafny.org/latest/DafnyRef/DafnyRef#sec-coinductive-datatypes): + +``` +codatatype Lang = Alpha(eps: bool, delta: A -> Lang) +``` + +To some, this choice might seem odd at first sight. If you are familiar with the regular expressions, you likely have expected the set of formal languages to be defined more concretely as the set of all sets of finite sequences (sometimes called *words*), `iset>`. Rest assured, we agree — up to an appropriate notion of equality! Where as you characterise languages intrinsically, we treat them extrinsically, in terms of their universal property: [it is well known](https://ir.cwi.nl/pub/28550/rutten.pdf) that `iset>` forms the *greatest* [coalgebraic](https://en.wikipedia.org/wiki/F-coalgebra) structure (think of a deterministic automaton without initial state) `S` that is equipped with a functions `eps: S -> bool` and `delta: S → (A → S)`. Indeed, for any set `U` of finite sequences, we can verify whether `U` contains the empty sequence, `U.eps == [] in U`, and for any `a: A`, we can transition to the set `U.delta(a) == iset s | [a] + s in U`. Here, we choose the more abstract perspective on formal languages at it hides irrelevant specifics and thus allows us to write more elegant proofs. + +### An Algebra of Formal Languages + +If you think of formal languages as the set of all sets of finite sequences, you will soon realise that languages admit quite a bit of algebraic structure. For example, there exist two languages of distinct importance (can you already guess which ones?), and one can obtain a new language by taking e.g. the union of two languages. In fact, if you think about it for a bit longer, you’ll realise that formal languages admit exactly the same [type of algebraic structure](https://en.wikipedia.org/wiki/F-algebra) as the one you’ve encountered when defining the set of regular expressions! + +First, there exists the empty language `Zero()` that contains no words at all. Under above view, we find `Zero().eps == false` and `Zero().delta(a) == Zero`, since the empty set does not contain the empty sequence, and the derivative `iset s | [a] + [s] in iset{}` w.r.t any `a: A` yields again the empty set, respectively. We thus define: + +``` +ghost function Zero(): Lang { + Alpha(false, (a: A) => Zero()) +} +``` + +Using similar reasoning, we additionally derive the following definitions. In order, we formalise i) the language `One()` that contains only the empty sequence; ii) for any `a: A` the language `Singleton(a)` that consists of only the word `[a]`; iii) the language `Plus(L1, L2)` which consists of the union of the languages `L1` and `L2`; iv) the language `Comp(L1, L2)` that consists of all possible concatenation of words in `L1` and `L2`; and v) the language `Star(L)` that consists of all finite compositions of `L` with itself. Our definitions match what is well-known as *[Brzozowski derivatives](https://en.wikipedia.org/wiki/Brzozowski_derivative)*. + +``` +ghost function One(): Lang { + Alpha(true, (a: A) => Zero()) +} + +ghost function Singleton(a: A): Lang { + Alpha(false, (b: A) => if a == b then One() else Zero()) +} + +ghost function {:abstemious} Plus(L1: Lang, L2: Lang): Lang { + Alpha(L1.eps || L2.eps, (a: A) => Plus(L1.delta(a), L2.delta(a))) +} + +ghost function {:abstemious} Comp(L1: Lang, L2: Lang): Lang { + Alpha(L1.eps && L2.eps, (a: A) => Plus(Comp(L1.delta(a), L2), Comp(if L1.eps then One() else Zero(), L2.delta(a))) ) +} + +ghost function Star(L: Lang): Lang { + Alpha(true, (a: A) => Comp(L.delta(a), Star(L))) +} +``` + +### Denotational Semantics as Induced Morphism + +The denotational semantics of regular expressions can now be defined through induction, as a function `Denotational: Exp -> Lang` , by making use of the operations on languages we have just defined: + +``` +ghost function Denotational(e: Exp): Lang { + match e + case Zero => Languages.Zero() + case One => Languages.One() + case Char(a) => Languages.Singleton(a) + case Plus(e1, e2) => Languages.Plus(Denotational(e1), Denotational(e2)) + case Comp(e1, e2) => Languages.Comp(Denotational(e1), Denotational(e2)) + case Star(e1) => Languages.Star(Denotational(e1)) +} +``` + +### Bisimilarity and Coinduction + +Let us briefly introduce a notion of equality between formal languages that will be useful soon. A binary relation `R` between languages is called a *[bisimulation](https://en.wikipedia.org/wiki/Bisimulation),* if for any two languages `L1`, `L2` related by `R` the following holds: i) `L1` contains the empty word iff `L2` does; and ii) for any `a: A` the derivatives `L1.delta(a)` and `L2.delta(a)` are again related by `R`. As it turns out, the union of two bisimulations is again a bisimulation. In consequence, one can combine all possible bisimulations into a single relation: the *greatest* bisimulation. In Dafny, we can formalise the latter as a [`greatest predicate`](https://dafny.org/latest/DafnyRef/DafnyRef#sec-copredicates): + +``` +greatest predicate Bisimilar[nat](L1: Lang, L2: Lang) { + && (L1.eps == L2.eps) + && (forall a :: Bisimilar(L1.delta(a), L2.delta(a))) +} +``` + +It is instructive to think of a `greatest predicate` as pure syntactic sugar. Indeed, under the hood, Dafny’s compiler uses above body to implicitly generate i) for any `k: nat`, a *[prefix predicate](https://dafny.org/latest/DafnyRef/DafnyRef#514361-properties-of-prefix-predicates)* `Bisimilar#[k](L1, L2)` that signifies that the languages `L1` and `L2` concur on the first `k`-unrollings of the definition above; and ii) a predicate `Bisimilar(L1, L2)` that is true iff `Bisimilar#[k](L1, L2)` is true for all `k: nat`: + +``` +/* Pseudo code for illustration purposes */ + +ghost predicate Bisimilar(L1: Lang, L2: Lang) { + forall k :: Bisimilar#[k](L1, L2) +} + +ghost predicate Bisimilar#[k: nat](L1: Lang, L2: Lang) + decreases k +{ + if k == 0 then + true + else + && (L1.eps == L2.eps) + && (forall a :: Bisimilar#[k-1](L1.delta(a), L2.delta(a))) +} +``` + +Now that we have its definition in place, let us establish a property about bisimilarity, say, that it is a [*reflexive*](https://en.wikipedia.org/wiki/Reflexive_relation) relation. With the [`greatest lemma`](https://dafny.org/latest/DafnyRef/DafnyRef#sec-colemmas) construct, Dafny is able to able to derive a proof completely on its own: + +``` +greatest lemma BisimilarityIsReflexive[nat](L: Lang) + ensures Bisimilar(L, L) +{} +``` + +Once again, it is instructive to think of a `greatest lemma` as pure syntactic sugar. Under the hood, Dafny’s compiler uses the body of the `greatest lemma` to generate i) for any `k: nat`, a *[prefix lemma](https://dafny.org/latest/DafnyRef/DafnyRef#sec-prefix-lemmas)* `BisimilarityIsReflexive#[k](L)` that ensures the prefix predicate `Bisimilar#[k](L, L)`; and ii) a lemma `BisimilarityIsReflexive(L)` that ensures `Bisimilar(L, L)` by calling `Bisimilar#[k](L, L)` for all `k: nat`: + +``` +/* Pseudo code for illustration purposes */ + +lemma BisimilarityIsReflexive(L: Lang) + ensures Bisimilar(L, L) +{ + forall k ensures Bisimilar#[k](L, L) { + BisimilarityIsReflexive#[k](L); + } +} + +lemma BisimilarityIsReflexive#[k: nat](L: Lang) + ensures Bisimilar#[k](L, L) + decreases k +{ + if k == 0 { + } else { + forall a ensures Bisimilar#[k-1](L.delta(a), L.delta(a)) { + BisimilarityIsReflexive#[k-1](L.delta(a)); + } + } +} +``` + +If you are interested in the full details, I recommend taking a look at [this note on coinduction, predicates, and ordinals](https://leino.science/papers/krml285.html). + +### Denotational Semantics as Algebra Homomorphism + +For a moment, consider the function `var f: nat -> nat := (n: nat) => n + n` which maps a natural number to twice its value. The function `f` is *structure-preserving*: for any `m: nat` we have `f(m * n) == m * f(n)`, i.e. `f` commutes with the (left-)multiplication of naturals. In this section, we are interested in functions of type `f: Exp -> Lang` (more precisely, in `Denotational: Exp -> Lang`) that commute with the algebraic structures we encountered in [Regular Expressions as Datatype](#regular-expressions-as-datatype) and [An Algebra of Formal Languages](#an-algebra-of-formal-languages), respectively. We call such structure-preserving functions *algebra homomorphisms*. To define pointwise commutativity in this context, we’ll have to be able to compare languages for equality. As you probably guessed, bisimilarity will do the job: + +``` +ghost predicate IsAlgebraHomomorphism(f: Exp -> Lang) { + forall e :: IsAlgebraHomomorphismPointwise(f, e) +} + +ghost predicate IsAlgebraHomomorphismPointwise(f: Exp -> Lang, e: Exp) { + Bisimilar( + f(e), + match e + case Zero => Languages.Zero() + case One => Languages.One() + case Char(a) => Languages.Singleton(a) + case Plus(e1, e2) => Languages.Plus(f(e1), f(e2)) + case Comp(e1, e2) => Languages.Comp(f(e1), f(e2)) + case Star(e1) => Languages.Star(f(e1)) + ) +} +``` + +The proof that `Denotational` is an algebra homomorphism is straightforward: it essentially follows from bisimilarity being reflexive: + +``` +lemma DenotationalIsAlgebraHomomorphism() + ensures IsAlgebraHomomorphism(Denotational) +{ + forall e ensures IsAlgebraHomomorphismPointwise(Denotational, e) { + BisimilarityIsReflexive(Denotational(e)); + } +} +``` + +## Operational Semantics + +In this section, we provide an alternative perspective on the semantics of regular expressions. We equip the set of regular expressions with a coalgebraic structure, formalise its *operational* semantics as a function from regular expressions to formal languages, and prove that the latter is a coalgebra homomorphism. + +### A Coalgebra of Regular Expressions + +In [An Algebra of Formal Languages](#an-algebra-of-formal-languagesn Algebra of Formal Languages) we equipped the set of formal languages with an algebraic structure that resembles the one of regular expressions in [Regular Expressions as Datatype](#regular-expressions-as-datatype). Now, we are aiming for the reverse: we would like to equip the set of regular expressions with a (co)algebraic structure that resembles the one of formal languages in [Formal Languages as Codatatype](#formal-languages-as-codatatype). More concretely, we would like to turn the set of regular expressions into a deterministic automaton in which a state `e` is i) accepting iff `Eps(e)` and ii) transitions to a state `Delta(e)(a)` if given the input `a: A`. Note how our definitions closely resemble the Brzozowski derivatives we encountered in [An Algebra of Formal Languages](#an-algebra-of-formal-languages): + +``` +ghost function Eps(e: Exp): bool { + match e + case Zero => false + case One => true + case Char(a) => false + case Plus(e1, e2) => Eps(e1) || Eps(e2) + case Comp(e1, e2) => Eps(e1) && Eps(e2) + case Star(e1) => true +} + +ghost function Delta(e: Exp): A -> Exp { + (a: A) => + match e + case Zero => Zero + case One => Zero + case Char(b) => if a == b then One else Zero + case Plus(e1, e2) => Plus(Delta(e1)(a), Delta(e2)(a)) + case Comp(e1, e2) => Plus(Comp(Delta(e1)(a), e2), Comp(if Eps(e1) then One else Zero, Delta(e2)(a))) + case Star(e1) => Comp(Delta(e1)(a), Star(e1)) +} +``` + +### Operational Semantics as Induced Morphism + +The operational semantics of regular expressions can now be defined via coinduction, as a function `Operational: Exp -> Lang`, by making use of the coalgebraic structure on expressions we have just defined: + +``` +function Operational(e: Exp): Lang { + Alpha(Eps(e), (a: A) => Operational(Delta(e)(a))) +} +``` + +### Operational Semantics as Coalgebra Homomorphism + +In [Denotational Semantics as Algebra Homomorphism](#denotational-semantics-as-algebra-homomorphism) we defined algebra homomorphisms as functions `f: Exp -> Lang` that commute with the algebraic structures on regular expressions and formal languages, respectively. Analogously, let us now call a function `f` of the same type a *coalgebra homomorphism*, if it commutes with the *coalgebraic* structures we defined in [A Coalgebra of Regular Expressions](#a-coalgebra-of-regular-expressions) and [Formal Languages as Codatatype](#formal-languages-as-codatatype), respectively: + +``` +ghost predicate IsCoalgebraHomomorphism(f: Exp -> Lang) { + && (forall e :: f(e).eps == Eps(e)) + && (forall e, a :: Bisimilar(f(e).delta(a), f(Delta(e)(a)))) +} +``` + +It is straightforward to prove that `Operational` is a coalgebra homomorphism: once again, the central argument is that bisimilarity is a reflexive relation. + +``` +lemma OperationalIsCoalgebraHomomorphism() + ensures IsCoalgebraHomomorphism(Operational) +{ + forall e, a ensures Bisimilar(Operational(e).delta(a), Operational(Delta(e)(a))) { + BisimilarityIsReflexive(Operational(e).delta(a)); + } +} +``` + +## Well-Behaved Semantics + +So far, we have seen two dual approaches for assigning a formal language semantics to regular expressions: + +* `Denotational`: an algebra homomorphism obtained via induction +* `Operational`: a coalgebra homomorphism obtained via coinduction + +Next, we show that the denotational and operational semantics of regular expressions are *well-behaved*: they constitute two sides of the same coin. First, we show that `Denotational` is also a coalgebra homomorphism, and that coalgebra homomorphisms are unique up to bisimulation. We then deduce from the former that `Denotational` and `Operational` coincide pointwise, up to bisimulation. Finally, we show that `Operational` is also an algebra homomorphism. + +### Denotational Semantics As Coalgebra Homomorphism + +In this section, we establish that `Denotational` does not only commute with the algebraic structures of regular expressions and formal languages, but also with their coalgebraic structures: + +``` +lemma DenotationalIsCoalgebraHomomorphism() + ensures IsCoalgebraHomomorphism(Denotational) +{...} +``` + +The proof of the lemma is a bit more elaborated than the ones we have encountered so far. It can be divided into two subproofs, both of which make use of induction. One of the subproofs is straightforward, the other, more difficult one, again uses the reflexivity of bisimilarity, but also that the latter is a *congruence* relation with respect to `Plus` and `Comp`: + +``` +greatest lemma PlusCongruence[nat](L1a: Lang, L1b: Lang, L2a: Lang, L2b: Lang) + requires Bisimilar(L1a, L1b) + requires Bisimilar(L2a, L2b) + ensures Bisimilar(Plus(L1a, L2a), Plus(L1b, L2b)) +{...} + +lemma CompCongruence(L1a: Lang, L1b: Lang, L2a: Lang, L2b: Lang) + requires Bisimilar(L1a, L1b) + requires Bisimilar(L2a, L2b) + ensures Bisimilar(Comp(L1a, L2a), Comp(L1b, L2b)) +{...} +``` + +The proof of `PlusCongruence` is relatively short, as we can take advantage of the syntactic sugaring of the `greatest lemma` construct. For `CompCongruence` we have to put in a bit more manual work. + +### Coalgebra Homomorphisms Are Unique + +The aim of this section is to show that, up to pointwise bisimilarity, there only exists *one* coalgebra homomorphism of type `Exp -> Lang`: + +``` +lemma UniqueCoalgebraHomomorphism(f: Exp -> Lang, g: Exp -> Lang, e: Exp) + requires IsCoalgebraHomomorphism(f) + requires IsCoalgebraHomomorphism(g) + ensures Bisimilar(f(e), g(e)) +{...} +``` + +[As is well-known](https://ir.cwi.nl/pub/28550/rutten.pdf), the statement may in fact be strengthened to: for *any* coalgebra `C` there exists only one coalgebra homomorphism of type `C -> Lang` , up to pointwise bisimulation. For our purposes, the weaker statement above will be sufficient. At the heart of the proof lies the observation that bisimilarity is transitive: + +``` +lemma BisimilarityIsTransitive(L1: Lang, L2: Lang, L3: Lang) + ensures Bisimilar(L1, L2) && Bisimilar(L2, L3) ==> Bisimilar(L1, L3) +{...} +``` + +### Denotational and Operational Semantics Are Bisimilar + +We are done! From the previous results, we can immediately deduce that denotational and operational semantics are the same, up to pointwise bisimilarity: + +``` +lemma OperationalAndDenotationalAreBisimilar(e: Exp) + ensures Bisimilar(Operational(e), Denotational(e)) +{ + OperationalIsCoalgebraHomomorphism(); + DenotationalIsCoalgebraHomomorphism(); + UniqueCoalgebraHomomorphism(Operational, Denotational, e); +} +``` + +### Operational Semantics As Algebra Homomorphism + +As a little extra, for the sake of symmetry, let us also prove that `Operational` is an algebra homomorphism. (We already know that it is a coalgebra homomorphism, and that `Denotational` is both an algebra and coalgebra homomorphism.) + +``` +lemma OperationalIsAlgebraHomomorphism() + ensures IsAlgebraHomomorphism(Operational) +{...} +``` + +The idea of the proof is to take advantage of `Denotational` being an algebra homomorphism, by translating its properties to `Operational` via the already proven main result in [Denotational and Operational Semantics Are Bisimilar](#denotational-and-operational-semantics-are-bisimilar). The relevant new statements capture that bisimilarity is symmetric and a congruence with respect to the `Star` operation: + +``` +greatest lemma BisimilarityIsSymmetric[nat](L1: Lang, L2: Lang) + ensures Bisimilar(L1, L2) ==> Bisimilar(L2, L1) + ensures Bisimilar(L1, L2) <== Bisimilar(L2, L1) +{} + +lemma StarCongruence(L1: Lang, L2: Lang) + requires Bisimilar(L1, L2) + ensures Bisimilar(Star(L1), Star(L2)) +{...} +``` + +## Conclusion + +We have used Dafny’s built-in inductive and coinductive reasoning capabilities to define two language semantics for regular expressions: denotational and operational semantics. Through a number of dualities — construction and deconstruction, algebras and coalgebras, and congruence and bisimilarity — we have proven the semantics to be two sides of the same coin. The blogpost is inspired by research in the field of [Coalgebra](https://en.wikipedia.org/wiki/F-coalgebra), which was pioneered by [Rutten](https://pdf.sciencedirectassets.com/271538/1-s2.0-S0304397500X01466/1-s2.0-S0304397500000566/main.pdf?X-Amz-Security-Token=IQoJb3JpZ2luX2VjELj%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FwEaCXVzLWVhc3QtMSJIMEYCIQDF6w6Fgo6yaRHLxOonVyd6c2NK5OBnjUFYmLPQ5%2FxwJwIhAO8MTWhCr6iXXkvrDJb5ylzqqKxwCWTsO%2BQHcOnpKBl2KrsFCOH%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FwEQBRoMMDU5MDAzNTQ2ODY1Igxjn%2BJg6E9T8pxDOv8qjwUsJZLdZb1wZRX8%2FfWwSP4ghcWU0g0XImQqnhAbMTKMrEGol%2FS%2BLRuWJVuTwdOKULbI%2BM6LjAwD3PqFD6r0eLS4dmUB5NQUjuiOV0EdgFC49Aws7ICWRt2%2BqmudKI%2FA2Ng4o8LQbeYm6S0xFuS61lNMaNqeIdaW39AmhpX6cH7BaisztuRPXsTntdxi7eZTstIc1LlpvEaZC5fQpQm2TqEaxQ6ZBMKytxnbfxQGjYTiUVk%2FP8gX4x4cqhWMoPeVZQHalY8oaztSvjDlwGoMxFjA%2FbAWlsrsu36EMQ4Ln%2FH1tT%2B%2BY9QHK7eP30ozRlUEVSl6Xo6FYtRPjn1FuSAHiTdro0tME67u20OPPTE7%2FDOoISCefvuqeb1wnkjRVC%2BeNqyY3YsxgYAo95C54FYNYOQPrdXxiN4y%2BV%2FJx1CUJl3mELinzXVP%2BTkjTHUszcCSmfxIZGkGb8gIsLjD1VNT2i1uNCv41tSP6ZzxRlRy1OsEfT83omdTxMSRSEHvKJC2ruPOR2YnOKoa6KyTFC9Nqecar36%2BMVawBPdRbvnRa36zHOUSz9gj2ugrok8iQnaMjijUB2%2BY900HOFnQ7GbfydlcNwZ8rMZKVV%2Bk5SuVuj3mraiCYQf3J1OAcTpte4MAnwWT7cw3yc26Agsi5I%2Bgbkduv%2B1NkKwQdddbRQLWjDPKBBguV2SDkddwJYL3buYsjtGeavTVkS7Yiv4S8ErOacoVcipC%2BsDc1dvNf4SGbrD0I72%2BKxxnuKoPzVeSi9Wq8qEOzjhnCfWl54bZR8UCr6Y7uHWZvOnXRS3upM%2Fmw8pcm960FfAXpr0MGfiaxIMlhym%2B1oWBJSgAzYQoDURpQiR6HDf5ENlWAJ9ih%2FUTs0EvMJj9gKoGOrABK5gEMZHmn5E3pBNO3do43pi3Eh9OSnySWgdHUwzBRFOu0YrZc5s%2FTLLc1w7uip4MCag93rwnlzR5pp34huRUI380mDWniRIzs3l8MD3U69smz9qmNI2sekojftrKmOv9cSgK8cwPG4mfwPYemWVf91k2vh4JglI60udJkOogGRr%2FJODZ50Uk0XoHLDSrGkiuDrttQuIuFw4rRj6CVNbNUZdHDlMGn8%2FQlTufNSYXthQ%3D&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20231030T235451Z&X-Amz-SignedHeaders=host&X-Amz-Expires=300&X-Amz-Credential=ASIAQ3PHCVTY5CTPNFFC%2F20231030%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Signature=2f06e758aca68bce8dcad281abb706e46f1135867a314833740d5892378fa798&hash=5139ce2f6945cb0fb38bace770ff3c87860d360a9c1077e8cf0fea52c4fe03c7&host=68042c943591013ac2b2430a89b270f6af2c76d8dfd086a07176afe7c76c2c61&pii=S0304397500000566&tid=spdf-654f0c1b-8ec2-4879-9902-886dee8e40d7&sid=9eb633476ba2634017193178041b279cd557gxrqa&type=client&tsoh=d3d3LnNjaWVuY2VkaXJlY3QuY29t&ua=0f165c520a575b5208&rr=81e795f6091dec78&cc=us) and [Gumm](https://www.researchgate.net/publication/2614339_Elements_Of_The_General_Theory_Of_Coalgebras). The concept of well-behaved semantics goes back to [Turi and Plotkin](https://homepages.inf.ed.ac.uk/gdp/publications/Math_Op_Sem.pdf) and was adapted by [Jacobs](https://link.springer.com/chapter/10.1007/11780274_20) to the case of regular expressions. We heavily used automata theoretic constructions from the 1960s, originally investigated by [Brzozowski](https://dl.acm.org/doi/10.1145/321239.321249). Due to space constraints, we had to exclude the details of most of the proofs. To dive deep, please take a look at the full Dafny source code, which is available here.[](https://dafny.org/blog/assets/src/cracking-the-coding-interview-permutations/permutations.dfy) diff --git a/assets/src/semantics-of-regular-expressions/Expressions.dfy b/assets/src/semantics-of-regular-expressions/Expressions.dfy new file mode 100644 index 0000000..45877ff --- /dev/null +++ b/assets/src/semantics-of-regular-expressions/Expressions.dfy @@ -0,0 +1,28 @@ +module Expressions { + + /* Definitions */ + + datatype Exp = Zero | One | Char(A) | Plus(Exp, Exp) | Comp(Exp, Exp) | Star(Exp) + + function Eps(e: Exp): bool { + match e + case Zero => false + case One => true + case Char(a) => false + case Plus(e1, e2) => Eps(e1) || Eps(e2) + case Comp(e1, e2) => Eps(e1) && Eps(e2) + case Star(e1) => true + } + + function Delta(e: Exp): A -> Exp { + (a: A) => + match e + case Zero => Zero + case One => Zero + case Char(b) => if a == b then One else Zero + case Plus(e1, e2) => Plus(Delta(e1)(a), Delta(e2)(a)) + case Comp(e1, e2) => Plus(Comp(Delta(e1)(a), e2), Comp(if Eps(e1) then One else Zero, Delta(e2)(a))) + case Star(e1) => Comp(Delta(e1)(a), Star(e1)) + } + +} \ No newline at end of file diff --git a/assets/src/semantics-of-regular-expressions/Languages.dfy b/assets/src/semantics-of-regular-expressions/Languages.dfy new file mode 100644 index 0000000..970fa00 --- /dev/null +++ b/assets/src/semantics-of-regular-expressions/Languages.dfy @@ -0,0 +1,222 @@ +module Languages { + + /* Definitions */ + + codatatype Lang = Alpha(eps: bool, delta: A -> Lang) + + function Zero(): Lang { + Alpha(false, (a: A) => Zero()) + } + + function One(): Lang { + Alpha(true, (a: A) => Zero()) + } + + function Singleton(a: A): Lang { + Alpha(false, (b: A) => if a == b then One() else Zero()) + } + + function {:abstemious} Plus(L1: Lang, L2: Lang): Lang { + Alpha(L1.eps || L2.eps, (a: A) => Plus(L1.delta(a), L2.delta(a))) + } + + function {:abstemious} Comp(L1: Lang, L2: Lang): Lang { + Alpha(L1.eps && L2.eps, (a: A) => Plus(Comp(L1.delta(a), L2), Comp(if L1.eps then One() else Zero(), L2.delta(a)))) + } + + function Star(L: Lang): Lang { + Alpha(true, (a: A) => Comp(L.delta(a), Star(L))) + } + + greatest predicate Bisimilar[nat](L1: Lang, L2: Lang) { + && (L1.eps == L2.eps) + && (forall a :: Bisimilar(L1.delta(a), L2.delta(a))) + } + + /* Lemmas */ + + /* Bisimilarity */ + + greatest lemma BisimilarityIsReflexive[nat](L: Lang) + ensures Bisimilar(L, L) + {} + + lemma BisimilarityIsTransitive(L1: Lang, L2: Lang, L3: Lang) + ensures Bisimilar(L1, L2) && Bisimilar(L2, L3) ==> Bisimilar(L1, L3) + { + if Bisimilar(L1,L2) && Bisimilar(L2, L3) { + assert Bisimilar(L1, L3) by { + BisimilarityIsTransitiveAlternative(L1, L2, L3); + } + } + } + + greatest lemma BisimilarityIsTransitiveAlternative[nat](L1: Lang, L2: Lang, L3: Lang) + requires Bisimilar(L1, L2) && Bisimilar(L2, L3) + ensures Bisimilar(L1, L3) + {} + + lemma BisimilarityIsTransitiveAlternativeTwo(k: nat, L1: Lang, L2: Lang, L3: Lang) + ensures Bisimilar#[k](L1, L2) && Bisimilar#[k](L2, L3) ==> Bisimilar#[k](L1, L3) + { + if k != 0 { + if Bisimilar#[k](L1, L2) && Bisimilar#[k](L2, L3) { + forall a ensures Bisimilar#[k - 1](L1.delta(a), L2.delta(a)) && Bisimilar#[k-1](L2.delta(a), L3.delta(a)) { + BisimilarityIsTransitiveAlternativeTwo(k-1, L1.delta(a), L2.delta(a), L3.delta(a)); + } + } + } + } + + greatest lemma BisimilarityIsSymmetric[nat](L1: Lang, L2: Lang) + ensures Bisimilar(L1, L2) ==> Bisimilar(L2, L1) + ensures Bisimilar(L1, L2) <== Bisimilar(L2, L1) + {} + + lemma BisimilarCuttingPrefixes(k: nat, L1: Lang, L2: Lang) + requires forall n: nat :: n <= k + 1 ==> Bisimilar#[n](L1, L2) + ensures forall a :: Bisimilar#[k](L1.delta(a), L2.delta(a)) + { + forall a ensures Bisimilar#[k](L1.delta(a), L2.delta(a)) { + if k != 0 { + assert Bisimilar#[k + 1](L1, L2); + } + } + } + + lemma BisimilarCuttingPrefixes2(k: nat, a: A, L1a: Lang, L1b: Lang) + requires k != 0 + requires forall n: nat :: n <= k + 1 ==> Bisimilar#[n](L1a, L1b) + ensures forall n: nat :: n <= k ==> Bisimilar#[n](L1a.delta(a), L1b.delta(a)) + { + forall n: nat ensures n <= k ==> Bisimilar#[n](L1a.delta(a), L1b.delta(a)) { + if n <= k { + BisimilarCuttingPrefixes(n, L1a, L1b); + } + } + } + + /* Congruence of Plus */ + + greatest lemma PlusCongruence[nat](L1a: Lang, L1b: Lang, L2a: Lang, L2b: Lang) + requires Bisimilar(L1a, L1b) + requires Bisimilar(L2a, L2b) + ensures Bisimilar(Plus(L1a, L2a), Plus(L1b, L2b)) + {} + + lemma PlusCongruenceAlternative(k: nat, L1a: Lang, L1b: Lang, L2a: Lang, L2b: Lang) + requires Bisimilar#[k](L1a, L1b) + requires Bisimilar#[k](L2a, L2b) + ensures Bisimilar#[k](Plus(L1a, L2a), Plus(L1b, L2b)) + {} + + /* Congruence of Comp */ + + lemma CompCongruence(L1a: Lang, L1b: Lang, L2a: Lang, L2b: Lang) + requires Bisimilar(L1a, L1b) + requires Bisimilar(L2a, L2b) + ensures Bisimilar(Comp(L1a, L2a), Comp(L1b, L2b)) + { + forall k ensures Bisimilar#[k](Comp(L1a,L2a), Comp(L1b,L2b)) { + if k != 0 { + var k' :| k' + 1 == k; + CompCongruenceHelper(k', L1a, L1b, L2a, L2b); + } + } + } + + lemma CompCongruenceHelper(k: nat, L1a: Lang, L1b: Lang, L2a: Lang, L2b: Lang) + requires forall n : nat :: n <= k + 1 ==> Bisimilar#[n](L1a, L1b) + requires forall n : nat :: n <= k + 1 ==> Bisimilar#[n](L2a, L2b) + ensures Bisimilar#[k+1](Comp(L1a, L2a), Comp(L1b, L2b)) + { + var lhs := Comp(L1a, L2a); + var rhs := Comp(L1b, L2b); + + assert Bisimilar#[1](L1a, L1b); + assert Bisimilar#[1](L2a, L2b); + assert lhs.eps == rhs.eps; + + forall a ensures (Bisimilar#[k](lhs.delta(a), rhs.delta(a))) { + var x1 := Comp(L1a.delta(a), L2a); + var x2 := Comp(L1b.delta(a), L2b); + assert Bisimilar#[k](x1, x2) by { + if k != 0 { + BisimilarCuttingPrefixes2(k, a, L1a, L1b); + var k' :| k == k' + 1; + CompCongruenceHelper(k', L1a.delta(a), L1b.delta(a), L2a, L2b); + } + } + var y1 := Comp(if L1a.eps then One() else Zero(), L2a.delta(a)); + var y2 := Comp(if L1b.eps then One() else Zero(), L2b.delta(a)); + assert Bisimilar#[k](y1, y2) by { + assert L1a.eps == L1b.eps; + if k != 0 { + if L1a.eps { + BisimilarityIsReflexive(One()); + BisimilarCuttingPrefixes2(k, a, L2a, L2b); + var k' :| k == k' + 1; + CompCongruenceHelper(k', One(), One(), L2a.delta(a), L2b.delta(a)); + } else { + BisimilarityIsReflexive(Zero()); + BisimilarCuttingPrefixes2(k, a, L2a, L2b); + var k' :| k == k' + 1; + CompCongruenceHelper(k', Zero(), Zero(), L2a.delta(a), L2b.delta(a)); + } + } + } + PlusCongruenceAlternative(k, x1, x2, y1, y2); + } + } + + /* Congruence of Star */ + + lemma StarCongruence(L1: Lang, L2: Lang) + requires Bisimilar(L1, L2) + ensures Bisimilar(Star(L1), Star(L2)) + { + forall k ensures Bisimilar#[k](Star(L1), Star(L2)) { + if k != 0 { + var k' :| k' + 1 == k; + StarCongruenceHelper(k', L1, L2); + } + } + } + + lemma StarCongruenceHelper(k: nat, L1: Lang, L2: Lang) + requires forall n: nat :: n <= k + 1 ==> Bisimilar#[n](L1, L2) + ensures Bisimilar#[k+1](Star(L1), Star(L2)) + { + forall a ensures Bisimilar#[k](Star(L1).delta(a), Star(L2).delta(a)) { + if k != 0 { + BisimilarCuttingPrefixes2(k, a, L1, L2); + var k' :| k == k' + 1; + forall n: nat ensures n <= k' + 1 ==> Bisimilar#[n](Star(L1), Star(L2)) { + if 0 < n <= k' + 1 { + var n' :| n == n' + 1; + StarCongruenceHelper(n', L1,L2); + } + } + CompCongruenceHelper(k', L1.delta(a), L2.delta(a), Star(L1), Star(L2)); + } + } + } + +} + + + + + + + + + + + + + + + + + diff --git a/assets/src/semantics-of-regular-expressions/Semantics.dfy b/assets/src/semantics-of-regular-expressions/Semantics.dfy new file mode 100644 index 0000000..803eb58 --- /dev/null +++ b/assets/src/semantics-of-regular-expressions/Semantics.dfy @@ -0,0 +1,244 @@ +include "Expressions.dfy" +include "Languages.dfy" + +module Semantics { + + import opened Expressions + import opened Languages + + /* Definitions */ + + /* Denotational Semantics */ + + function Denotational(e: Exp): Lang { + match e + case Zero => Languages.Zero() + case One => Languages.One() + case Char(a) => Languages.Singleton(a) + case Plus(e1, e2) => Languages.Plus(Denotational(e1), Denotational(e2)) + case Comp(e1, e2) => Languages.Comp(Denotational(e1), Denotational(e2)) + case Star(e1) => Languages.Star(Denotational(e1)) + } + + /* Operational Semantics */ + + function Operational(e: Exp): Lang { + Alpha(Eps(e), (a: A) => Operational(Delta(e)(a))) + } + + /* (Co)algebra homomorphisms f: Exp -> Lang */ + + ghost predicate IsCoalgebraHomomorphism(f: Exp -> Lang) { + && (forall e :: f(e).eps == Eps(e)) + && (forall e, a :: Bisimilar(f(e).delta(a), f(Delta(e)(a)))) + } + + ghost predicate IsAlgebraHomomorphism(f: Exp -> Lang) { + forall e :: IsAlgebraHomomorphismPointwise(f, e) + } + + ghost predicate IsAlgebraHomomorphismPointwise(f: Exp -> Lang, e: Exp) { + Bisimilar( + f(e), + match e + case Zero => Languages.Zero() + case One => Languages.One() + case Char(a) => Languages.Singleton(a) + case Plus(e1, e2) => Languages.Plus(f(e1), f(e2)) + case Comp(e1, e2) => Languages.Comp(f(e1), f(e2)) + case Star(e1) => Languages.Star(f(e1)) + ) + } + + /* Lemmas */ + + /* Any two coalgebra homomorphisms f,g: Exp -> Lang are equal up to bisimulation */ + + lemma UniqueCoalgebraHomomorphism(f: Exp -> Lang, g: Exp -> Lang, e: Exp) + requires IsCoalgebraHomomorphism(f) + requires IsCoalgebraHomomorphism(g) + ensures Bisimilar(f(e), g(e)) + { + BisimilarityIsReflexive(f(e)); + BisimilarityIsReflexive(g(e)); + UniqueCoalgebraHomomorphismHelper1(f, g, f(e), g(e)); + } + + lemma UniqueCoalgebraHomomorphismHelper1(f: Exp -> Lang, g: Exp -> Lang, L1: Lang, L2: Lang) + requires IsCoalgebraHomomorphism(f) + requires IsCoalgebraHomomorphism(g) + requires exists e :: Bisimilar(L1, f(e)) && Bisimilar(L2, g(e)) + ensures Bisimilar(L1, L2) + { + forall k ensures Bisimilar#[k](L1, L2) { + if k != 0 { + UniqueCoalgebraHomomorphismHelper2(k, f, g, L1, L2); + } + } + } + + lemma UniqueCoalgebraHomomorphismHelper2(k: nat, f: Exp -> Lang, g: Exp -> Lang, L1: Lang, L2: Lang) + requires IsCoalgebraHomomorphism(f) + requires IsCoalgebraHomomorphism(g) + requires exists e :: Bisimilar#[k](L1, f(e)) && Bisimilar#[k](L2, g(e)) + ensures Bisimilar#[k](L1, L2) + { + var e :| Bisimilar#[k](L1, f(e)) && Bisimilar#[k](L2, g(e)); + if k != 0 { + forall a ensures Bisimilar#[k-1](L1.delta(a), L2.delta(a)) { + BisimilarityIsTransitiveAlternativeTwo(k-1, L1.delta(a), f(e).delta(a), f(Delta(e)(a))); + BisimilarityIsTransitiveAlternativeTwo(k-1, L2.delta(a), g(e).delta(a), g(Delta(e)(a))); + UniqueCoalgebraHomomorphismHelper2(k-1, f, g, L1.delta(a), L2.delta(a)); + } + } + } + + /* Denotational is a algebra homomorphism */ + + lemma DenotationalIsAlgebraHomomorphism() + ensures IsAlgebraHomomorphism(Denotational) + { + forall e ensures IsAlgebraHomomorphismPointwise(Denotational, e) { + BisimilarityIsReflexive(Denotational(e)); + } + } + + /* Denotational is a coalgebra homomorphism */ + + lemma DenotationalIsCoalgebraHomomorphism() + ensures IsCoalgebraHomomorphism(Denotational) + { + forall e ensures Denotational(e).eps == Eps(e) { + DenotationalIsCoalgebraHomomorphismHelper1(e); + } + forall e, a ensures Bisimilar(Denotational(e).delta(a), Denotational(Delta(e)(a))) { + DenotationalIsCoalgebraHomomorphismHelper2(e, a); + } + } + + lemma DenotationalIsCoalgebraHomomorphismHelper1(e: Exp) + ensures Denotational(e).eps == Eps(e) + { + match e + case Zero => + case One => + case Char(a) => + case Plus(e1, e2) => + DenotationalIsCoalgebraHomomorphismHelper1(e1); + DenotationalIsCoalgebraHomomorphismHelper1(e2); + case Comp(e1, e2) => + DenotationalIsCoalgebraHomomorphismHelper1(e1); + DenotationalIsCoalgebraHomomorphismHelper1(e2); + case Star(e1) => + DenotationalIsCoalgebraHomomorphismHelper1(e1); + } + + lemma DenotationalIsCoalgebraHomomorphismHelper2(e: Exp, a: A) + ensures Bisimilar(Denotational(e).delta(a), Denotational(Delta(e)(a))) + { + match e + case Zero => BisimilarityIsReflexive(Languages.Zero()); + case One => BisimilarityIsReflexive(Languages.One()); + case Char(b) => + if a == b { + BisimilarityIsReflexive(Languages.One()); + } else { + BisimilarityIsReflexive(Languages.Zero()); + } + case Plus(e1, e2) => + DenotationalIsCoalgebraHomomorphismHelper2(e1, a); + DenotationalIsCoalgebraHomomorphismHelper2(e2, a); + PlusCongruence(Denotational(e1).delta(a), Denotational(Delta(e1)(a)), Denotational(e2).delta(a), Denotational(Delta(e2)(a))); + case Comp(e1, e2) => + DenotationalIsCoalgebraHomomorphismHelper1(e1); + DenotationalIsCoalgebraHomomorphismHelper2(e1, a); + DenotationalIsCoalgebraHomomorphismHelper2(e2, a); + BisimilarityIsReflexive(Denotational(e2)); + BisimilarityIsReflexive(if Eps(e1) then Languages.One() else Languages.Zero()); + CompCongruence( + Denotational(e1).delta(a), + Denotational(Delta(e1)(a)), + Denotational(e2), + Denotational(e2) + ); + CompCongruence( + if Eps(e1) then Languages.One() else Languages.Zero(), + if Eps(e1) then Languages.One() else Languages.Zero(), + Denotational(e2).delta(a), + Denotational(Delta(e2)(a)) + ); + PlusCongruence( + Languages.Comp(Denotational(e1).delta(a), Denotational(e2)), + Languages.Comp(Denotational(Delta(e1)(a)), Denotational(e2)), + Languages.Comp(if Eps(e1) then Languages.One() else Languages.Zero(), Denotational(e2).delta(a)), + Languages.Comp(if Eps(e1) then Languages.One() else Languages.Zero(), Denotational(Delta(e2)(a))) + ); + case Star(e1) => + DenotationalIsCoalgebraHomomorphismHelper2(e1, a); + BisimilarityIsReflexive(Languages.Star(Denotational(e1))); + CompCongruence(Denotational(e1).delta(a), Denotational(Delta(e1)(a)), Languages.Star(Denotational(e1)), Languages.Star(Denotational(e1))); + } + + /* Operational is a algebra homomorphism */ + + lemma OperationalIsAlgebraHomomorphism() + ensures IsAlgebraHomomorphism(Operational) + { + forall e ensures IsAlgebraHomomorphismPointwise(Operational, e) { + OperationalAndDenotationalAreBisimilar(e); + assert IsAlgebraHomomorphismPointwise(Denotational, e) by { + DenotationalIsAlgebraHomomorphism(); + } + match e + case Zero => + BisimilarityIsTransitive(Operational(Zero), Denotational(Zero), Languages.Zero()); + case One => + BisimilarityIsTransitive(Operational(One), Denotational(One), Languages.One()); + case Char(a) => + BisimilarityIsTransitive(Operational(Char(a)), Denotational(Char(a)), Languages.Singleton(a)); + case Plus(e1, e2) => + BisimilarityIsTransitive(Operational(Plus(e1, e2)), Denotational(Plus(e1, e2)), Languages.Plus(Denotational(e1), Denotational(e2))); + OperationalAndDenotationalAreBisimilar(e1); + BisimilarityIsSymmetric(Denotational(e1), Operational(e1)); + OperationalAndDenotationalAreBisimilar(e2); + BisimilarityIsSymmetric(Denotational(e2), Operational(e2)); + PlusCongruence(Denotational(e1), Operational(e1), Denotational(e2), Operational(e2)); + BisimilarityIsTransitive(Operational(Plus(e1, e2)), Languages.Plus(Denotational(e1), Denotational(e2)), Languages.Plus(Operational(e1), Operational(e2))); + case Comp(e1, e2) => + BisimilarityIsTransitive(Operational(Comp(e1, e2)), Denotational(Comp(e1, e2)), Languages.Comp(Denotational(e1), Denotational(e2))); + OperationalAndDenotationalAreBisimilar(e1); + BisimilarityIsSymmetric(Denotational(e1), Operational(e1)); + OperationalAndDenotationalAreBisimilar(e2); + BisimilarityIsSymmetric(Denotational(e2), Operational(e2)); + CompCongruence(Denotational(e1), Operational(e1), Denotational(e2), Operational(e2)); + BisimilarityIsTransitive(Operational(Comp(e1, e2)), Languages.Comp(Denotational(e1), Denotational(e2)), Languages.Comp(Operational(e1), Operational(e2))); + case Star(e1) => + BisimilarityIsTransitive(Operational(Star(e1)), Denotational(Star(e1)), Languages.Star(Denotational(e1))); + OperationalAndDenotationalAreBisimilar(e1); + BisimilarityIsSymmetric(Denotational(e1), Operational(e1)); + StarCongruence(Denotational(e1), Operational(e1)); + BisimilarityIsTransitive(Operational(Star(e1)), Languages.Star(Denotational(e1)), Languages.Star(Operational(e1))); + } + } + + /* Operational is a coalgebra homomorphism */ + + lemma OperationalIsCoalgebraHomomorphism() + ensures IsCoalgebraHomomorphism(Operational) + { + forall e, a ensures Bisimilar(Operational(e).delta(a), Operational(Delta(e)(a))) { + BisimilarityIsReflexive(Operational(e).delta(a)); + } + } + + /* Operational and Denotational are equal, up to bisimulation */ + + lemma OperationalAndDenotationalAreBisimilar(e: Exp) + ensures Bisimilar(Operational(e), Denotational(e)) + { + OperationalIsCoalgebraHomomorphism(); + DenotationalIsCoalgebraHomomorphism(); + UniqueCoalgebraHomomorphism(Operational, Denotational, e); + } + +} \ No newline at end of file From 12eaa1749ae61698c9d4a45cfd717dc93cf3a40e Mon Sep 17 00:00:00 2001 From: stefan-aws Date: Thu, 14 Dec 2023 02:40:19 +0000 Subject: [PATCH 02/62] source code --- ...-semantics-of-regular-expressions.markdown | 2 +- .../Expressions.dfy | 28 -- .../Languages.dfy | 222 ---------------- .../Semantics.dfy | 244 ------------------ .../semantics-of-regular-expressions.zip | Bin 0 -> 3654 bytes 5 files changed, 1 insertion(+), 495 deletions(-) delete mode 100644 assets/src/semantics-of-regular-expressions/Expressions.dfy delete mode 100644 assets/src/semantics-of-regular-expressions/Languages.dfy delete mode 100644 assets/src/semantics-of-regular-expressions/Semantics.dfy create mode 100644 assets/src/semantics-of-regular-expressions/semantics-of-regular-expressions.zip diff --git a/_posts/2024-01-10-semantics-of-regular-expressions.markdown b/_posts/2024-01-10-semantics-of-regular-expressions.markdown index 688d869..37a5d60 100644 --- a/_posts/2024-01-10-semantics-of-regular-expressions.markdown +++ b/_posts/2024-01-10-semantics-of-regular-expressions.markdown @@ -348,4 +348,4 @@ lemma StarCongruence(L1: Lang, L2: Lang) ## Conclusion -We have used Dafny’s built-in inductive and coinductive reasoning capabilities to define two language semantics for regular expressions: denotational and operational semantics. Through a number of dualities — construction and deconstruction, algebras and coalgebras, and congruence and bisimilarity — we have proven the semantics to be two sides of the same coin. The blogpost is inspired by research in the field of [Coalgebra](https://en.wikipedia.org/wiki/F-coalgebra), which was pioneered by [Rutten](https://pdf.sciencedirectassets.com/271538/1-s2.0-S0304397500X01466/1-s2.0-S0304397500000566/main.pdf?X-Amz-Security-Token=IQoJb3JpZ2luX2VjELj%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FwEaCXVzLWVhc3QtMSJIMEYCIQDF6w6Fgo6yaRHLxOonVyd6c2NK5OBnjUFYmLPQ5%2FxwJwIhAO8MTWhCr6iXXkvrDJb5ylzqqKxwCWTsO%2BQHcOnpKBl2KrsFCOH%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FwEQBRoMMDU5MDAzNTQ2ODY1Igxjn%2BJg6E9T8pxDOv8qjwUsJZLdZb1wZRX8%2FfWwSP4ghcWU0g0XImQqnhAbMTKMrEGol%2FS%2BLRuWJVuTwdOKULbI%2BM6LjAwD3PqFD6r0eLS4dmUB5NQUjuiOV0EdgFC49Aws7ICWRt2%2BqmudKI%2FA2Ng4o8LQbeYm6S0xFuS61lNMaNqeIdaW39AmhpX6cH7BaisztuRPXsTntdxi7eZTstIc1LlpvEaZC5fQpQm2TqEaxQ6ZBMKytxnbfxQGjYTiUVk%2FP8gX4x4cqhWMoPeVZQHalY8oaztSvjDlwGoMxFjA%2FbAWlsrsu36EMQ4Ln%2FH1tT%2B%2BY9QHK7eP30ozRlUEVSl6Xo6FYtRPjn1FuSAHiTdro0tME67u20OPPTE7%2FDOoISCefvuqeb1wnkjRVC%2BeNqyY3YsxgYAo95C54FYNYOQPrdXxiN4y%2BV%2FJx1CUJl3mELinzXVP%2BTkjTHUszcCSmfxIZGkGb8gIsLjD1VNT2i1uNCv41tSP6ZzxRlRy1OsEfT83omdTxMSRSEHvKJC2ruPOR2YnOKoa6KyTFC9Nqecar36%2BMVawBPdRbvnRa36zHOUSz9gj2ugrok8iQnaMjijUB2%2BY900HOFnQ7GbfydlcNwZ8rMZKVV%2Bk5SuVuj3mraiCYQf3J1OAcTpte4MAnwWT7cw3yc26Agsi5I%2Bgbkduv%2B1NkKwQdddbRQLWjDPKBBguV2SDkddwJYL3buYsjtGeavTVkS7Yiv4S8ErOacoVcipC%2BsDc1dvNf4SGbrD0I72%2BKxxnuKoPzVeSi9Wq8qEOzjhnCfWl54bZR8UCr6Y7uHWZvOnXRS3upM%2Fmw8pcm960FfAXpr0MGfiaxIMlhym%2B1oWBJSgAzYQoDURpQiR6HDf5ENlWAJ9ih%2FUTs0EvMJj9gKoGOrABK5gEMZHmn5E3pBNO3do43pi3Eh9OSnySWgdHUwzBRFOu0YrZc5s%2FTLLc1w7uip4MCag93rwnlzR5pp34huRUI380mDWniRIzs3l8MD3U69smz9qmNI2sekojftrKmOv9cSgK8cwPG4mfwPYemWVf91k2vh4JglI60udJkOogGRr%2FJODZ50Uk0XoHLDSrGkiuDrttQuIuFw4rRj6CVNbNUZdHDlMGn8%2FQlTufNSYXthQ%3D&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20231030T235451Z&X-Amz-SignedHeaders=host&X-Amz-Expires=300&X-Amz-Credential=ASIAQ3PHCVTY5CTPNFFC%2F20231030%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Signature=2f06e758aca68bce8dcad281abb706e46f1135867a314833740d5892378fa798&hash=5139ce2f6945cb0fb38bace770ff3c87860d360a9c1077e8cf0fea52c4fe03c7&host=68042c943591013ac2b2430a89b270f6af2c76d8dfd086a07176afe7c76c2c61&pii=S0304397500000566&tid=spdf-654f0c1b-8ec2-4879-9902-886dee8e40d7&sid=9eb633476ba2634017193178041b279cd557gxrqa&type=client&tsoh=d3d3LnNjaWVuY2VkaXJlY3QuY29t&ua=0f165c520a575b5208&rr=81e795f6091dec78&cc=us) and [Gumm](https://www.researchgate.net/publication/2614339_Elements_Of_The_General_Theory_Of_Coalgebras). The concept of well-behaved semantics goes back to [Turi and Plotkin](https://homepages.inf.ed.ac.uk/gdp/publications/Math_Op_Sem.pdf) and was adapted by [Jacobs](https://link.springer.com/chapter/10.1007/11780274_20) to the case of regular expressions. We heavily used automata theoretic constructions from the 1960s, originally investigated by [Brzozowski](https://dl.acm.org/doi/10.1145/321239.321249). Due to space constraints, we had to exclude the details of most of the proofs. To dive deep, please take a look at the full Dafny source code, which is available here.[](https://dafny.org/blog/assets/src/cracking-the-coding-interview-permutations/permutations.dfy) +We have used Dafny’s built-in inductive and coinductive reasoning capabilities to define two language semantics for regular expressions: denotational and operational semantics. Through a number of dualities — construction and deconstruction, algebras and coalgebras, and congruence and bisimilarity — we have proven the semantics to be two sides of the same coin. The blogpost is inspired by research in the field of [Coalgebra](https://en.wikipedia.org/wiki/F-coalgebra), which was pioneered by [Rutten](https://pdf.sciencedirectassets.com/271538/1-s2.0-S0304397500X01466/1-s2.0-S0304397500000566/main.pdf?X-Amz-Security-Token=IQoJb3JpZ2luX2VjELj%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FwEaCXVzLWVhc3QtMSJIMEYCIQDF6w6Fgo6yaRHLxOonVyd6c2NK5OBnjUFYmLPQ5%2FxwJwIhAO8MTWhCr6iXXkvrDJb5ylzqqKxwCWTsO%2BQHcOnpKBl2KrsFCOH%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FwEQBRoMMDU5MDAzNTQ2ODY1Igxjn%2BJg6E9T8pxDOv8qjwUsJZLdZb1wZRX8%2FfWwSP4ghcWU0g0XImQqnhAbMTKMrEGol%2FS%2BLRuWJVuTwdOKULbI%2BM6LjAwD3PqFD6r0eLS4dmUB5NQUjuiOV0EdgFC49Aws7ICWRt2%2BqmudKI%2FA2Ng4o8LQbeYm6S0xFuS61lNMaNqeIdaW39AmhpX6cH7BaisztuRPXsTntdxi7eZTstIc1LlpvEaZC5fQpQm2TqEaxQ6ZBMKytxnbfxQGjYTiUVk%2FP8gX4x4cqhWMoPeVZQHalY8oaztSvjDlwGoMxFjA%2FbAWlsrsu36EMQ4Ln%2FH1tT%2B%2BY9QHK7eP30ozRlUEVSl6Xo6FYtRPjn1FuSAHiTdro0tME67u20OPPTE7%2FDOoISCefvuqeb1wnkjRVC%2BeNqyY3YsxgYAo95C54FYNYOQPrdXxiN4y%2BV%2FJx1CUJl3mELinzXVP%2BTkjTHUszcCSmfxIZGkGb8gIsLjD1VNT2i1uNCv41tSP6ZzxRlRy1OsEfT83omdTxMSRSEHvKJC2ruPOR2YnOKoa6KyTFC9Nqecar36%2BMVawBPdRbvnRa36zHOUSz9gj2ugrok8iQnaMjijUB2%2BY900HOFnQ7GbfydlcNwZ8rMZKVV%2Bk5SuVuj3mraiCYQf3J1OAcTpte4MAnwWT7cw3yc26Agsi5I%2Bgbkduv%2B1NkKwQdddbRQLWjDPKBBguV2SDkddwJYL3buYsjtGeavTVkS7Yiv4S8ErOacoVcipC%2BsDc1dvNf4SGbrD0I72%2BKxxnuKoPzVeSi9Wq8qEOzjhnCfWl54bZR8UCr6Y7uHWZvOnXRS3upM%2Fmw8pcm960FfAXpr0MGfiaxIMlhym%2B1oWBJSgAzYQoDURpQiR6HDf5ENlWAJ9ih%2FUTs0EvMJj9gKoGOrABK5gEMZHmn5E3pBNO3do43pi3Eh9OSnySWgdHUwzBRFOu0YrZc5s%2FTLLc1w7uip4MCag93rwnlzR5pp34huRUI380mDWniRIzs3l8MD3U69smz9qmNI2sekojftrKmOv9cSgK8cwPG4mfwPYemWVf91k2vh4JglI60udJkOogGRr%2FJODZ50Uk0XoHLDSrGkiuDrttQuIuFw4rRj6CVNbNUZdHDlMGn8%2FQlTufNSYXthQ%3D&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20231030T235451Z&X-Amz-SignedHeaders=host&X-Amz-Expires=300&X-Amz-Credential=ASIAQ3PHCVTY5CTPNFFC%2F20231030%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Signature=2f06e758aca68bce8dcad281abb706e46f1135867a314833740d5892378fa798&hash=5139ce2f6945cb0fb38bace770ff3c87860d360a9c1077e8cf0fea52c4fe03c7&host=68042c943591013ac2b2430a89b270f6af2c76d8dfd086a07176afe7c76c2c61&pii=S0304397500000566&tid=spdf-654f0c1b-8ec2-4879-9902-886dee8e40d7&sid=9eb633476ba2634017193178041b279cd557gxrqa&type=client&tsoh=d3d3LnNjaWVuY2VkaXJlY3QuY29t&ua=0f165c520a575b5208&rr=81e795f6091dec78&cc=us) and [Gumm](https://www.researchgate.net/publication/2614339_Elements_Of_The_General_Theory_Of_Coalgebras). The concept of well-behaved semantics goes back to [Turi and Plotkin](https://homepages.inf.ed.ac.uk/gdp/publications/Math_Op_Sem.pdf) and was adapted by [Jacobs](https://link.springer.com/chapter/10.1007/11780274_20) to the case of regular expressions. We heavily used automata theoretic constructions from the 1960s, originally investigated by [Brzozowski](https://dl.acm.org/doi/10.1145/321239.321249). Due to space constraints, we had to exclude the details of most of the proofs. To dive deep, please take a look at the full Dafny source code, which is available [here](../../../../assets/src/semantics-of-regular-expressions/semantics-of-regular-expressions.zip). \ No newline at end of file diff --git a/assets/src/semantics-of-regular-expressions/Expressions.dfy b/assets/src/semantics-of-regular-expressions/Expressions.dfy deleted file mode 100644 index 45877ff..0000000 --- a/assets/src/semantics-of-regular-expressions/Expressions.dfy +++ /dev/null @@ -1,28 +0,0 @@ -module Expressions { - - /* Definitions */ - - datatype Exp = Zero | One | Char(A) | Plus(Exp, Exp) | Comp(Exp, Exp) | Star(Exp) - - function Eps(e: Exp): bool { - match e - case Zero => false - case One => true - case Char(a) => false - case Plus(e1, e2) => Eps(e1) || Eps(e2) - case Comp(e1, e2) => Eps(e1) && Eps(e2) - case Star(e1) => true - } - - function Delta(e: Exp): A -> Exp { - (a: A) => - match e - case Zero => Zero - case One => Zero - case Char(b) => if a == b then One else Zero - case Plus(e1, e2) => Plus(Delta(e1)(a), Delta(e2)(a)) - case Comp(e1, e2) => Plus(Comp(Delta(e1)(a), e2), Comp(if Eps(e1) then One else Zero, Delta(e2)(a))) - case Star(e1) => Comp(Delta(e1)(a), Star(e1)) - } - -} \ No newline at end of file diff --git a/assets/src/semantics-of-regular-expressions/Languages.dfy b/assets/src/semantics-of-regular-expressions/Languages.dfy deleted file mode 100644 index 970fa00..0000000 --- a/assets/src/semantics-of-regular-expressions/Languages.dfy +++ /dev/null @@ -1,222 +0,0 @@ -module Languages { - - /* Definitions */ - - codatatype Lang = Alpha(eps: bool, delta: A -> Lang) - - function Zero(): Lang { - Alpha(false, (a: A) => Zero()) - } - - function One(): Lang { - Alpha(true, (a: A) => Zero()) - } - - function Singleton(a: A): Lang { - Alpha(false, (b: A) => if a == b then One() else Zero()) - } - - function {:abstemious} Plus(L1: Lang, L2: Lang): Lang { - Alpha(L1.eps || L2.eps, (a: A) => Plus(L1.delta(a), L2.delta(a))) - } - - function {:abstemious} Comp(L1: Lang, L2: Lang): Lang { - Alpha(L1.eps && L2.eps, (a: A) => Plus(Comp(L1.delta(a), L2), Comp(if L1.eps then One() else Zero(), L2.delta(a)))) - } - - function Star(L: Lang): Lang { - Alpha(true, (a: A) => Comp(L.delta(a), Star(L))) - } - - greatest predicate Bisimilar[nat](L1: Lang, L2: Lang) { - && (L1.eps == L2.eps) - && (forall a :: Bisimilar(L1.delta(a), L2.delta(a))) - } - - /* Lemmas */ - - /* Bisimilarity */ - - greatest lemma BisimilarityIsReflexive[nat](L: Lang) - ensures Bisimilar(L, L) - {} - - lemma BisimilarityIsTransitive(L1: Lang, L2: Lang, L3: Lang) - ensures Bisimilar(L1, L2) && Bisimilar(L2, L3) ==> Bisimilar(L1, L3) - { - if Bisimilar(L1,L2) && Bisimilar(L2, L3) { - assert Bisimilar(L1, L3) by { - BisimilarityIsTransitiveAlternative(L1, L2, L3); - } - } - } - - greatest lemma BisimilarityIsTransitiveAlternative[nat](L1: Lang, L2: Lang, L3: Lang) - requires Bisimilar(L1, L2) && Bisimilar(L2, L3) - ensures Bisimilar(L1, L3) - {} - - lemma BisimilarityIsTransitiveAlternativeTwo(k: nat, L1: Lang, L2: Lang, L3: Lang) - ensures Bisimilar#[k](L1, L2) && Bisimilar#[k](L2, L3) ==> Bisimilar#[k](L1, L3) - { - if k != 0 { - if Bisimilar#[k](L1, L2) && Bisimilar#[k](L2, L3) { - forall a ensures Bisimilar#[k - 1](L1.delta(a), L2.delta(a)) && Bisimilar#[k-1](L2.delta(a), L3.delta(a)) { - BisimilarityIsTransitiveAlternativeTwo(k-1, L1.delta(a), L2.delta(a), L3.delta(a)); - } - } - } - } - - greatest lemma BisimilarityIsSymmetric[nat](L1: Lang, L2: Lang) - ensures Bisimilar(L1, L2) ==> Bisimilar(L2, L1) - ensures Bisimilar(L1, L2) <== Bisimilar(L2, L1) - {} - - lemma BisimilarCuttingPrefixes(k: nat, L1: Lang, L2: Lang) - requires forall n: nat :: n <= k + 1 ==> Bisimilar#[n](L1, L2) - ensures forall a :: Bisimilar#[k](L1.delta(a), L2.delta(a)) - { - forall a ensures Bisimilar#[k](L1.delta(a), L2.delta(a)) { - if k != 0 { - assert Bisimilar#[k + 1](L1, L2); - } - } - } - - lemma BisimilarCuttingPrefixes2(k: nat, a: A, L1a: Lang, L1b: Lang) - requires k != 0 - requires forall n: nat :: n <= k + 1 ==> Bisimilar#[n](L1a, L1b) - ensures forall n: nat :: n <= k ==> Bisimilar#[n](L1a.delta(a), L1b.delta(a)) - { - forall n: nat ensures n <= k ==> Bisimilar#[n](L1a.delta(a), L1b.delta(a)) { - if n <= k { - BisimilarCuttingPrefixes(n, L1a, L1b); - } - } - } - - /* Congruence of Plus */ - - greatest lemma PlusCongruence[nat](L1a: Lang, L1b: Lang, L2a: Lang, L2b: Lang) - requires Bisimilar(L1a, L1b) - requires Bisimilar(L2a, L2b) - ensures Bisimilar(Plus(L1a, L2a), Plus(L1b, L2b)) - {} - - lemma PlusCongruenceAlternative(k: nat, L1a: Lang, L1b: Lang, L2a: Lang, L2b: Lang) - requires Bisimilar#[k](L1a, L1b) - requires Bisimilar#[k](L2a, L2b) - ensures Bisimilar#[k](Plus(L1a, L2a), Plus(L1b, L2b)) - {} - - /* Congruence of Comp */ - - lemma CompCongruence(L1a: Lang, L1b: Lang, L2a: Lang, L2b: Lang) - requires Bisimilar(L1a, L1b) - requires Bisimilar(L2a, L2b) - ensures Bisimilar(Comp(L1a, L2a), Comp(L1b, L2b)) - { - forall k ensures Bisimilar#[k](Comp(L1a,L2a), Comp(L1b,L2b)) { - if k != 0 { - var k' :| k' + 1 == k; - CompCongruenceHelper(k', L1a, L1b, L2a, L2b); - } - } - } - - lemma CompCongruenceHelper(k: nat, L1a: Lang, L1b: Lang, L2a: Lang, L2b: Lang) - requires forall n : nat :: n <= k + 1 ==> Bisimilar#[n](L1a, L1b) - requires forall n : nat :: n <= k + 1 ==> Bisimilar#[n](L2a, L2b) - ensures Bisimilar#[k+1](Comp(L1a, L2a), Comp(L1b, L2b)) - { - var lhs := Comp(L1a, L2a); - var rhs := Comp(L1b, L2b); - - assert Bisimilar#[1](L1a, L1b); - assert Bisimilar#[1](L2a, L2b); - assert lhs.eps == rhs.eps; - - forall a ensures (Bisimilar#[k](lhs.delta(a), rhs.delta(a))) { - var x1 := Comp(L1a.delta(a), L2a); - var x2 := Comp(L1b.delta(a), L2b); - assert Bisimilar#[k](x1, x2) by { - if k != 0 { - BisimilarCuttingPrefixes2(k, a, L1a, L1b); - var k' :| k == k' + 1; - CompCongruenceHelper(k', L1a.delta(a), L1b.delta(a), L2a, L2b); - } - } - var y1 := Comp(if L1a.eps then One() else Zero(), L2a.delta(a)); - var y2 := Comp(if L1b.eps then One() else Zero(), L2b.delta(a)); - assert Bisimilar#[k](y1, y2) by { - assert L1a.eps == L1b.eps; - if k != 0 { - if L1a.eps { - BisimilarityIsReflexive(One()); - BisimilarCuttingPrefixes2(k, a, L2a, L2b); - var k' :| k == k' + 1; - CompCongruenceHelper(k', One(), One(), L2a.delta(a), L2b.delta(a)); - } else { - BisimilarityIsReflexive(Zero()); - BisimilarCuttingPrefixes2(k, a, L2a, L2b); - var k' :| k == k' + 1; - CompCongruenceHelper(k', Zero(), Zero(), L2a.delta(a), L2b.delta(a)); - } - } - } - PlusCongruenceAlternative(k, x1, x2, y1, y2); - } - } - - /* Congruence of Star */ - - lemma StarCongruence(L1: Lang, L2: Lang) - requires Bisimilar(L1, L2) - ensures Bisimilar(Star(L1), Star(L2)) - { - forall k ensures Bisimilar#[k](Star(L1), Star(L2)) { - if k != 0 { - var k' :| k' + 1 == k; - StarCongruenceHelper(k', L1, L2); - } - } - } - - lemma StarCongruenceHelper(k: nat, L1: Lang, L2: Lang) - requires forall n: nat :: n <= k + 1 ==> Bisimilar#[n](L1, L2) - ensures Bisimilar#[k+1](Star(L1), Star(L2)) - { - forall a ensures Bisimilar#[k](Star(L1).delta(a), Star(L2).delta(a)) { - if k != 0 { - BisimilarCuttingPrefixes2(k, a, L1, L2); - var k' :| k == k' + 1; - forall n: nat ensures n <= k' + 1 ==> Bisimilar#[n](Star(L1), Star(L2)) { - if 0 < n <= k' + 1 { - var n' :| n == n' + 1; - StarCongruenceHelper(n', L1,L2); - } - } - CompCongruenceHelper(k', L1.delta(a), L2.delta(a), Star(L1), Star(L2)); - } - } - } - -} - - - - - - - - - - - - - - - - - diff --git a/assets/src/semantics-of-regular-expressions/Semantics.dfy b/assets/src/semantics-of-regular-expressions/Semantics.dfy deleted file mode 100644 index 803eb58..0000000 --- a/assets/src/semantics-of-regular-expressions/Semantics.dfy +++ /dev/null @@ -1,244 +0,0 @@ -include "Expressions.dfy" -include "Languages.dfy" - -module Semantics { - - import opened Expressions - import opened Languages - - /* Definitions */ - - /* Denotational Semantics */ - - function Denotational(e: Exp): Lang { - match e - case Zero => Languages.Zero() - case One => Languages.One() - case Char(a) => Languages.Singleton(a) - case Plus(e1, e2) => Languages.Plus(Denotational(e1), Denotational(e2)) - case Comp(e1, e2) => Languages.Comp(Denotational(e1), Denotational(e2)) - case Star(e1) => Languages.Star(Denotational(e1)) - } - - /* Operational Semantics */ - - function Operational(e: Exp): Lang { - Alpha(Eps(e), (a: A) => Operational(Delta(e)(a))) - } - - /* (Co)algebra homomorphisms f: Exp -> Lang */ - - ghost predicate IsCoalgebraHomomorphism(f: Exp -> Lang) { - && (forall e :: f(e).eps == Eps(e)) - && (forall e, a :: Bisimilar(f(e).delta(a), f(Delta(e)(a)))) - } - - ghost predicate IsAlgebraHomomorphism(f: Exp -> Lang) { - forall e :: IsAlgebraHomomorphismPointwise(f, e) - } - - ghost predicate IsAlgebraHomomorphismPointwise(f: Exp -> Lang, e: Exp) { - Bisimilar( - f(e), - match e - case Zero => Languages.Zero() - case One => Languages.One() - case Char(a) => Languages.Singleton(a) - case Plus(e1, e2) => Languages.Plus(f(e1), f(e2)) - case Comp(e1, e2) => Languages.Comp(f(e1), f(e2)) - case Star(e1) => Languages.Star(f(e1)) - ) - } - - /* Lemmas */ - - /* Any two coalgebra homomorphisms f,g: Exp -> Lang are equal up to bisimulation */ - - lemma UniqueCoalgebraHomomorphism(f: Exp -> Lang, g: Exp -> Lang, e: Exp) - requires IsCoalgebraHomomorphism(f) - requires IsCoalgebraHomomorphism(g) - ensures Bisimilar(f(e), g(e)) - { - BisimilarityIsReflexive(f(e)); - BisimilarityIsReflexive(g(e)); - UniqueCoalgebraHomomorphismHelper1(f, g, f(e), g(e)); - } - - lemma UniqueCoalgebraHomomorphismHelper1(f: Exp -> Lang, g: Exp -> Lang, L1: Lang, L2: Lang) - requires IsCoalgebraHomomorphism(f) - requires IsCoalgebraHomomorphism(g) - requires exists e :: Bisimilar(L1, f(e)) && Bisimilar(L2, g(e)) - ensures Bisimilar(L1, L2) - { - forall k ensures Bisimilar#[k](L1, L2) { - if k != 0 { - UniqueCoalgebraHomomorphismHelper2(k, f, g, L1, L2); - } - } - } - - lemma UniqueCoalgebraHomomorphismHelper2(k: nat, f: Exp -> Lang, g: Exp -> Lang, L1: Lang, L2: Lang) - requires IsCoalgebraHomomorphism(f) - requires IsCoalgebraHomomorphism(g) - requires exists e :: Bisimilar#[k](L1, f(e)) && Bisimilar#[k](L2, g(e)) - ensures Bisimilar#[k](L1, L2) - { - var e :| Bisimilar#[k](L1, f(e)) && Bisimilar#[k](L2, g(e)); - if k != 0 { - forall a ensures Bisimilar#[k-1](L1.delta(a), L2.delta(a)) { - BisimilarityIsTransitiveAlternativeTwo(k-1, L1.delta(a), f(e).delta(a), f(Delta(e)(a))); - BisimilarityIsTransitiveAlternativeTwo(k-1, L2.delta(a), g(e).delta(a), g(Delta(e)(a))); - UniqueCoalgebraHomomorphismHelper2(k-1, f, g, L1.delta(a), L2.delta(a)); - } - } - } - - /* Denotational is a algebra homomorphism */ - - lemma DenotationalIsAlgebraHomomorphism() - ensures IsAlgebraHomomorphism(Denotational) - { - forall e ensures IsAlgebraHomomorphismPointwise(Denotational, e) { - BisimilarityIsReflexive(Denotational(e)); - } - } - - /* Denotational is a coalgebra homomorphism */ - - lemma DenotationalIsCoalgebraHomomorphism() - ensures IsCoalgebraHomomorphism(Denotational) - { - forall e ensures Denotational(e).eps == Eps(e) { - DenotationalIsCoalgebraHomomorphismHelper1(e); - } - forall e, a ensures Bisimilar(Denotational(e).delta(a), Denotational(Delta(e)(a))) { - DenotationalIsCoalgebraHomomorphismHelper2(e, a); - } - } - - lemma DenotationalIsCoalgebraHomomorphismHelper1(e: Exp) - ensures Denotational(e).eps == Eps(e) - { - match e - case Zero => - case One => - case Char(a) => - case Plus(e1, e2) => - DenotationalIsCoalgebraHomomorphismHelper1(e1); - DenotationalIsCoalgebraHomomorphismHelper1(e2); - case Comp(e1, e2) => - DenotationalIsCoalgebraHomomorphismHelper1(e1); - DenotationalIsCoalgebraHomomorphismHelper1(e2); - case Star(e1) => - DenotationalIsCoalgebraHomomorphismHelper1(e1); - } - - lemma DenotationalIsCoalgebraHomomorphismHelper2(e: Exp, a: A) - ensures Bisimilar(Denotational(e).delta(a), Denotational(Delta(e)(a))) - { - match e - case Zero => BisimilarityIsReflexive(Languages.Zero()); - case One => BisimilarityIsReflexive(Languages.One()); - case Char(b) => - if a == b { - BisimilarityIsReflexive(Languages.One()); - } else { - BisimilarityIsReflexive(Languages.Zero()); - } - case Plus(e1, e2) => - DenotationalIsCoalgebraHomomorphismHelper2(e1, a); - DenotationalIsCoalgebraHomomorphismHelper2(e2, a); - PlusCongruence(Denotational(e1).delta(a), Denotational(Delta(e1)(a)), Denotational(e2).delta(a), Denotational(Delta(e2)(a))); - case Comp(e1, e2) => - DenotationalIsCoalgebraHomomorphismHelper1(e1); - DenotationalIsCoalgebraHomomorphismHelper2(e1, a); - DenotationalIsCoalgebraHomomorphismHelper2(e2, a); - BisimilarityIsReflexive(Denotational(e2)); - BisimilarityIsReflexive(if Eps(e1) then Languages.One() else Languages.Zero()); - CompCongruence( - Denotational(e1).delta(a), - Denotational(Delta(e1)(a)), - Denotational(e2), - Denotational(e2) - ); - CompCongruence( - if Eps(e1) then Languages.One() else Languages.Zero(), - if Eps(e1) then Languages.One() else Languages.Zero(), - Denotational(e2).delta(a), - Denotational(Delta(e2)(a)) - ); - PlusCongruence( - Languages.Comp(Denotational(e1).delta(a), Denotational(e2)), - Languages.Comp(Denotational(Delta(e1)(a)), Denotational(e2)), - Languages.Comp(if Eps(e1) then Languages.One() else Languages.Zero(), Denotational(e2).delta(a)), - Languages.Comp(if Eps(e1) then Languages.One() else Languages.Zero(), Denotational(Delta(e2)(a))) - ); - case Star(e1) => - DenotationalIsCoalgebraHomomorphismHelper2(e1, a); - BisimilarityIsReflexive(Languages.Star(Denotational(e1))); - CompCongruence(Denotational(e1).delta(a), Denotational(Delta(e1)(a)), Languages.Star(Denotational(e1)), Languages.Star(Denotational(e1))); - } - - /* Operational is a algebra homomorphism */ - - lemma OperationalIsAlgebraHomomorphism() - ensures IsAlgebraHomomorphism(Operational) - { - forall e ensures IsAlgebraHomomorphismPointwise(Operational, e) { - OperationalAndDenotationalAreBisimilar(e); - assert IsAlgebraHomomorphismPointwise(Denotational, e) by { - DenotationalIsAlgebraHomomorphism(); - } - match e - case Zero => - BisimilarityIsTransitive(Operational(Zero), Denotational(Zero), Languages.Zero()); - case One => - BisimilarityIsTransitive(Operational(One), Denotational(One), Languages.One()); - case Char(a) => - BisimilarityIsTransitive(Operational(Char(a)), Denotational(Char(a)), Languages.Singleton(a)); - case Plus(e1, e2) => - BisimilarityIsTransitive(Operational(Plus(e1, e2)), Denotational(Plus(e1, e2)), Languages.Plus(Denotational(e1), Denotational(e2))); - OperationalAndDenotationalAreBisimilar(e1); - BisimilarityIsSymmetric(Denotational(e1), Operational(e1)); - OperationalAndDenotationalAreBisimilar(e2); - BisimilarityIsSymmetric(Denotational(e2), Operational(e2)); - PlusCongruence(Denotational(e1), Operational(e1), Denotational(e2), Operational(e2)); - BisimilarityIsTransitive(Operational(Plus(e1, e2)), Languages.Plus(Denotational(e1), Denotational(e2)), Languages.Plus(Operational(e1), Operational(e2))); - case Comp(e1, e2) => - BisimilarityIsTransitive(Operational(Comp(e1, e2)), Denotational(Comp(e1, e2)), Languages.Comp(Denotational(e1), Denotational(e2))); - OperationalAndDenotationalAreBisimilar(e1); - BisimilarityIsSymmetric(Denotational(e1), Operational(e1)); - OperationalAndDenotationalAreBisimilar(e2); - BisimilarityIsSymmetric(Denotational(e2), Operational(e2)); - CompCongruence(Denotational(e1), Operational(e1), Denotational(e2), Operational(e2)); - BisimilarityIsTransitive(Operational(Comp(e1, e2)), Languages.Comp(Denotational(e1), Denotational(e2)), Languages.Comp(Operational(e1), Operational(e2))); - case Star(e1) => - BisimilarityIsTransitive(Operational(Star(e1)), Denotational(Star(e1)), Languages.Star(Denotational(e1))); - OperationalAndDenotationalAreBisimilar(e1); - BisimilarityIsSymmetric(Denotational(e1), Operational(e1)); - StarCongruence(Denotational(e1), Operational(e1)); - BisimilarityIsTransitive(Operational(Star(e1)), Languages.Star(Denotational(e1)), Languages.Star(Operational(e1))); - } - } - - /* Operational is a coalgebra homomorphism */ - - lemma OperationalIsCoalgebraHomomorphism() - ensures IsCoalgebraHomomorphism(Operational) - { - forall e, a ensures Bisimilar(Operational(e).delta(a), Operational(Delta(e)(a))) { - BisimilarityIsReflexive(Operational(e).delta(a)); - } - } - - /* Operational and Denotational are equal, up to bisimulation */ - - lemma OperationalAndDenotationalAreBisimilar(e: Exp) - ensures Bisimilar(Operational(e), Denotational(e)) - { - OperationalIsCoalgebraHomomorphism(); - DenotationalIsCoalgebraHomomorphism(); - UniqueCoalgebraHomomorphism(Operational, Denotational, e); - } - -} \ No newline at end of file diff --git a/assets/src/semantics-of-regular-expressions/semantics-of-regular-expressions.zip b/assets/src/semantics-of-regular-expressions/semantics-of-regular-expressions.zip new file mode 100644 index 0000000000000000000000000000000000000000..c6a20e53b4bca65145957052e1d048f75590e062 GIT binary patch literal 3654 zcmb8yX*3jG8vyXp*mr68GZ@)r%aS5w4cUevOV-h3$(FJ2V~cFr$*!5PR>D}5>>^|t zYol!0hm;yid3m>bKfE9Q&pr2?d%ryA-us+$f9HYf0m&}_s87drWRf}H-$4=t02lzg z02spE!NbSJ5h;21UZ9yN1AuDpbs%EjB?$5DbovI+0m#TnWB|Zv3IJfmir_gX&m2Bt z_h36h{nCWbr}jv;R$v0G)F73A2fWkyCkooHJJx>QMB}=I|j%m74^)nD|Gq= z^14&;2Dyx+bb*hqTNz?x-9gN9gCga#(S?jy`fq%CElrp%bTvnAzU@S;Bs=JnApU3H z9upUa6zrV2Zf@$~$_FIJV;wZXu`&+M)& zmdsVI*Es3(UJc0Fd(Ubge)a3rlclQI5C%5m%9g=nL8jH-?G_d@MP3%W?}}yK3s#hk z-T=S~GKzOREZ3y@GiogRA|S7iv3xP`;Z!AZY%4YhH(?`~X_ir`5)3u(++1f2f!2ud6(OSTm z2aWe)B`#D&uj=n_K<*Do-%^pOx#T!m5P=Pae0O zOvB6x4EpYw%~^FcJHj5%kQ<*xV@=f8Bj*TN4sa<2IMU;zUQg>$trmH>Glee)4_WsjncO)ow)YoSud zk8^0T3p6MDVLx^_NW;uiWFs?W?13oeWzg0eQ3~PSY2`Tn!ql0{)LU4Q7qY~4KI-=? ziAvkQ0a(S z_3e>1MX6Sm#U+-aN|=(RkZWrLZT5#jnO`ow)(HdwlRfS?)4_3DE5p~rTNyv|yl~gE zXdZW3`;tT`#kRkHS4SiG0{*O!`r7_-!Yh;Es!Tusm_QN}RF8_fKqQq!pa1~Qbb$WX zjS#n+|8?Ww900&{>V{T;mp1~5bn*1~Er<9C>bZllY zg$_R|O3zuBALBGXGlYsi(H*f7cjtNAMli%EIrLf=y*#f;w?5^tJ7FloNZUF&&_h~I zVFz{J$;Bf=yM+7V{W}^#5rtN*ugEj5O|kXiFQL@In;qgY&?zP!kpZ73b#@GijjY#1 zh$17c^XRYMlB+Q(77HGYFRYtW5>Bjm>R?jN`6?KcNr}G~?NM~s`kE?hx2!;3UQG?E zT}9=fCb}nH|E?K@Gn7`wOHh6+;@*?BMsbNW>*cSgyZ6aqY)H1@NeS_ETe4MPIaktM zll^b|W|Q!7x6B>VK&+Dj*Gc!%KP!R!!M-PP>h9Tl|E>g`|8K#IIQ}KLzJrI8uY(if zcOBSz|F7Uf79Y<94?7jSBjToO8qQ>Xc{(Cn*)0MEx?gmggz|RQuF(%n`_QX%+`dE7 zI4_!-wxQ&sX0J0r=xf~}1_=1iEM3IZ@VX&+!B%eudnn#K{IPr8Mp#@lJrT6~3kb1B zq@V>>9!T}Uu%r+Zs29ccJU^vk1>@}*8)ZeW79CiJhT4lUQk}e4>QWm? z>GscO*)>|M*0p*M-n|yVG@i@+Jp`gDgf>wfWW1blo znDN7<493J4FVCyz*{8!y#!90@4I$|ul`c=+)UC9PkyRU+Lqbs~0&P9(Y?96|3{uWv zOXF$(RXgmRhRu;Np0H1e4A4Wa1lu}V_l1wP(G>FiQVI?flJs1#QAY>LA|mBITFo%8*!qsgV(_)geiEbC z9kKvjcV$!*9njYRqO?!4Z8YOjaP-%H=9dW$r7DG(-lkiLH&L=NZ7=Hxuu#jJrXR~0 z4`+HCz;jgI4hG*F8k721C$u(4?R6nFOo`MULXdnGJIK@^;B5L%bGmcog36@v1kCY+ zxWVzJB5sDRky)IeHP_p|(pe%IQbuzXnLMC~CTM6H;zEO1!%UtQ4eN}K^bwM?0&6*K ze^Op*HS5mbvDaDUnKH^tzG4hvM8q2$!;*tDovaddhi-FJJeIvUGUXJ@W^ZVt@!s&p z?bWn}NZ$wMxvTw|ilM=*&!uMS-8@&q{huv02TLH$Ep{v#4l5X1KkV@Y19hY;6d2)R zZ^ybDS&YbEWgardn%R}f#1T56&Umg>)r|$?!z`h3__QOJR`YrsiwA&5rlCWb@s4r- zBzz9>k#bim$*nA-ReGs;G%@zO&c=SFcDE4odDPOS02~sep0BpgqlfW(n@7xPgH?TR zY{3jX|9Azj97JH1akSZr&fKvwv%nmMyg5H~qQ#4U-u~UBZPRvRALskntg9YMuCo*Q zBWby&EpjdMWMCiIV8A5Td=!NZeiA%ag`JIF7@M8PjZ)sduizAmwhI50FZjhRqeJdB zW7boRcl@a;AZCLO)7*(7=y|qQ9HzDYk@iGV zIj>Feix4h`_a@NoXXW{%>0MzWRsS0bvUTDE3Vr9!Zs6qy?7ag&S(P0=Defk)*1LTh)aRZ=$!gxB|NoZBvIwYD?i zOmQsmyVr(J;jAhi9cVVj0sb97#@9OY>oCEqN~k5JN`Co+m%4&X6;7HQNB3B6s$ILiNbb zT>$=WkNyPktUdZqpiyV5ztt@Lnfgr7KY#QyLH`qI)HO=L-}Us*QS7Y2LJ$`4%SnFxEaoN{{UWCme2qI literal 0 HcmV?d00001 From 8bbaf7cd7f6976bb13db6f27b905ed04b0694443 Mon Sep 17 00:00:00 2001 From: stefan-aws Date: Thu, 14 Dec 2023 02:47:08 +0000 Subject: [PATCH 03/62] w.r.t. --- _posts/2024-01-10-semantics-of-regular-expressions.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_posts/2024-01-10-semantics-of-regular-expressions.markdown b/_posts/2024-01-10-semantics-of-regular-expressions.markdown index 37a5d60..bf91b95 100644 --- a/_posts/2024-01-10-semantics-of-regular-expressions.markdown +++ b/_posts/2024-01-10-semantics-of-regular-expressions.markdown @@ -37,7 +37,7 @@ To some, this choice might seem odd at first sight. If you are familiar with the If you think of formal languages as the set of all sets of finite sequences, you will soon realise that languages admit quite a bit of algebraic structure. For example, there exist two languages of distinct importance (can you already guess which ones?), and one can obtain a new language by taking e.g. the union of two languages. In fact, if you think about it for a bit longer, you’ll realise that formal languages admit exactly the same [type of algebraic structure](https://en.wikipedia.org/wiki/F-algebra) as the one you’ve encountered when defining the set of regular expressions! -First, there exists the empty language `Zero()` that contains no words at all. Under above view, we find `Zero().eps == false` and `Zero().delta(a) == Zero`, since the empty set does not contain the empty sequence, and the derivative `iset s | [a] + [s] in iset{}` w.r.t any `a: A` yields again the empty set, respectively. We thus define: +First, there exists the empty language `Zero()` that contains no words at all. Under above view, we find `Zero().eps == false` and `Zero().delta(a) == Zero`, since the empty set does not contain the empty sequence, and the derivative `iset s | [a] + [s] in iset{}` with respect to any `a: A` yields again the empty set, respectively. We thus define: ``` ghost function Zero(): Lang { From a47b2ed06fc4513a6bd2274c9b61e9b29aaf7c22 Mon Sep 17 00:00:00 2001 From: stefan-aws Date: Thu, 14 Dec 2023 02:48:36 +0000 Subject: [PATCH 04/62] space --- _posts/2024-01-10-semantics-of-regular-expressions.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_posts/2024-01-10-semantics-of-regular-expressions.markdown b/_posts/2024-01-10-semantics-of-regular-expressions.markdown index bf91b95..29f4a10 100644 --- a/_posts/2024-01-10-semantics-of-regular-expressions.markdown +++ b/_posts/2024-01-10-semantics-of-regular-expressions.markdown @@ -71,7 +71,7 @@ ghost function Star(L: Lang): Lang { ### Denotational Semantics as Induced Morphism -The denotational semantics of regular expressions can now be defined through induction, as a function `Denotational: Exp -> Lang` , by making use of the operations on languages we have just defined: +The denotational semantics of regular expressions can now be defined through induction, as a function `Denotational: Exp -> Lang`, by making use of the operations on languages we have just defined: ``` ghost function Denotational(e: Exp): Lang { From 8ad14e3fd4adef9c63db7fec560882434e3c0cf7 Mon Sep 17 00:00:00 2001 From: stefan-aws Date: Thu, 14 Dec 2023 12:49:45 +0000 Subject: [PATCH 05/62] fixes --- ...-semantics-of-regular-expressions.markdown | 63 ++++- .../Expressions.dfy | 28 ++ .../Languages.dfy | 224 ++++++++++++++++ .../Semantics.dfy | 244 ++++++++++++++++++ .../semantics-of-regular-expressions.zip | Bin 3654 -> 0 bytes 5 files changed, 545 insertions(+), 14 deletions(-) create mode 100644 assets/src/semantics-of-regular-expressions/Expressions.dfy create mode 100644 assets/src/semantics-of-regular-expressions/Languages.dfy create mode 100644 assets/src/semantics-of-regular-expressions/Semantics.dfy delete mode 100644 assets/src/semantics-of-regular-expressions/semantics-of-regular-expressions.zip diff --git a/_posts/2024-01-10-semantics-of-regular-expressions.markdown b/_posts/2024-01-10-semantics-of-regular-expressions.markdown index 29f4a10..88444a9 100644 --- a/_posts/2024-01-10-semantics-of-regular-expressions.markdown +++ b/_posts/2024-01-10-semantics-of-regular-expressions.markdown @@ -31,11 +31,11 @@ We define the set of formal languages parametric in an alphabet `A` as an coindu codatatype Lang = Alpha(eps: bool, delta: A -> Lang) ``` -To some, this choice might seem odd at first sight. If you are familiar with the regular expressions, you likely have expected the set of formal languages to be defined more concretely as the set of all sets of finite sequences (sometimes called *words*), `iset>`. Rest assured, we agree — up to an appropriate notion of equality! Where as you characterise languages intrinsically, we treat them extrinsically, in terms of their universal property: [it is well known](https://ir.cwi.nl/pub/28550/rutten.pdf) that `iset>` forms the *greatest* [coalgebraic](https://en.wikipedia.org/wiki/F-coalgebra) structure (think of a deterministic automaton without initial state) `S` that is equipped with a functions `eps: S -> bool` and `delta: S → (A → S)`. Indeed, for any set `U` of finite sequences, we can verify whether `U` contains the empty sequence, `U.eps == [] in U`, and for any `a: A`, we can transition to the set `U.delta(a) == iset s | [a] + s in U`. Here, we choose the more abstract perspective on formal languages at it hides irrelevant specifics and thus allows us to write more elegant proofs. +To some, this choice might seem odd at first sight. If you are familiar with the topic, you likely have expected the set of formal languages to be defined more concretely as the set of all sets of finite sequences (sometimes called *words*), `iset>`. Rest assured, we agree — up to an appropriate notion of equality! Where as you characterise languages intrinsically, we treat them extrinsically, in terms of their universal property: [it is well known](https://ir.cwi.nl/pub/28550/rutten.pdf) that `iset>` forms the *greatest* [coalgebraic](https://en.wikipedia.org/wiki/F-coalgebra) structure (think of a deterministic automaton without initial state) `S` that is equipped with functions `eps: S -> bool` and `delta: S → (A → S)`. Indeed, for any set `U` of finite sequences, we can verify whether `U` contains the empty sequence, `U.eps == ([] in U)`, and for any `a: A`, we can transition to the set `U.delta(a) == (iset s | [a] + s in U)`. Here, we choose the more abstract perspective on formal languages at it hides irrelevant specifics and thus allows us to write more elegant proofs. ### An Algebra of Formal Languages -If you think of formal languages as the set of all sets of finite sequences, you will soon realise that languages admit quite a bit of algebraic structure. For example, there exist two languages of distinct importance (can you already guess which ones?), and one can obtain a new language by taking e.g. the union of two languages. In fact, if you think about it for a bit longer, you’ll realise that formal languages admit exactly the same [type of algebraic structure](https://en.wikipedia.org/wiki/F-algebra) as the one you’ve encountered when defining the set of regular expressions! +If you think of formal languages as the set of all sets of finite sequences, you will soon realise that languages admit quite a bit of algebraic structure. For example, there exist two languages of distinct importance (can you already guess which ones?), and one can obtain a new language by taking e.g. the union of two languages. In fact, if you think about it for a bit longer, you’ll realise that formal languages admit exactly the same [type of algebraic structure](https://en.wikipedia.org/wiki/F-algebra) as the one you’ve encountered when we defined regular expressions! First, there exists the empty language `Zero()` that contains no words at all. Under above view, we find `Zero().eps == false` and `Zero().delta(a) == Zero`, since the empty set does not contain the empty sequence, and the derivative `iset s | [a] + [s] in iset{}` with respect to any `a: A` yields again the empty set, respectively. We thus define: @@ -193,7 +193,7 @@ In this section, we provide an alternative perspective on the semantics of regul ### A Coalgebra of Regular Expressions -In [An Algebra of Formal Languages](#an-algebra-of-formal-languagesn Algebra of Formal Languages) we equipped the set of formal languages with an algebraic structure that resembles the one of regular expressions in [Regular Expressions as Datatype](#regular-expressions-as-datatype). Now, we are aiming for the reverse: we would like to equip the set of regular expressions with a (co)algebraic structure that resembles the one of formal languages in [Formal Languages as Codatatype](#formal-languages-as-codatatype). More concretely, we would like to turn the set of regular expressions into a deterministic automaton in which a state `e` is i) accepting iff `Eps(e)` and ii) transitions to a state `Delta(e)(a)` if given the input `a: A`. Note how our definitions closely resemble the Brzozowski derivatives we encountered in [An Algebra of Formal Languages](#an-algebra-of-formal-languages): +In [An Algebra of Formal Languages](#an-algebra-of-formal-languagesn Algebra of Formal Languages) we equipped the set of formal languages with an algebraic structure that resembles the one of regular expressions. Now, we are aiming for the reverse: we would like to equip the set of regular expressions with a coalgebraic structure that resembles the one of formal languages. More concretely, we would like to turn the set of regular expressions into a deterministic automaton (without initial state) in which a state `e` is i) accepting iff `Eps(e) == true` and ii) transitions to a state `Delta(e)(a)` if given the input `a: A`. Note how our definitions resemble the Brzozowski derivatives we previously encountered: ``` ghost function Eps(e: Exp): bool { @@ -230,7 +230,7 @@ function Operational(e: Exp): Lang { ### Operational Semantics as Coalgebra Homomorphism -In [Denotational Semantics as Algebra Homomorphism](#denotational-semantics-as-algebra-homomorphism) we defined algebra homomorphisms as functions `f: Exp -> Lang` that commute with the algebraic structures on regular expressions and formal languages, respectively. Analogously, let us now call a function `f` of the same type a *coalgebra homomorphism*, if it commutes with the *coalgebraic* structures we defined in [A Coalgebra of Regular Expressions](#a-coalgebra-of-regular-expressions) and [Formal Languages as Codatatype](#formal-languages-as-codatatype), respectively: +In [Denotational Semantics as Algebra Homomorphism](#denotational-semantics-as-algebra-homomorphism) we defined algebra homomorphisms as functions `f: Exp -> Lang` that commute with the algebraic structures of regular expressions and formal languages, respectively. Analogously, let us now call a function `f` of the same type a *coalgebra homomorphism*, if it commutes with the *coalgebraic* structures of regular expressions and formal languages, respectively: ``` ghost predicate IsCoalgebraHomomorphism(f: Exp -> Lang) { @@ -260,7 +260,7 @@ So far, we have seen two dual approaches for assigning a formal language semanti Next, we show that the denotational and operational semantics of regular expressions are *well-behaved*: they constitute two sides of the same coin. First, we show that `Denotational` is also a coalgebra homomorphism, and that coalgebra homomorphisms are unique up to bisimulation. We then deduce from the former that `Denotational` and `Operational` coincide pointwise, up to bisimulation. Finally, we show that `Operational` is also an algebra homomorphism. -### Denotational Semantics As Coalgebra Homomorphism +### Denotational Semantics as Coalgebra Homomorphism In this section, we establish that `Denotational` does not only commute with the algebraic structures of regular expressions and formal languages, but also with their coalgebraic structures: @@ -277,7 +277,7 @@ greatest lemma PlusCongruence[nat](L1a: Lang, L1b: Lang, L2a: Lang, L2b requires Bisimilar(L1a, L1b) requires Bisimilar(L2a, L2b) ensures Bisimilar(Plus(L1a, L2a), Plus(L1b, L2b)) -{...} +{} lemma CompCongruence(L1a: Lang, L1b: Lang, L2a: Lang, L2b: Lang) requires Bisimilar(L1a, L1b) @@ -286,7 +286,7 @@ lemma CompCongruence(L1a: Lang, L1b: Lang, L2a: Lang, L2b: Lang) {...} ``` -The proof of `PlusCongruence` is relatively short, as we can take advantage of the syntactic sugaring of the `greatest lemma` construct. For `CompCongruence` we have to put in a bit more manual work. +Dafny is able to prove `PlusCongruence` on its own, as it can take advantage of the syntactic sugaring of the `greatest lemma` construct. For `CompCongruence` we have to put in a bit of manual work ourselves. ### Coalgebra Homomorphisms Are Unique @@ -300,12 +300,47 @@ lemma UniqueCoalgebraHomomorphism(f: Exp -> Lang, g: Exp -> Lang, e: Ex {...} ``` -[As is well-known](https://ir.cwi.nl/pub/28550/rutten.pdf), the statement may in fact be strengthened to: for *any* coalgebra `C` there exists only one coalgebra homomorphism of type `C -> Lang` , up to pointwise bisimulation. For our purposes, the weaker statement above will be sufficient. At the heart of the proof lies the observation that bisimilarity is transitive: +[As is well-known](https://ir.cwi.nl/pub/28550/rutten.pdf), the statement may in fact be strengthened to: for *any* coalgebra `C` there exists exactly one coalgebra homomorphism of type `C -> Lang` , up to pointwise bisimulation. For our purposes, the weaker statement above will be sufficient. At the heart of the proof lies the observation that bisimilarity is transitive: ``` -lemma BisimilarityIsTransitive(L1: Lang, L2: Lang, L3: Lang) - ensures Bisimilar(L1, L2) && Bisimilar(L2, L3) ==> Bisimilar(L1, L3) -{...} +greatest lemma BisimilarityIsTransitive[nat](L1: Lang, L2: Lang, L3: Lang) + requires Bisimilar(L1, L2) && Bisimilar(L2, L3) + ensures Bisimilar(L1, L3) +{} +``` + +In fact, in practice, we actually use a slightly more fine grained formalisation of transitivity, as is illustrated below by the proof of `UniqueCoalgebraHomomorphismHelperPointwise`, which is used in the proof of `UniqueCoalgebraHomomorphism`: + +``` +lemma UniqueCoalgebraHomomorphismHelperPointwise(k: nat, f: Exp -> Lang, g: Exp -> Lang, L1: Lang, L2: Lang) + requires IsCoalgebraHomomorphism(f) + requires IsCoalgebraHomomorphism(g) + requires exists e :: Bisimilar#[k](L1, f(e)) && Bisimilar#[k](L2, g(e)) + ensures Bisimilar#[k](L1, L2) +{ + var e :| Bisimilar#[k](L1, f(e)) && Bisimilar#[k](L2, g(e)); + if k != 0 { + forall a ensures Bisimilar#[k-1](L1.delta(a), L2.delta(a)) { + BisimilarityIsTransitivePointwise(k-1, L1.delta(a), f(e).delta(a), f(Delta(e)(a))); + BisimilarityIsTransitivePointwise(k-1, L2.delta(a), g(e).delta(a), g(Delta(e)(a))); + UniqueCoalgebraHomomorphismHelperPointwise(k-1, f, g, L1.delta(a), L2.delta(a)); + } + } +} + +lemma BisimilarityIsTransitivePointwise(k: nat, L1: Lang, L2: Lang, L3: Lang) + ensures Bisimilar#[k](L1, L2) && Bisimilar#[k](L2, L3) ==> Bisimilar#[k](L1, L3) +{ + if k != 0 { + if Bisimilar#[k](L1, L2) && Bisimilar#[k](L2, L3) { + assert Bisimilar#[k](L1, L3) by { + forall a ensures Bisimilar#[k-1](L1.delta(a), L3.delta(a)) { + BisimilarityIsTransitivePointwise(k-1, L1.delta(a), L2.delta(a), L3.delta(a)); + } + } + } + } +} ``` ### Denotational and Operational Semantics Are Bisimilar @@ -322,7 +357,7 @@ lemma OperationalAndDenotationalAreBisimilar(e: Exp) } ``` -### Operational Semantics As Algebra Homomorphism +### Operational Semantics as Algebra Homomorphism As a little extra, for the sake of symmetry, let us also prove that `Operational` is an algebra homomorphism. (We already know that it is a coalgebra homomorphism, and that `Denotational` is both an algebra and coalgebra homomorphism.) @@ -332,7 +367,7 @@ lemma OperationalIsAlgebraHomomorphism() {...} ``` -The idea of the proof is to take advantage of `Denotational` being an algebra homomorphism, by translating its properties to `Operational` via the already proven main result in [Denotational and Operational Semantics Are Bisimilar](#denotational-and-operational-semantics-are-bisimilar). The relevant new statements capture that bisimilarity is symmetric and a congruence with respect to the `Star` operation: +The idea of the proof is to take advantage of `Denotational` being an algebra homomorphism, by translating its properties to `Operational` via the lemma in [Denotational and Operational Semantics Are Bisimilar](#denotational-and-operational-semantics-are-bisimilar). The relevant new statements capture that bisimilarity is symmetric and a congruence with respect to the `Star` operation: ``` greatest lemma BisimilarityIsSymmetric[nat](L1: Lang, L2: Lang) @@ -348,4 +383,4 @@ lemma StarCongruence(L1: Lang, L2: Lang) ## Conclusion -We have used Dafny’s built-in inductive and coinductive reasoning capabilities to define two language semantics for regular expressions: denotational and operational semantics. Through a number of dualities — construction and deconstruction, algebras and coalgebras, and congruence and bisimilarity — we have proven the semantics to be two sides of the same coin. The blogpost is inspired by research in the field of [Coalgebra](https://en.wikipedia.org/wiki/F-coalgebra), which was pioneered by [Rutten](https://pdf.sciencedirectassets.com/271538/1-s2.0-S0304397500X01466/1-s2.0-S0304397500000566/main.pdf?X-Amz-Security-Token=IQoJb3JpZ2luX2VjELj%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FwEaCXVzLWVhc3QtMSJIMEYCIQDF6w6Fgo6yaRHLxOonVyd6c2NK5OBnjUFYmLPQ5%2FxwJwIhAO8MTWhCr6iXXkvrDJb5ylzqqKxwCWTsO%2BQHcOnpKBl2KrsFCOH%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FwEQBRoMMDU5MDAzNTQ2ODY1Igxjn%2BJg6E9T8pxDOv8qjwUsJZLdZb1wZRX8%2FfWwSP4ghcWU0g0XImQqnhAbMTKMrEGol%2FS%2BLRuWJVuTwdOKULbI%2BM6LjAwD3PqFD6r0eLS4dmUB5NQUjuiOV0EdgFC49Aws7ICWRt2%2BqmudKI%2FA2Ng4o8LQbeYm6S0xFuS61lNMaNqeIdaW39AmhpX6cH7BaisztuRPXsTntdxi7eZTstIc1LlpvEaZC5fQpQm2TqEaxQ6ZBMKytxnbfxQGjYTiUVk%2FP8gX4x4cqhWMoPeVZQHalY8oaztSvjDlwGoMxFjA%2FbAWlsrsu36EMQ4Ln%2FH1tT%2B%2BY9QHK7eP30ozRlUEVSl6Xo6FYtRPjn1FuSAHiTdro0tME67u20OPPTE7%2FDOoISCefvuqeb1wnkjRVC%2BeNqyY3YsxgYAo95C54FYNYOQPrdXxiN4y%2BV%2FJx1CUJl3mELinzXVP%2BTkjTHUszcCSmfxIZGkGb8gIsLjD1VNT2i1uNCv41tSP6ZzxRlRy1OsEfT83omdTxMSRSEHvKJC2ruPOR2YnOKoa6KyTFC9Nqecar36%2BMVawBPdRbvnRa36zHOUSz9gj2ugrok8iQnaMjijUB2%2BY900HOFnQ7GbfydlcNwZ8rMZKVV%2Bk5SuVuj3mraiCYQf3J1OAcTpte4MAnwWT7cw3yc26Agsi5I%2Bgbkduv%2B1NkKwQdddbRQLWjDPKBBguV2SDkddwJYL3buYsjtGeavTVkS7Yiv4S8ErOacoVcipC%2BsDc1dvNf4SGbrD0I72%2BKxxnuKoPzVeSi9Wq8qEOzjhnCfWl54bZR8UCr6Y7uHWZvOnXRS3upM%2Fmw8pcm960FfAXpr0MGfiaxIMlhym%2B1oWBJSgAzYQoDURpQiR6HDf5ENlWAJ9ih%2FUTs0EvMJj9gKoGOrABK5gEMZHmn5E3pBNO3do43pi3Eh9OSnySWgdHUwzBRFOu0YrZc5s%2FTLLc1w7uip4MCag93rwnlzR5pp34huRUI380mDWniRIzs3l8MD3U69smz9qmNI2sekojftrKmOv9cSgK8cwPG4mfwPYemWVf91k2vh4JglI60udJkOogGRr%2FJODZ50Uk0XoHLDSrGkiuDrttQuIuFw4rRj6CVNbNUZdHDlMGn8%2FQlTufNSYXthQ%3D&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20231030T235451Z&X-Amz-SignedHeaders=host&X-Amz-Expires=300&X-Amz-Credential=ASIAQ3PHCVTY5CTPNFFC%2F20231030%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Signature=2f06e758aca68bce8dcad281abb706e46f1135867a314833740d5892378fa798&hash=5139ce2f6945cb0fb38bace770ff3c87860d360a9c1077e8cf0fea52c4fe03c7&host=68042c943591013ac2b2430a89b270f6af2c76d8dfd086a07176afe7c76c2c61&pii=S0304397500000566&tid=spdf-654f0c1b-8ec2-4879-9902-886dee8e40d7&sid=9eb633476ba2634017193178041b279cd557gxrqa&type=client&tsoh=d3d3LnNjaWVuY2VkaXJlY3QuY29t&ua=0f165c520a575b5208&rr=81e795f6091dec78&cc=us) and [Gumm](https://www.researchgate.net/publication/2614339_Elements_Of_The_General_Theory_Of_Coalgebras). The concept of well-behaved semantics goes back to [Turi and Plotkin](https://homepages.inf.ed.ac.uk/gdp/publications/Math_Op_Sem.pdf) and was adapted by [Jacobs](https://link.springer.com/chapter/10.1007/11780274_20) to the case of regular expressions. We heavily used automata theoretic constructions from the 1960s, originally investigated by [Brzozowski](https://dl.acm.org/doi/10.1145/321239.321249). Due to space constraints, we had to exclude the details of most of the proofs. To dive deep, please take a look at the full Dafny source code, which is available [here](../../../../assets/src/semantics-of-regular-expressions/semantics-of-regular-expressions.zip). \ No newline at end of file +We have used Dafny’s built-in inductive and coinductive reasoning capabilities to define two language semantics for regular expressions: denotational and operational semantics. Through a number of dualities — construction and deconstruction, algebras and coalgebras, and congruence and bisimilarity — we have proven the semantics to be two sides of the same coin. The blogpost is inspired by research in the field of [Coalgebra](https://en.wikipedia.org/wiki/F-coalgebra), which was pioneered by [Rutten](https://pdf.sciencedirectassets.com/271538/1-s2.0-S0304397500X01466/1-s2.0-S0304397500000566/main.pdf), [Gumm](https://www.researchgate.net/publication/2614339_Elements_Of_The_General_Theory_Of_Coalgebras), and others. The concept of well-behaved semantics goes back to [Turi and Plotkin](https://homepages.inf.ed.ac.uk/gdp/publications/Math_Op_Sem.pdf) and was adapted by [Jacobs](https://link.springer.com/chapter/10.1007/11780274_20) to the case of regular expressions. We heavily used automata theoretic constructions from the 1960s, originally investigated by [Brzozowski](https://dl.acm.org/doi/10.1145/321239.321249). Due to space constraints, we had to exclude the details of most of the proofs. To dive deep, please take a look at the full Dafny source code, which is available [here](../../../../assets/src/semantics-of-regular-expressions/Languages.dfy), [here](../../../../assets/src/semantics-of-regular-expressions/Semantics.dfy), and [here](../../../../assets/src/semantics-of-regular-expressions/Expressions.dfy). \ No newline at end of file diff --git a/assets/src/semantics-of-regular-expressions/Expressions.dfy b/assets/src/semantics-of-regular-expressions/Expressions.dfy new file mode 100644 index 0000000..45877ff --- /dev/null +++ b/assets/src/semantics-of-regular-expressions/Expressions.dfy @@ -0,0 +1,28 @@ +module Expressions { + + /* Definitions */ + + datatype Exp = Zero | One | Char(A) | Plus(Exp, Exp) | Comp(Exp, Exp) | Star(Exp) + + function Eps(e: Exp): bool { + match e + case Zero => false + case One => true + case Char(a) => false + case Plus(e1, e2) => Eps(e1) || Eps(e2) + case Comp(e1, e2) => Eps(e1) && Eps(e2) + case Star(e1) => true + } + + function Delta(e: Exp): A -> Exp { + (a: A) => + match e + case Zero => Zero + case One => Zero + case Char(b) => if a == b then One else Zero + case Plus(e1, e2) => Plus(Delta(e1)(a), Delta(e2)(a)) + case Comp(e1, e2) => Plus(Comp(Delta(e1)(a), e2), Comp(if Eps(e1) then One else Zero, Delta(e2)(a))) + case Star(e1) => Comp(Delta(e1)(a), Star(e1)) + } + +} \ No newline at end of file diff --git a/assets/src/semantics-of-regular-expressions/Languages.dfy b/assets/src/semantics-of-regular-expressions/Languages.dfy new file mode 100644 index 0000000..9ee30f0 --- /dev/null +++ b/assets/src/semantics-of-regular-expressions/Languages.dfy @@ -0,0 +1,224 @@ +module Languages { + + /* Definitions */ + + codatatype Lang = Alpha(eps: bool, delta: A -> Lang) + + function Zero(): Lang { + Alpha(false, (a: A) => Zero()) + } + + function One(): Lang { + Alpha(true, (a: A) => Zero()) + } + + function Singleton(a: A): Lang { + Alpha(false, (b: A) => if a == b then One() else Zero()) + } + + function {:abstemious} Plus(L1: Lang, L2: Lang): Lang { + Alpha(L1.eps || L2.eps, (a: A) => Plus(L1.delta(a), L2.delta(a))) + } + + function {:abstemious} Comp(L1: Lang, L2: Lang): Lang { + Alpha(L1.eps && L2.eps, (a: A) => Plus(Comp(L1.delta(a), L2), Comp(if L1.eps then One() else Zero(), L2.delta(a)))) + } + + function Star(L: Lang): Lang { + Alpha(true, (a: A) => Comp(L.delta(a), Star(L))) + } + + greatest predicate Bisimilar[nat](L1: Lang, L2: Lang) { + && (L1.eps == L2.eps) + && (forall a :: Bisimilar(L1.delta(a), L2.delta(a))) + } + + /* Lemmas */ + + /* Bisimilarity */ + + greatest lemma BisimilarityIsReflexive[nat](L: Lang) + ensures Bisimilar(L, L) + {} + + lemma BisimilarityIsTransitive(L1: Lang, L2: Lang, L3: Lang) + ensures Bisimilar(L1, L2) && Bisimilar(L2, L3) ==> Bisimilar(L1, L3) + { + if Bisimilar(L1,L2) && Bisimilar(L2, L3) { + assert Bisimilar(L1, L3) by { + BisimilarityIsTransitiveAlternative(L1, L2, L3); + } + } + } + + greatest lemma BisimilarityIsTransitiveAlternative[nat](L1: Lang, L2: Lang, L3: Lang) + requires Bisimilar(L1, L2) && Bisimilar(L2, L3) + ensures Bisimilar(L1, L3) + {} + + lemma BisimilarityIsTransitivePointwise(k: nat, L1: Lang, L2: Lang, L3: Lang) + ensures Bisimilar#[k](L1, L2) && Bisimilar#[k](L2, L3) ==> Bisimilar#[k](L1, L3) + { + if k != 0 { + if Bisimilar#[k](L1, L2) && Bisimilar#[k](L2, L3) { + assert Bisimilar#[k](L1, L3) by { + forall a ensures Bisimilar#[k-1](L1.delta(a), L3.delta(a)) { + BisimilarityIsTransitivePointwise(k-1, L1.delta(a), L2.delta(a), L3.delta(a)); + } + } + } + } + } + + greatest lemma BisimilarityIsSymmetric[nat](L1: Lang, L2: Lang) + ensures Bisimilar(L1, L2) ==> Bisimilar(L2, L1) + ensures Bisimilar(L1, L2) <== Bisimilar(L2, L1) + {} + + lemma BisimilarCuttingPrefixes(k: nat, L1: Lang, L2: Lang) + requires forall n: nat :: n <= k + 1 ==> Bisimilar#[n](L1, L2) + ensures forall a :: Bisimilar#[k](L1.delta(a), L2.delta(a)) + { + forall a ensures Bisimilar#[k](L1.delta(a), L2.delta(a)) { + if k != 0 { + assert Bisimilar#[k + 1](L1, L2); + } + } + } + + lemma BisimilarCuttingPrefixes2(k: nat, a: A, L1a: Lang, L1b: Lang) + requires k != 0 + requires forall n: nat :: n <= k + 1 ==> Bisimilar#[n](L1a, L1b) + ensures forall n: nat :: n <= k ==> Bisimilar#[n](L1a.delta(a), L1b.delta(a)) + { + forall n: nat ensures n <= k ==> Bisimilar#[n](L1a.delta(a), L1b.delta(a)) { + if n <= k { + BisimilarCuttingPrefixes(n, L1a, L1b); + } + } + } + + /* Congruence of Plus */ + + greatest lemma PlusCongruence[nat](L1a: Lang, L1b: Lang, L2a: Lang, L2b: Lang) + requires Bisimilar(L1a, L1b) + requires Bisimilar(L2a, L2b) + ensures Bisimilar(Plus(L1a, L2a), Plus(L1b, L2b)) + {} + + lemma PlusCongruenceAlternative(k: nat, L1a: Lang, L1b: Lang, L2a: Lang, L2b: Lang) + requires Bisimilar#[k](L1a, L1b) + requires Bisimilar#[k](L2a, L2b) + ensures Bisimilar#[k](Plus(L1a, L2a), Plus(L1b, L2b)) + {} + + /* Congruence of Comp */ + + lemma CompCongruence(L1a: Lang, L1b: Lang, L2a: Lang, L2b: Lang) + requires Bisimilar(L1a, L1b) + requires Bisimilar(L2a, L2b) + ensures Bisimilar(Comp(L1a, L2a), Comp(L1b, L2b)) + { + forall k ensures Bisimilar#[k](Comp(L1a,L2a), Comp(L1b,L2b)) { + if k != 0 { + var k' :| k' + 1 == k; + CompCongruenceHelper(k', L1a, L1b, L2a, L2b); + } + } + } + + lemma CompCongruenceHelper(k: nat, L1a: Lang, L1b: Lang, L2a: Lang, L2b: Lang) + requires forall n : nat :: n <= k + 1 ==> Bisimilar#[n](L1a, L1b) + requires forall n : nat :: n <= k + 1 ==> Bisimilar#[n](L2a, L2b) + ensures Bisimilar#[k+1](Comp(L1a, L2a), Comp(L1b, L2b)) + { + var lhs := Comp(L1a, L2a); + var rhs := Comp(L1b, L2b); + + assert Bisimilar#[1](L1a, L1b); + assert Bisimilar#[1](L2a, L2b); + assert lhs.eps == rhs.eps; + + forall a ensures (Bisimilar#[k](lhs.delta(a), rhs.delta(a))) { + var x1 := Comp(L1a.delta(a), L2a); + var x2 := Comp(L1b.delta(a), L2b); + assert Bisimilar#[k](x1, x2) by { + if k != 0 { + BisimilarCuttingPrefixes2(k, a, L1a, L1b); + var k' :| k == k' + 1; + CompCongruenceHelper(k', L1a.delta(a), L1b.delta(a), L2a, L2b); + } + } + var y1 := Comp(if L1a.eps then One() else Zero(), L2a.delta(a)); + var y2 := Comp(if L1b.eps then One() else Zero(), L2b.delta(a)); + assert Bisimilar#[k](y1, y2) by { + assert L1a.eps == L1b.eps; + if k != 0 { + if L1a.eps { + BisimilarityIsReflexive(One()); + BisimilarCuttingPrefixes2(k, a, L2a, L2b); + var k' :| k == k' + 1; + CompCongruenceHelper(k', One(), One(), L2a.delta(a), L2b.delta(a)); + } else { + BisimilarityIsReflexive(Zero()); + BisimilarCuttingPrefixes2(k, a, L2a, L2b); + var k' :| k == k' + 1; + CompCongruenceHelper(k', Zero(), Zero(), L2a.delta(a), L2b.delta(a)); + } + } + } + PlusCongruenceAlternative(k, x1, x2, y1, y2); + } + } + + /* Congruence of Star */ + + lemma StarCongruence(L1: Lang, L2: Lang) + requires Bisimilar(L1, L2) + ensures Bisimilar(Star(L1), Star(L2)) + { + forall k ensures Bisimilar#[k](Star(L1), Star(L2)) { + if k != 0 { + var k' :| k' + 1 == k; + StarCongruenceHelper(k', L1, L2); + } + } + } + + lemma StarCongruenceHelper(k: nat, L1: Lang, L2: Lang) + requires forall n: nat :: n <= k + 1 ==> Bisimilar#[n](L1, L2) + ensures Bisimilar#[k+1](Star(L1), Star(L2)) + { + forall a ensures Bisimilar#[k](Star(L1).delta(a), Star(L2).delta(a)) { + if k != 0 { + BisimilarCuttingPrefixes2(k, a, L1, L2); + var k' :| k == k' + 1; + forall n: nat ensures n <= k' + 1 ==> Bisimilar#[n](Star(L1), Star(L2)) { + if 0 < n <= k' + 1 { + var n' :| n == n' + 1; + StarCongruenceHelper(n', L1,L2); + } + } + CompCongruenceHelper(k', L1.delta(a), L2.delta(a), Star(L1), Star(L2)); + } + } + } + +} + + + + + + + + + + + + + + + + + diff --git a/assets/src/semantics-of-regular-expressions/Semantics.dfy b/assets/src/semantics-of-regular-expressions/Semantics.dfy new file mode 100644 index 0000000..09923df --- /dev/null +++ b/assets/src/semantics-of-regular-expressions/Semantics.dfy @@ -0,0 +1,244 @@ +include "Expressions.dfy" +include "Languages.dfy" + +module Semantics { + + import opened Expressions + import opened Languages + + /* Definitions */ + + /* Denotational Semantics */ + + function Denotational(e: Exp): Lang { + match e + case Zero => Languages.Zero() + case One => Languages.One() + case Char(a) => Languages.Singleton(a) + case Plus(e1, e2) => Languages.Plus(Denotational(e1), Denotational(e2)) + case Comp(e1, e2) => Languages.Comp(Denotational(e1), Denotational(e2)) + case Star(e1) => Languages.Star(Denotational(e1)) + } + + /* Operational Semantics */ + + function Operational(e: Exp): Lang { + Alpha(Eps(e), (a: A) => Operational(Delta(e)(a))) + } + + /* (Co)algebra homomorphisms f: Exp -> Lang */ + + ghost predicate IsCoalgebraHomomorphism(f: Exp -> Lang) { + && (forall e :: f(e).eps == Eps(e)) + && (forall e, a :: Bisimilar(f(e).delta(a), f(Delta(e)(a)))) + } + + ghost predicate IsAlgebraHomomorphism(f: Exp -> Lang) { + forall e :: IsAlgebraHomomorphismPointwise(f, e) + } + + ghost predicate IsAlgebraHomomorphismPointwise(f: Exp -> Lang, e: Exp) { + Bisimilar( + f(e), + match e + case Zero => Languages.Zero() + case One => Languages.One() + case Char(a) => Languages.Singleton(a) + case Plus(e1, e2) => Languages.Plus(f(e1), f(e2)) + case Comp(e1, e2) => Languages.Comp(f(e1), f(e2)) + case Star(e1) => Languages.Star(f(e1)) + ) + } + + /* Lemmas */ + + /* Any two coalgebra homomorphisms f,g: Exp -> Lang are equal up to bisimulation */ + + lemma UniqueCoalgebraHomomorphism(f: Exp -> Lang, g: Exp -> Lang, e: Exp) + requires IsCoalgebraHomomorphism(f) + requires IsCoalgebraHomomorphism(g) + ensures Bisimilar(f(e), g(e)) + { + BisimilarityIsReflexive(f(e)); + BisimilarityIsReflexive(g(e)); + UniqueCoalgebraHomomorphismHelper(f, g, f(e), g(e)); + } + + lemma UniqueCoalgebraHomomorphismHelper(f: Exp -> Lang, g: Exp -> Lang, L1: Lang, L2: Lang) + requires IsCoalgebraHomomorphism(f) + requires IsCoalgebraHomomorphism(g) + requires exists e :: Bisimilar(L1, f(e)) && Bisimilar(L2, g(e)) + ensures Bisimilar(L1, L2) + { + forall k ensures Bisimilar#[k](L1, L2) { + if k != 0 { + UniqueCoalgebraHomomorphismHelperPointwise(k, f, g, L1, L2); + } + } + } + + lemma UniqueCoalgebraHomomorphismHelperPointwise(k: nat, f: Exp -> Lang, g: Exp -> Lang, L1: Lang, L2: Lang) + requires IsCoalgebraHomomorphism(f) + requires IsCoalgebraHomomorphism(g) + requires exists e :: Bisimilar#[k](L1, f(e)) && Bisimilar#[k](L2, g(e)) + ensures Bisimilar#[k](L1, L2) + { + var e :| Bisimilar#[k](L1, f(e)) && Bisimilar#[k](L2, g(e)); + if k != 0 { + forall a ensures Bisimilar#[k-1](L1.delta(a), L2.delta(a)) { + BisimilarityIsTransitivePointwise(k-1, L1.delta(a), f(e).delta(a), f(Delta(e)(a))); + BisimilarityIsTransitivePointwise(k-1, L2.delta(a), g(e).delta(a), g(Delta(e)(a))); + UniqueCoalgebraHomomorphismHelperPointwise(k-1, f, g, L1.delta(a), L2.delta(a)); + } + } + } + + /* Denotational is a algebra homomorphism */ + + lemma DenotationalIsAlgebraHomomorphism() + ensures IsAlgebraHomomorphism(Denotational) + { + forall e ensures IsAlgebraHomomorphismPointwise(Denotational, e) { + BisimilarityIsReflexive(Denotational(e)); + } + } + + /* Denotational is a coalgebra homomorphism */ + + lemma DenotationalIsCoalgebraHomomorphism() + ensures IsCoalgebraHomomorphism(Denotational) + { + forall e ensures Denotational(e).eps == Eps(e) { + DenotationalIsCoalgebraHomomorphismHelper1(e); + } + forall e, a ensures Bisimilar(Denotational(e).delta(a), Denotational(Delta(e)(a))) { + DenotationalIsCoalgebraHomomorphismHelper2(e, a); + } + } + + lemma DenotationalIsCoalgebraHomomorphismHelper1(e: Exp) + ensures Denotational(e).eps == Eps(e) + { + match e + case Zero => + case One => + case Char(a) => + case Plus(e1, e2) => + DenotationalIsCoalgebraHomomorphismHelper1(e1); + DenotationalIsCoalgebraHomomorphismHelper1(e2); + case Comp(e1, e2) => + DenotationalIsCoalgebraHomomorphismHelper1(e1); + DenotationalIsCoalgebraHomomorphismHelper1(e2); + case Star(e1) => + DenotationalIsCoalgebraHomomorphismHelper1(e1); + } + + lemma DenotationalIsCoalgebraHomomorphismHelper2(e: Exp, a: A) + ensures Bisimilar(Denotational(e).delta(a), Denotational(Delta(e)(a))) + { + match e + case Zero => BisimilarityIsReflexive(Languages.Zero()); + case One => BisimilarityIsReflexive(Languages.One()); + case Char(b) => + if a == b { + BisimilarityIsReflexive(Languages.One()); + } else { + BisimilarityIsReflexive(Languages.Zero()); + } + case Plus(e1, e2) => + DenotationalIsCoalgebraHomomorphismHelper2(e1, a); + DenotationalIsCoalgebraHomomorphismHelper2(e2, a); + PlusCongruence(Denotational(e1).delta(a), Denotational(Delta(e1)(a)), Denotational(e2).delta(a), Denotational(Delta(e2)(a))); + case Comp(e1, e2) => + DenotationalIsCoalgebraHomomorphismHelper1(e1); + DenotationalIsCoalgebraHomomorphismHelper2(e1, a); + DenotationalIsCoalgebraHomomorphismHelper2(e2, a); + BisimilarityIsReflexive(Denotational(e2)); + BisimilarityIsReflexive(if Eps(e1) then Languages.One() else Languages.Zero()); + CompCongruence( + Denotational(e1).delta(a), + Denotational(Delta(e1)(a)), + Denotational(e2), + Denotational(e2) + ); + CompCongruence( + if Eps(e1) then Languages.One() else Languages.Zero(), + if Eps(e1) then Languages.One() else Languages.Zero(), + Denotational(e2).delta(a), + Denotational(Delta(e2)(a)) + ); + PlusCongruence( + Languages.Comp(Denotational(e1).delta(a), Denotational(e2)), + Languages.Comp(Denotational(Delta(e1)(a)), Denotational(e2)), + Languages.Comp(if Eps(e1) then Languages.One() else Languages.Zero(), Denotational(e2).delta(a)), + Languages.Comp(if Eps(e1) then Languages.One() else Languages.Zero(), Denotational(Delta(e2)(a))) + ); + case Star(e1) => + DenotationalIsCoalgebraHomomorphismHelper2(e1, a); + BisimilarityIsReflexive(Languages.Star(Denotational(e1))); + CompCongruence(Denotational(e1).delta(a), Denotational(Delta(e1)(a)), Languages.Star(Denotational(e1)), Languages.Star(Denotational(e1))); + } + + /* Operational is a algebra homomorphism */ + + lemma OperationalIsAlgebraHomomorphism() + ensures IsAlgebraHomomorphism(Operational) + { + forall e ensures IsAlgebraHomomorphismPointwise(Operational, e) { + OperationalAndDenotationalAreBisimilar(e); + assert IsAlgebraHomomorphismPointwise(Denotational, e) by { + DenotationalIsAlgebraHomomorphism(); + } + match e + case Zero => + BisimilarityIsTransitive(Operational(Zero), Denotational(Zero), Languages.Zero()); + case One => + BisimilarityIsTransitive(Operational(One), Denotational(One), Languages.One()); + case Char(a) => + BisimilarityIsTransitive(Operational(Char(a)), Denotational(Char(a)), Languages.Singleton(a)); + case Plus(e1, e2) => + BisimilarityIsTransitive(Operational(Plus(e1, e2)), Denotational(Plus(e1, e2)), Languages.Plus(Denotational(e1), Denotational(e2))); + OperationalAndDenotationalAreBisimilar(e1); + BisimilarityIsSymmetric(Denotational(e1), Operational(e1)); + OperationalAndDenotationalAreBisimilar(e2); + BisimilarityIsSymmetric(Denotational(e2), Operational(e2)); + PlusCongruence(Denotational(e1), Operational(e1), Denotational(e2), Operational(e2)); + BisimilarityIsTransitive(Operational(Plus(e1, e2)), Languages.Plus(Denotational(e1), Denotational(e2)), Languages.Plus(Operational(e1), Operational(e2))); + case Comp(e1, e2) => + BisimilarityIsTransitive(Operational(Comp(e1, e2)), Denotational(Comp(e1, e2)), Languages.Comp(Denotational(e1), Denotational(e2))); + OperationalAndDenotationalAreBisimilar(e1); + BisimilarityIsSymmetric(Denotational(e1), Operational(e1)); + OperationalAndDenotationalAreBisimilar(e2); + BisimilarityIsSymmetric(Denotational(e2), Operational(e2)); + CompCongruence(Denotational(e1), Operational(e1), Denotational(e2), Operational(e2)); + BisimilarityIsTransitive(Operational(Comp(e1, e2)), Languages.Comp(Denotational(e1), Denotational(e2)), Languages.Comp(Operational(e1), Operational(e2))); + case Star(e1) => + BisimilarityIsTransitive(Operational(Star(e1)), Denotational(Star(e1)), Languages.Star(Denotational(e1))); + OperationalAndDenotationalAreBisimilar(e1); + BisimilarityIsSymmetric(Denotational(e1), Operational(e1)); + StarCongruence(Denotational(e1), Operational(e1)); + BisimilarityIsTransitive(Operational(Star(e1)), Languages.Star(Denotational(e1)), Languages.Star(Operational(e1))); + } + } + + /* Operational is a coalgebra homomorphism */ + + lemma OperationalIsCoalgebraHomomorphism() + ensures IsCoalgebraHomomorphism(Operational) + { + forall e, a ensures Bisimilar(Operational(e).delta(a), Operational(Delta(e)(a))) { + BisimilarityIsReflexive(Operational(e).delta(a)); + } + } + + /* Operational and Denotational are equal, up to bisimulation */ + + lemma OperationalAndDenotationalAreBisimilar(e: Exp) + ensures Bisimilar(Operational(e), Denotational(e)) + { + OperationalIsCoalgebraHomomorphism(); + DenotationalIsCoalgebraHomomorphism(); + UniqueCoalgebraHomomorphism(Operational, Denotational, e); + } + +} \ No newline at end of file diff --git a/assets/src/semantics-of-regular-expressions/semantics-of-regular-expressions.zip b/assets/src/semantics-of-regular-expressions/semantics-of-regular-expressions.zip deleted file mode 100644 index c6a20e53b4bca65145957052e1d048f75590e062..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3654 zcmb8yX*3jG8vyXp*mr68GZ@)r%aS5w4cUevOV-h3$(FJ2V~cFr$*!5PR>D}5>>^|t zYol!0hm;yid3m>bKfE9Q&pr2?d%ryA-us+$f9HYf0m&}_s87drWRf}H-$4=t02lzg z02spE!NbSJ5h;21UZ9yN1AuDpbs%EjB?$5DbovI+0m#TnWB|Zv3IJfmir_gX&m2Bt z_h36h{nCWbr}jv;R$v0G)F73A2fWkyCkooHJJx>QMB}=I|j%m74^)nD|Gq= z^14&;2Dyx+bb*hqTNz?x-9gN9gCga#(S?jy`fq%CElrp%bTvnAzU@S;Bs=JnApU3H z9upUa6zrV2Zf@$~$_FIJV;wZXu`&+M)& zmdsVI*Es3(UJc0Fd(Ubge)a3rlclQI5C%5m%9g=nL8jH-?G_d@MP3%W?}}yK3s#hk z-T=S~GKzOREZ3y@GiogRA|S7iv3xP`;Z!AZY%4YhH(?`~X_ir`5)3u(++1f2f!2ud6(OSTm z2aWe)B`#D&uj=n_K<*Do-%^pOx#T!m5P=Pae0O zOvB6x4EpYw%~^FcJHj5%kQ<*xV@=f8Bj*TN4sa<2IMU;zUQg>$trmH>Glee)4_WsjncO)ow)YoSud zk8^0T3p6MDVLx^_NW;uiWFs?W?13oeWzg0eQ3~PSY2`Tn!ql0{)LU4Q7qY~4KI-=? ziAvkQ0a(S z_3e>1MX6Sm#U+-aN|=(RkZWrLZT5#jnO`ow)(HdwlRfS?)4_3DE5p~rTNyv|yl~gE zXdZW3`;tT`#kRkHS4SiG0{*O!`r7_-!Yh;Es!Tusm_QN}RF8_fKqQq!pa1~Qbb$WX zjS#n+|8?Ww900&{>V{T;mp1~5bn*1~Er<9C>bZllY zg$_R|O3zuBALBGXGlYsi(H*f7cjtNAMli%EIrLf=y*#f;w?5^tJ7FloNZUF&&_h~I zVFz{J$;Bf=yM+7V{W}^#5rtN*ugEj5O|kXiFQL@In;qgY&?zP!kpZ73b#@GijjY#1 zh$17c^XRYMlB+Q(77HGYFRYtW5>Bjm>R?jN`6?KcNr}G~?NM~s`kE?hx2!;3UQG?E zT}9=fCb}nH|E?K@Gn7`wOHh6+;@*?BMsbNW>*cSgyZ6aqY)H1@NeS_ETe4MPIaktM zll^b|W|Q!7x6B>VK&+Dj*Gc!%KP!R!!M-PP>h9Tl|E>g`|8K#IIQ}KLzJrI8uY(if zcOBSz|F7Uf79Y<94?7jSBjToO8qQ>Xc{(Cn*)0MEx?gmggz|RQuF(%n`_QX%+`dE7 zI4_!-wxQ&sX0J0r=xf~}1_=1iEM3IZ@VX&+!B%eudnn#K{IPr8Mp#@lJrT6~3kb1B zq@V>>9!T}Uu%r+Zs29ccJU^vk1>@}*8)ZeW79CiJhT4lUQk}e4>QWm? z>GscO*)>|M*0p*M-n|yVG@i@+Jp`gDgf>wfWW1blo znDN7<493J4FVCyz*{8!y#!90@4I$|ul`c=+)UC9PkyRU+Lqbs~0&P9(Y?96|3{uWv zOXF$(RXgmRhRu;Np0H1e4A4Wa1lu}V_l1wP(G>FiQVI?flJs1#QAY>LA|mBITFo%8*!qsgV(_)geiEbC z9kKvjcV$!*9njYRqO?!4Z8YOjaP-%H=9dW$r7DG(-lkiLH&L=NZ7=Hxuu#jJrXR~0 z4`+HCz;jgI4hG*F8k721C$u(4?R6nFOo`MULXdnGJIK@^;B5L%bGmcog36@v1kCY+ zxWVzJB5sDRky)IeHP_p|(pe%IQbuzXnLMC~CTM6H;zEO1!%UtQ4eN}K^bwM?0&6*K ze^Op*HS5mbvDaDUnKH^tzG4hvM8q2$!;*tDovaddhi-FJJeIvUGUXJ@W^ZVt@!s&p z?bWn}NZ$wMxvTw|ilM=*&!uMS-8@&q{huv02TLH$Ep{v#4l5X1KkV@Y19hY;6d2)R zZ^ybDS&YbEWgardn%R}f#1T56&Umg>)r|$?!z`h3__QOJR`YrsiwA&5rlCWb@s4r- zBzz9>k#bim$*nA-ReGs;G%@zO&c=SFcDE4odDPOS02~sep0BpgqlfW(n@7xPgH?TR zY{3jX|9Azj97JH1akSZr&fKvwv%nmMyg5H~qQ#4U-u~UBZPRvRALskntg9YMuCo*Q zBWby&EpjdMWMCiIV8A5Td=!NZeiA%ag`JIF7@M8PjZ)sduizAmwhI50FZjhRqeJdB zW7boRcl@a;AZCLO)7*(7=y|qQ9HzDYk@iGV zIj>Feix4h`_a@NoXXW{%>0MzWRsS0bvUTDE3Vr9!Zs6qy?7ag&S(P0=Defk)*1LTh)aRZ=$!gxB|NoZBvIwYD?i zOmQsmyVr(J;jAhi9cVVj0sb97#@9OY>oCEqN~k5JN`Co+m%4&X6;7HQNB3B6s$ILiNbb zT>$=WkNyPktUdZqpiyV5ztt@Lnfgr7KY#QyLH`qI)HO=L-}Us*QS7Y2LJ$`4%SnFxEaoN{{UWCme2qI From be9faf2e78767844e62aa0a929f1ac1c4b260ac2 Mon Sep 17 00:00:00 2001 From: stefan-aws Date: Thu, 14 Dec 2023 12:58:02 +0000 Subject: [PATCH 06/62] format, naming --- .../Languages.dfy | 29 ++++--------------- .../Semantics.dfy | 16 +++++----- 2 files changed, 14 insertions(+), 31 deletions(-) diff --git a/assets/src/semantics-of-regular-expressions/Languages.dfy b/assets/src/semantics-of-regular-expressions/Languages.dfy index 9ee30f0..9eca84a 100644 --- a/assets/src/semantics-of-regular-expressions/Languages.dfy +++ b/assets/src/semantics-of-regular-expressions/Languages.dfy @@ -86,7 +86,7 @@ module Languages { } } - lemma BisimilarCuttingPrefixes2(k: nat, a: A, L1a: Lang, L1b: Lang) + lemma BisimilarCuttingPrefixesPointwise(k: nat, a: A, L1a: Lang, L1b: Lang) requires k != 0 requires forall n: nat :: n <= k + 1 ==> Bisimilar#[n](L1a, L1b) ensures forall n: nat :: n <= k ==> Bisimilar#[n](L1a.delta(a), L1b.delta(a)) @@ -144,7 +144,7 @@ module Languages { var x2 := Comp(L1b.delta(a), L2b); assert Bisimilar#[k](x1, x2) by { if k != 0 { - BisimilarCuttingPrefixes2(k, a, L1a, L1b); + BisimilarCuttingPrefixesPointwise(k, a, L1a, L1b); var k' :| k == k' + 1; CompCongruenceHelper(k', L1a.delta(a), L1b.delta(a), L2a, L2b); } @@ -156,12 +156,12 @@ module Languages { if k != 0 { if L1a.eps { BisimilarityIsReflexive(One()); - BisimilarCuttingPrefixes2(k, a, L2a, L2b); + BisimilarCuttingPrefixesPointwise(k, a, L2a, L2b); var k' :| k == k' + 1; CompCongruenceHelper(k', One(), One(), L2a.delta(a), L2b.delta(a)); } else { BisimilarityIsReflexive(Zero()); - BisimilarCuttingPrefixes2(k, a, L2a, L2b); + BisimilarCuttingPrefixesPointwise(k, a, L2a, L2b); var k' :| k == k' + 1; CompCongruenceHelper(k', Zero(), Zero(), L2a.delta(a), L2b.delta(a)); } @@ -191,7 +191,7 @@ module Languages { { forall a ensures Bisimilar#[k](Star(L1).delta(a), Star(L2).delta(a)) { if k != 0 { - BisimilarCuttingPrefixes2(k, a, L1, L2); + BisimilarCuttingPrefixesPointwise(k, a, L1, L2); var k' :| k == k' + 1; forall n: nat ensures n <= k' + 1 ==> Bisimilar#[n](Star(L1), Star(L2)) { if 0 < n <= k' + 1 { @@ -204,21 +204,4 @@ module Languages { } } -} - - - - - - - - - - - - - - - - - +} \ No newline at end of file diff --git a/assets/src/semantics-of-regular-expressions/Semantics.dfy b/assets/src/semantics-of-regular-expressions/Semantics.dfy index 09923df..fbe03ce 100644 --- a/assets/src/semantics-of-regular-expressions/Semantics.dfy +++ b/assets/src/semantics-of-regular-expressions/Semantics.dfy @@ -116,7 +116,7 @@ module Semantics { } } - lemma DenotationalIsCoalgebraHomomorphismHelper1(e: Exp) + lemma DenotationalIsCoalgebraHomomorphismHelper1(e: Exp) ensures Denotational(e).eps == Eps(e) { match e @@ -179,7 +179,7 @@ module Semantics { CompCongruence(Denotational(e1).delta(a), Denotational(Delta(e1)(a)), Languages.Star(Denotational(e1)), Languages.Star(Denotational(e1))); } - /* Operational is a algebra homomorphism */ + /* Operational is an algebra homomorphism */ lemma OperationalIsAlgebraHomomorphism() ensures IsAlgebraHomomorphism(Operational) @@ -192,11 +192,11 @@ module Semantics { match e case Zero => BisimilarityIsTransitive(Operational(Zero), Denotational(Zero), Languages.Zero()); - case One => + case One => BisimilarityIsTransitive(Operational(One), Denotational(One), Languages.One()); - case Char(a) => + case Char(a) => BisimilarityIsTransitive(Operational(Char(a)), Denotational(Char(a)), Languages.Singleton(a)); - case Plus(e1, e2) => + case Plus(e1, e2) => BisimilarityIsTransitive(Operational(Plus(e1, e2)), Denotational(Plus(e1, e2)), Languages.Plus(Denotational(e1), Denotational(e2))); OperationalAndDenotationalAreBisimilar(e1); BisimilarityIsSymmetric(Denotational(e1), Operational(e1)); @@ -204,7 +204,7 @@ module Semantics { BisimilarityIsSymmetric(Denotational(e2), Operational(e2)); PlusCongruence(Denotational(e1), Operational(e1), Denotational(e2), Operational(e2)); BisimilarityIsTransitive(Operational(Plus(e1, e2)), Languages.Plus(Denotational(e1), Denotational(e2)), Languages.Plus(Operational(e1), Operational(e2))); - case Comp(e1, e2) => + case Comp(e1, e2) => BisimilarityIsTransitive(Operational(Comp(e1, e2)), Denotational(Comp(e1, e2)), Languages.Comp(Denotational(e1), Denotational(e2))); OperationalAndDenotationalAreBisimilar(e1); BisimilarityIsSymmetric(Denotational(e1), Operational(e1)); @@ -212,7 +212,7 @@ module Semantics { BisimilarityIsSymmetric(Denotational(e2), Operational(e2)); CompCongruence(Denotational(e1), Operational(e1), Denotational(e2), Operational(e2)); BisimilarityIsTransitive(Operational(Comp(e1, e2)), Languages.Comp(Denotational(e1), Denotational(e2)), Languages.Comp(Operational(e1), Operational(e2))); - case Star(e1) => + case Star(e1) => BisimilarityIsTransitive(Operational(Star(e1)), Denotational(Star(e1)), Languages.Star(Denotational(e1))); OperationalAndDenotationalAreBisimilar(e1); BisimilarityIsSymmetric(Denotational(e1), Operational(e1)); @@ -230,7 +230,7 @@ module Semantics { BisimilarityIsReflexive(Operational(e).delta(a)); } } - + /* Operational and Denotational are equal, up to bisimulation */ lemma OperationalAndDenotationalAreBisimilar(e: Exp) From 76204c549116a7540ab92e45ff9d00590a8f3800 Mon Sep 17 00:00:00 2001 From: stefan-aws Date: Fri, 15 Dec 2023 14:52:36 +0000 Subject: [PATCH 07/62] name, silva, i/we, [s] --- .../2024-01-10-semantics-of-regular-expressions.markdown | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/_posts/2024-01-10-semantics-of-regular-expressions.markdown b/_posts/2024-01-10-semantics-of-regular-expressions.markdown index 88444a9..a26fecd 100644 --- a/_posts/2024-01-10-semantics-of-regular-expressions.markdown +++ b/_posts/2024-01-10-semantics-of-regular-expressions.markdown @@ -2,7 +2,7 @@ layout: post title: "Well-Behaved (Co)algebraic Semantics of Regular Expressions in Dafny" date: 2024-01-10 18:00:00 +0100 -author: Stefan Zetzsche, Wojciech Rozowski +author: Stefan Zetzsche, Wojciech Różowski --- ## Introduction @@ -37,7 +37,7 @@ To some, this choice might seem odd at first sight. If you are familiar with the If you think of formal languages as the set of all sets of finite sequences, you will soon realise that languages admit quite a bit of algebraic structure. For example, there exist two languages of distinct importance (can you already guess which ones?), and one can obtain a new language by taking e.g. the union of two languages. In fact, if you think about it for a bit longer, you’ll realise that formal languages admit exactly the same [type of algebraic structure](https://en.wikipedia.org/wiki/F-algebra) as the one you’ve encountered when we defined regular expressions! -First, there exists the empty language `Zero()` that contains no words at all. Under above view, we find `Zero().eps == false` and `Zero().delta(a) == Zero`, since the empty set does not contain the empty sequence, and the derivative `iset s | [a] + [s] in iset{}` with respect to any `a: A` yields again the empty set, respectively. We thus define: +First, there exists the empty language `Zero()` that contains no words at all. Under above view, we find `Zero().eps == false` and `Zero().delta(a) == Zero`, since the empty set does not contain the empty sequence, and the derivative `iset s | [a] + s in iset{}` with respect to any `a: A` yields again the empty set, respectively. We thus define: ``` ghost function Zero(): Lang { @@ -150,7 +150,7 @@ lemma BisimilarityIsReflexive#[k: nat](L: Lang) } ``` -If you are interested in the full details, I recommend taking a look at [this note on coinduction, predicates, and ordinals](https://leino.science/papers/krml285.html). +If you are interested in the full details, we recommend taking a look at [this note on coinduction, predicates, and ordinals](https://leino.science/papers/krml285.html). ### Denotational Semantics as Algebra Homomorphism @@ -383,4 +383,4 @@ lemma StarCongruence(L1: Lang, L2: Lang) ## Conclusion -We have used Dafny’s built-in inductive and coinductive reasoning capabilities to define two language semantics for regular expressions: denotational and operational semantics. Through a number of dualities — construction and deconstruction, algebras and coalgebras, and congruence and bisimilarity — we have proven the semantics to be two sides of the same coin. The blogpost is inspired by research in the field of [Coalgebra](https://en.wikipedia.org/wiki/F-coalgebra), which was pioneered by [Rutten](https://pdf.sciencedirectassets.com/271538/1-s2.0-S0304397500X01466/1-s2.0-S0304397500000566/main.pdf), [Gumm](https://www.researchgate.net/publication/2614339_Elements_Of_The_General_Theory_Of_Coalgebras), and others. The concept of well-behaved semantics goes back to [Turi and Plotkin](https://homepages.inf.ed.ac.uk/gdp/publications/Math_Op_Sem.pdf) and was adapted by [Jacobs](https://link.springer.com/chapter/10.1007/11780274_20) to the case of regular expressions. We heavily used automata theoretic constructions from the 1960s, originally investigated by [Brzozowski](https://dl.acm.org/doi/10.1145/321239.321249). Due to space constraints, we had to exclude the details of most of the proofs. To dive deep, please take a look at the full Dafny source code, which is available [here](../../../../assets/src/semantics-of-regular-expressions/Languages.dfy), [here](../../../../assets/src/semantics-of-regular-expressions/Semantics.dfy), and [here](../../../../assets/src/semantics-of-regular-expressions/Expressions.dfy). \ No newline at end of file +We have used Dafny’s built-in inductive and coinductive reasoning capabilities to define two language semantics for regular expressions: denotational and operational semantics. Through a number of dualities — construction and deconstruction, algebras and coalgebras, and congruence and bisimilarity — we have proven the semantics to be two sides of the same coin. The blogpost is inspired by research in the field of [Coalgebra](https://en.wikipedia.org/wiki/F-coalgebra), which was pioneered by [Rutten](https://pdf.sciencedirectassets.com/271538/1-s2.0-S0304397500X01466/1-s2.0-S0304397500000566/main.pdf), [Gumm](https://www.researchgate.net/publication/2614339_Elements_Of_The_General_Theory_Of_Coalgebras), and others. The concept of well-behaved semantics goes back to [Turi and Plotkin](https://homepages.inf.ed.ac.uk/gdp/publications/Math_Op_Sem.pdf) and was adapted by [Jacobs](https://link.springer.com/chapter/10.1007/11780274_20) to the case of regular expressions. We heavily used automata theoretic constructions from the 1960s, originally investigated by [Brzozowski](https://dl.acm.org/doi/10.1145/321239.321249) (a more modern presentation can be found e.g. [here](https://alexandrasilva.org/files/thesis.pdf)). Due to space constraints, we had to exclude the details of most of the proofs. To dive deep, please take a look at the full Dafny source code, which is available [here](../../../../assets/src/semantics-of-regular-expressions/Languages.dfy), [here](../../../../assets/src/semantics-of-regular-expressions/Semantics.dfy), and [here](../../../../assets/src/semantics-of-regular-expressions/Expressions.dfy). \ No newline at end of file From 0af556d71a975e49722502f0760ab188506acc9a Mon Sep 17 00:00:00 2001 From: stefan-aws Date: Fri, 15 Dec 2023 14:54:19 +0000 Subject: [PATCH 08/62] an algebra --- assets/src/semantics-of-regular-expressions/Semantics.dfy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/src/semantics-of-regular-expressions/Semantics.dfy b/assets/src/semantics-of-regular-expressions/Semantics.dfy index fbe03ce..87d8694 100644 --- a/assets/src/semantics-of-regular-expressions/Semantics.dfy +++ b/assets/src/semantics-of-regular-expressions/Semantics.dfy @@ -93,7 +93,7 @@ module Semantics { } } - /* Denotational is a algebra homomorphism */ + /* Denotational is an algebra homomorphism */ lemma DenotationalIsAlgebraHomomorphism() ensures IsAlgebraHomomorphism(Denotational) From de3b1baed83f3157eb82eba51929e9b97e3d8396 Mon Sep 17 00:00:00 2001 From: Stefan Zetzsche <120379523+stefan-aws@users.noreply.github.com> Date: Tue, 2 Jan 2024 15:15:35 +0000 Subject: [PATCH 09/62] Update _posts/2024-01-10-semantics-of-regular-expressions.markdown Co-authored-by: Aaron Tomb --- _posts/2024-01-10-semantics-of-regular-expressions.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_posts/2024-01-10-semantics-of-regular-expressions.markdown b/_posts/2024-01-10-semantics-of-regular-expressions.markdown index a26fecd..9e5dd25 100644 --- a/_posts/2024-01-10-semantics-of-regular-expressions.markdown +++ b/_posts/2024-01-10-semantics-of-regular-expressions.markdown @@ -21,7 +21,7 @@ We define the set of regular expressions parametric in an alphabet `A` as an in datatype Exp = Zero | One | Char(A) | Plus(Exp, Exp) | Comp(Exp, Exp) | Star(Exp) ``` -The definition captures that a regular expression is either a primitive character `Char(a)`, a non-deterministic choice between two regular expressions `Plus(e1, e2)`, a sequential iteration of two regular expressions `Comp(e1, e2)`, a finite number of self-iterations `Star(e)`, or one of the constants `Zero` (the unit of `Plus`) and `One` (the unit of `Comp`). On a more high-level, above defines `Exp` as the *smallest* algebraic structures that is equipped with two constants, contains all elements of type `A`, and is closed under two binary and one unary operation. +The definition captures that a regular expression is either a primitive character `Char(a)`, a non-deterministic choice between two regular expressions `Plus(e1, e2)`, a sequential composition of two regular expressions `Comp(e1, e2)`, a finite number of self-iterations `Star(e)`, or one of the constants `Zero` (the unit of `Plus`) and `One` (the unit of `Comp`). At a higher level, the above defines `Exp` as the *smallest* algebraic structure that is equipped with two constants, contains all elements of type `A`, and is closed under two binary operations and one unary operation. ### Formal Languages as Codatatype From b8a54ef0a831cba024b30bcc88af1f82719f1b37 Mon Sep 17 00:00:00 2001 From: Stefan Zetzsche <120379523+stefan-aws@users.noreply.github.com> Date: Tue, 2 Jan 2024 15:17:34 +0000 Subject: [PATCH 10/62] Update _posts/2024-01-10-semantics-of-regular-expressions.markdown Co-authored-by: Aaron Tomb --- _posts/2024-01-10-semantics-of-regular-expressions.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_posts/2024-01-10-semantics-of-regular-expressions.markdown b/_posts/2024-01-10-semantics-of-regular-expressions.markdown index 9e5dd25..3898cb8 100644 --- a/_posts/2024-01-10-semantics-of-regular-expressions.markdown +++ b/_posts/2024-01-10-semantics-of-regular-expressions.markdown @@ -31,7 +31,7 @@ We define the set of formal languages parametric in an alphabet `A` as an coindu codatatype Lang = Alpha(eps: bool, delta: A -> Lang) ``` -To some, this choice might seem odd at first sight. If you are familiar with the topic, you likely have expected the set of formal languages to be defined more concretely as the set of all sets of finite sequences (sometimes called *words*), `iset>`. Rest assured, we agree — up to an appropriate notion of equality! Where as you characterise languages intrinsically, we treat them extrinsically, in terms of their universal property: [it is well known](https://ir.cwi.nl/pub/28550/rutten.pdf) that `iset>` forms the *greatest* [coalgebraic](https://en.wikipedia.org/wiki/F-coalgebra) structure (think of a deterministic automaton without initial state) `S` that is equipped with functions `eps: S -> bool` and `delta: S → (A → S)`. Indeed, for any set `U` of finite sequences, we can verify whether `U` contains the empty sequence, `U.eps == ([] in U)`, and for any `a: A`, we can transition to the set `U.delta(a) == (iset s | [a] + s in U)`. Here, we choose the more abstract perspective on formal languages at it hides irrelevant specifics and thus allows us to write more elegant proofs. +To some, this choice might seem odd at first sight. If you are familiar with the topic, you likely have expected the set of formal languages to be defined more concretely as the set of all sets of finite sequences (sometimes called *words*), `iset>`. Rest assured, we agree — up to an appropriate notion of equality! Whereas you characterise languages intrinsically, we treat them extrinsically, in terms of their universal property: [it is well known](https://ir.cwi.nl/pub/28550/rutten.pdf) that `iset>` forms the *greatest* [coalgebraic](https://en.wikipedia.org/wiki/F-coalgebra) structure (think of a deterministic automaton without initial state) `S` that is equipped with functions `eps: S -> bool` and `delta: S → (A → S)`. Indeed, for any set `U` of finite sequences, we can verify whether `U` contains the empty sequence, `U.eps == ([] in U)`, and for any `a: A`, we can transition to the set `U.delta(a) == (iset s | [a] + s in U)`. Here, we choose the more abstract perspective on formal languages at it hides irrelevant specifics and thus allows us to write more elegant proofs. ### An Algebra of Formal Languages From 0e227953dc69998c765aae4ec740e4bc24ccc04a Mon Sep 17 00:00:00 2001 From: Stefan Zetzsche <120379523+stefan-aws@users.noreply.github.com> Date: Tue, 2 Jan 2024 15:17:51 +0000 Subject: [PATCH 11/62] Update _posts/2024-01-10-semantics-of-regular-expressions.markdown Co-authored-by: Aaron Tomb --- _posts/2024-01-10-semantics-of-regular-expressions.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_posts/2024-01-10-semantics-of-regular-expressions.markdown b/_posts/2024-01-10-semantics-of-regular-expressions.markdown index 3898cb8..27d1603 100644 --- a/_posts/2024-01-10-semantics-of-regular-expressions.markdown +++ b/_posts/2024-01-10-semantics-of-regular-expressions.markdown @@ -262,7 +262,7 @@ Next, we show that the denotational and operational semantics of regular express ### Denotational Semantics as Coalgebra Homomorphism -In this section, we establish that `Denotational` does not only commute with the algebraic structures of regular expressions and formal languages, but also with their coalgebraic structures: +In this section, we establish that `Denotational` not only commutes with the algebraic structures of regular expressions and formal languages, but also with their coalgebraic structures: ``` lemma DenotationalIsCoalgebraHomomorphism() From 7ec296a96ca90a7ab2035026a52792c2fc7374f6 Mon Sep 17 00:00:00 2001 From: stefan-aws Date: Tue, 2 Jan 2024 15:19:12 +0000 Subject: [PATCH 12/62] at -> as --- _posts/2024-01-10-semantics-of-regular-expressions.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_posts/2024-01-10-semantics-of-regular-expressions.markdown b/_posts/2024-01-10-semantics-of-regular-expressions.markdown index 27d1603..258f19c 100644 --- a/_posts/2024-01-10-semantics-of-regular-expressions.markdown +++ b/_posts/2024-01-10-semantics-of-regular-expressions.markdown @@ -31,7 +31,7 @@ We define the set of formal languages parametric in an alphabet `A` as an coindu codatatype Lang = Alpha(eps: bool, delta: A -> Lang) ``` -To some, this choice might seem odd at first sight. If you are familiar with the topic, you likely have expected the set of formal languages to be defined more concretely as the set of all sets of finite sequences (sometimes called *words*), `iset>`. Rest assured, we agree — up to an appropriate notion of equality! Whereas you characterise languages intrinsically, we treat them extrinsically, in terms of their universal property: [it is well known](https://ir.cwi.nl/pub/28550/rutten.pdf) that `iset>` forms the *greatest* [coalgebraic](https://en.wikipedia.org/wiki/F-coalgebra) structure (think of a deterministic automaton without initial state) `S` that is equipped with functions `eps: S -> bool` and `delta: S → (A → S)`. Indeed, for any set `U` of finite sequences, we can verify whether `U` contains the empty sequence, `U.eps == ([] in U)`, and for any `a: A`, we can transition to the set `U.delta(a) == (iset s | [a] + s in U)`. Here, we choose the more abstract perspective on formal languages at it hides irrelevant specifics and thus allows us to write more elegant proofs. +To some, this choice might seem odd at first sight. If you are familiar with the topic, you likely have expected the set of formal languages to be defined more concretely as the set of all sets of finite sequences (sometimes called *words*), `iset>`. Rest assured, we agree — up to an appropriate notion of equality! Whereas you characterise languages intrinsically, we treat them extrinsically, in terms of their universal property: [it is well known](https://ir.cwi.nl/pub/28550/rutten.pdf) that `iset>` forms the *greatest* [coalgebraic](https://en.wikipedia.org/wiki/F-coalgebra) structure (think of a deterministic automaton without initial state) `S` that is equipped with functions `eps: S -> bool` and `delta: S → (A → S)`. Indeed, for any set `U` of finite sequences, we can verify whether `U` contains the empty sequence, `U.eps == ([] in U)`, and for any `a: A`, we can transition to the set `U.delta(a) == (iset s | [a] + s in U)`. Here, we choose the more abstract perspective on formal languages as it hides irrelevant specifics and thus allows us to write more elegant proofs. ### An Algebra of Formal Languages From 05bfc5a1d6281bcaa6bf39f02f788862e2672c09 Mon Sep 17 00:00:00 2001 From: stefan-aws Date: Tue, 2 Jan 2024 15:20:53 +0000 Subject: [PATCH 13/62] Zero -> Zero() --- _posts/2024-01-10-semantics-of-regular-expressions.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_posts/2024-01-10-semantics-of-regular-expressions.markdown b/_posts/2024-01-10-semantics-of-regular-expressions.markdown index 258f19c..5c7dfdc 100644 --- a/_posts/2024-01-10-semantics-of-regular-expressions.markdown +++ b/_posts/2024-01-10-semantics-of-regular-expressions.markdown @@ -37,7 +37,7 @@ To some, this choice might seem odd at first sight. If you are familiar with the If you think of formal languages as the set of all sets of finite sequences, you will soon realise that languages admit quite a bit of algebraic structure. For example, there exist two languages of distinct importance (can you already guess which ones?), and one can obtain a new language by taking e.g. the union of two languages. In fact, if you think about it for a bit longer, you’ll realise that formal languages admit exactly the same [type of algebraic structure](https://en.wikipedia.org/wiki/F-algebra) as the one you’ve encountered when we defined regular expressions! -First, there exists the empty language `Zero()` that contains no words at all. Under above view, we find `Zero().eps == false` and `Zero().delta(a) == Zero`, since the empty set does not contain the empty sequence, and the derivative `iset s | [a] + s in iset{}` with respect to any `a: A` yields again the empty set, respectively. We thus define: +First, there exists the empty language `Zero()` that contains no words at all. Under above view, we find `Zero().eps == false` and `Zero().delta(a) == Zero()`, since the empty set does not contain the empty sequence, and the derivative `iset s | [a] + s in iset{}` with respect to any `a: A` yields again the empty set, respectively. We thus define: ``` ghost function Zero(): Lang { From 213cde0c23be015f3658b191d018690dcd987920 Mon Sep 17 00:00:00 2001 From: stefan-aws Date: Tue, 2 Jan 2024 15:34:09 +0000 Subject: [PATCH 14/62] authors --- _posts/2024-01-10-semantics-of-regular-expressions.markdown | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/_posts/2024-01-10-semantics-of-regular-expressions.markdown b/_posts/2024-01-10-semantics-of-regular-expressions.markdown index 5c7dfdc..ffd1cca 100644 --- a/_posts/2024-01-10-semantics-of-regular-expressions.markdown +++ b/_posts/2024-01-10-semantics-of-regular-expressions.markdown @@ -1,8 +1,8 @@ --- layout: post title: "Well-Behaved (Co)algebraic Semantics of Regular Expressions in Dafny" -date: 2024-01-10 18:00:00 +0100 -author: Stefan Zetzsche, Wojciech Różowski +date: 2024-01-10 18:00:00 +0100 +author: Stefan Zetzsche and Wojciech Różowski --- ## Introduction From 3a46cc7b3931b9601bc8eceac66036753ee1c389 Mon Sep 17 00:00:00 2001 From: stefan-aws Date: Tue, 2 Jan 2024 15:40:11 +0000 Subject: [PATCH 15/62] expression def avoid scrollbar --- _posts/2024-01-10-semantics-of-regular-expressions.markdown | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/_posts/2024-01-10-semantics-of-regular-expressions.markdown b/_posts/2024-01-10-semantics-of-regular-expressions.markdown index ffd1cca..3fab240 100644 --- a/_posts/2024-01-10-semantics-of-regular-expressions.markdown +++ b/_posts/2024-01-10-semantics-of-regular-expressions.markdown @@ -18,7 +18,8 @@ In this section, we define regular expressions and formal languages, introduce t We define the set of regular expressions parametric in an alphabet `A` as an inductive [`datatype`](https://dafny.org/latest/DafnyRef/DafnyRef#sec-inductive-datatypes): ``` -datatype Exp = Zero | One | Char(A) | Plus(Exp, Exp) | Comp(Exp, Exp) | Star(Exp) +datatype Exp = Zero | One | Char(A) | Plus(Exp, Exp) | + Comp(Exp, Exp) | Star(Exp) ``` The definition captures that a regular expression is either a primitive character `Char(a)`, a non-deterministic choice between two regular expressions `Plus(e1, e2)`, a sequential composition of two regular expressions `Comp(e1, e2)`, a finite number of self-iterations `Star(e)`, or one of the constants `Zero` (the unit of `Plus`) and `One` (the unit of `Comp`). At a higher level, the above defines `Exp` as the *smallest* algebraic structure that is equipped with two constants, contains all elements of type `A`, and is closed under two binary operations and one unary operation. From 4f12b603eb1875383a632eeca9a6a59f05e1c62d Mon Sep 17 00:00:00 2001 From: stefan-aws Date: Tue, 2 Jan 2024 16:25:35 +0000 Subject: [PATCH 16/62] space constraints --- _posts/2024-01-10-semantics-of-regular-expressions.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_posts/2024-01-10-semantics-of-regular-expressions.markdown b/_posts/2024-01-10-semantics-of-regular-expressions.markdown index 3fab240..b6cb672 100644 --- a/_posts/2024-01-10-semantics-of-regular-expressions.markdown +++ b/_posts/2024-01-10-semantics-of-regular-expressions.markdown @@ -384,4 +384,4 @@ lemma StarCongruence(L1: Lang, L2: Lang) ## Conclusion -We have used Dafny’s built-in inductive and coinductive reasoning capabilities to define two language semantics for regular expressions: denotational and operational semantics. Through a number of dualities — construction and deconstruction, algebras and coalgebras, and congruence and bisimilarity — we have proven the semantics to be two sides of the same coin. The blogpost is inspired by research in the field of [Coalgebra](https://en.wikipedia.org/wiki/F-coalgebra), which was pioneered by [Rutten](https://pdf.sciencedirectassets.com/271538/1-s2.0-S0304397500X01466/1-s2.0-S0304397500000566/main.pdf), [Gumm](https://www.researchgate.net/publication/2614339_Elements_Of_The_General_Theory_Of_Coalgebras), and others. The concept of well-behaved semantics goes back to [Turi and Plotkin](https://homepages.inf.ed.ac.uk/gdp/publications/Math_Op_Sem.pdf) and was adapted by [Jacobs](https://link.springer.com/chapter/10.1007/11780274_20) to the case of regular expressions. We heavily used automata theoretic constructions from the 1960s, originally investigated by [Brzozowski](https://dl.acm.org/doi/10.1145/321239.321249) (a more modern presentation can be found e.g. [here](https://alexandrasilva.org/files/thesis.pdf)). Due to space constraints, we had to exclude the details of most of the proofs. To dive deep, please take a look at the full Dafny source code, which is available [here](../../../../assets/src/semantics-of-regular-expressions/Languages.dfy), [here](../../../../assets/src/semantics-of-regular-expressions/Semantics.dfy), and [here](../../../../assets/src/semantics-of-regular-expressions/Expressions.dfy). \ No newline at end of file +We have used Dafny’s built-in inductive and coinductive reasoning capabilities to define two language semantics for regular expressions: denotational and operational semantics. Through a number of dualities — construction and deconstruction, algebras and coalgebras, and congruence and bisimilarity — we have proven the semantics to be two sides of the same coin. The blogpost is inspired by research in the field of [Coalgebra](https://en.wikipedia.org/wiki/F-coalgebra), which was pioneered by [Rutten](https://pdf.sciencedirectassets.com/271538/1-s2.0-S0304397500X01466/1-s2.0-S0304397500000566/main.pdf), [Gumm](https://www.researchgate.net/publication/2614339_Elements_Of_The_General_Theory_Of_Coalgebras), and others. The concept of well-behaved semantics goes back to [Turi and Plotkin](https://homepages.inf.ed.ac.uk/gdp/publications/Math_Op_Sem.pdf) and was adapted by [Jacobs](https://link.springer.com/chapter/10.1007/11780274_20) to the case of regular expressions. We heavily used automata theoretic constructions from the 1960s, originally investigated by [Brzozowski](https://dl.acm.org/doi/10.1145/321239.321249) (a more modern presentation can be found e.g. [here](https://alexandrasilva.org/files/thesis.pdf)). Our presentation focused on the most important intuitive aspects of the proofs. To dive deep, please take a look at the full Dafny source code, which is available [here](../../../../assets/src/semantics-of-regular-expressions/Languages.dfy), [here](../../../../assets/src/semantics-of-regular-expressions/Semantics.dfy), and [here](../../../../assets/src/semantics-of-regular-expressions/Expressions.dfy). \ No newline at end of file From 377db8468e268a31d5d17a5f7006865d4304b081 Mon Sep 17 00:00:00 2001 From: stefan-aws Date: Tue, 2 Jan 2024 16:56:06 +0000 Subject: [PATCH 17/62] Alexandra comments --- ...-semantics-of-regular-expressions.markdown | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/_posts/2024-01-10-semantics-of-regular-expressions.markdown b/_posts/2024-01-10-semantics-of-regular-expressions.markdown index b6cb672..ac7b5f4 100644 --- a/_posts/2024-01-10-semantics-of-regular-expressions.markdown +++ b/_posts/2024-01-10-semantics-of-regular-expressions.markdown @@ -7,7 +7,7 @@ author: Stefan Zetzsche and Wojciech Różowski ## Introduction -[Regular expressions](https://en.wikipedia.org/wiki/Regular_expression) are one of the most ubiquitous formalisms of theoretical computer science. Commonly, they are understood in terms of their [*denotational* semantics](https://en.wikipedia.org/wiki/Denotational_semantics), that is, through the formal languages — the [*regular* languages](https://en.wikipedia.org/wiki/Regular_language) — that they induce. This view is *inductive* in nature: two primitives are equivalent if they are *constructed* in the same way. Alternatively, regular expressions can be understood in terms of their [*operational* semantics](https://en.wikipedia.org/wiki/Operational_semantics), that is, through the [finite automata](https://en.wikipedia.org/wiki/Finite-state_machine) they induce. This view is *coinductive* in nature: two primitives are equivalent if they are *deconstructed* in the same way. It is is hinted at by [Kleene’s famous theorem](http://www.dlsi.ua.es/~mlf/nnafmc/papers/kleene56representation.pdf) that both views are equivalent: regular languages are precisely the formal languages accepted by finite automata. In this blogpost, we utilise Dafny’s built-in inductive and coinductive reasoning capabilities to show that the two semantics of regular expressions are *[well-behaved](https://homepages.inf.ed.ac.uk/gdp/publications/Math_Op_Sem.pdf)*, in the sense they are in fact one and the same, up to pointwise [bisimulation](https://en.wikipedia.org/wiki/Bisimulation). +[Regular expressions](https://en.wikipedia.org/wiki/Regular_expression) are one of the most ubiquitous formalisms of theoretical computer science. Commonly, they are understood in terms of their [*denotational* semantics](https://en.wikipedia.org/wiki/Denotational_semantics), that is, through formal languages — the [*regular* languages](https://en.wikipedia.org/wiki/Regular_language). This view is *inductive* in nature: two primitives are equivalent if they are *constructed* in the same way. Alternatively, regular expressions can be understood in terms of their [*operational* semantics](https://en.wikipedia.org/wiki/Operational_semantics), that is, through the [finite automata](https://en.wikipedia.org/wiki/Finite-state_machine) they induce. This view is *coinductive* in nature: two primitives are equivalent if they are *deconstructed* in the same way. It is implied by [Kleene’s famous theorem](http://www.dlsi.ua.es/~mlf/nnafmc/papers/kleene56representation.pdf) that both views are equivalent: regular languages are precisely the formal languages accepted by finite automata. In this blogpost, we utilise Dafny’s built-in inductive and coinductive reasoning capabilities to show that the two semantics of regular expressions are *[well-behaved](https://homepages.inf.ed.ac.uk/gdp/publications/Math_Op_Sem.pdf)*, in the sense they are in fact one and the same, up to pointwise [bisimulation](https://en.wikipedia.org/wiki/Bisimulation). ## Denotational Semantics @@ -41,7 +41,7 @@ If you think of formal languages as the set of all sets of finite sequences, you First, there exists the empty language `Zero()` that contains no words at all. Under above view, we find `Zero().eps == false` and `Zero().delta(a) == Zero()`, since the empty set does not contain the empty sequence, and the derivative `iset s | [a] + s in iset{}` with respect to any `a: A` yields again the empty set, respectively. We thus define: ``` -ghost function Zero(): Lang { +function Zero(): Lang { Alpha(false, (a: A) => Zero()) } ``` @@ -49,23 +49,23 @@ ghost function Zero(): Lang { Using similar reasoning, we additionally derive the following definitions. In order, we formalise i) the language `One()` that contains only the empty sequence; ii) for any `a: A` the language `Singleton(a)` that consists of only the word `[a]`; iii) the language `Plus(L1, L2)` which consists of the union of the languages `L1` and `L2`; iv) the language `Comp(L1, L2)` that consists of all possible concatenation of words in `L1` and `L2`; and v) the language `Star(L)` that consists of all finite compositions of `L` with itself. Our definitions match what is well-known as *[Brzozowski derivatives](https://en.wikipedia.org/wiki/Brzozowski_derivative)*. ``` -ghost function One(): Lang { +function One(): Lang { Alpha(true, (a: A) => Zero()) } -ghost function Singleton(a: A): Lang { +function Singleton(a: A): Lang { Alpha(false, (b: A) => if a == b then One() else Zero()) } -ghost function {:abstemious} Plus(L1: Lang, L2: Lang): Lang { +function {:abstemious} Plus(L1: Lang, L2: Lang): Lang { Alpha(L1.eps || L2.eps, (a: A) => Plus(L1.delta(a), L2.delta(a))) } -ghost function {:abstemious} Comp(L1: Lang, L2: Lang): Lang { +function {:abstemious} Comp(L1: Lang, L2: Lang): Lang { Alpha(L1.eps && L2.eps, (a: A) => Plus(Comp(L1.delta(a), L2), Comp(if L1.eps then One() else Zero(), L2.delta(a))) ) } -ghost function Star(L: Lang): Lang { +function Star(L: Lang): Lang { Alpha(true, (a: A) => Comp(L.delta(a), Star(L))) } ``` @@ -75,7 +75,7 @@ ghost function Star(L: Lang): Lang { The denotational semantics of regular expressions can now be defined through induction, as a function `Denotational: Exp -> Lang`, by making use of the operations on languages we have just defined: ``` -ghost function Denotational(e: Exp): Lang { +function Denotational(e: Exp): Lang { match e case Zero => Languages.Zero() case One => Languages.One() @@ -102,11 +102,11 @@ It is instructive to think of a `greatest predicate` as pure syntactic sugar. In ``` /* Pseudo code for illustration purposes */ -ghost predicate Bisimilar(L1: Lang, L2: Lang) { +predicate Bisimilar(L1: Lang, L2: Lang) { forall k :: Bisimilar#[k](L1, L2) } -ghost predicate Bisimilar#[k: nat](L1: Lang, L2: Lang) +predicate Bisimilar#[k: nat](L1: Lang, L2: Lang) decreases k { if k == 0 then @@ -176,6 +176,8 @@ ghost predicate IsAlgebraHomomorphismPointwise(f: Exp -> Lang, e: Exp) } ``` +Note that we used the `ghost` modifier (which signals that an entity is for specification only, not for compilation) in `IsAlgebraHomomorphism`, since quantifiers in non-ghost contexts must be compilable, but Dafny's heuristics can't figure out how to produce a bounded set of values for `e`, and in `IsAlgebraHomomorphismPointwise` because a call to a greatest predicate is allowed only in specification contexts. + The proof that `Denotational` is an algebra homomorphism is straightforward: it essentially follows from bisimilarity being reflexive: ``` @@ -197,7 +199,7 @@ In this section, we provide an alternative perspective on the semantics of regul In [An Algebra of Formal Languages](#an-algebra-of-formal-languagesn Algebra of Formal Languages) we equipped the set of formal languages with an algebraic structure that resembles the one of regular expressions. Now, we are aiming for the reverse: we would like to equip the set of regular expressions with a coalgebraic structure that resembles the one of formal languages. More concretely, we would like to turn the set of regular expressions into a deterministic automaton (without initial state) in which a state `e` is i) accepting iff `Eps(e) == true` and ii) transitions to a state `Delta(e)(a)` if given the input `a: A`. Note how our definitions resemble the Brzozowski derivatives we previously encountered: ``` -ghost function Eps(e: Exp): bool { +function Eps(e: Exp): bool { match e case Zero => false case One => true @@ -207,7 +209,7 @@ ghost function Eps(e: Exp): bool { case Star(e1) => true } -ghost function Delta(e: Exp): A -> Exp { +function Delta(e: Exp): A -> Exp { (a: A) => match e case Zero => Zero From db2ab13e86574a49f8a84e33219959010d647556 Mon Sep 17 00:00:00 2001 From: stefan-aws Date: Tue, 2 Jan 2024 17:14:23 +0000 Subject: [PATCH 18/62] explain ghost and abstemious --- _posts/2024-01-10-semantics-of-regular-expressions.markdown | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/_posts/2024-01-10-semantics-of-regular-expressions.markdown b/_posts/2024-01-10-semantics-of-regular-expressions.markdown index ac7b5f4..abc7cc9 100644 --- a/_posts/2024-01-10-semantics-of-regular-expressions.markdown +++ b/_posts/2024-01-10-semantics-of-regular-expressions.markdown @@ -70,6 +70,8 @@ function Star(L: Lang): Lang { } ``` +Note that the [`{:abstemious}`](https://dafny.org/latest/DafnyRef/DafnyRef#sec-abstemious) attribute above signals that a function does not need to unfold a codatatype instance very far (perhaps just one destructor call) to prove a relevant property. Knowing this is the case can aid the proofs of properties about the function. + ### Denotational Semantics as Induced Morphism The denotational semantics of regular expressions can now be defined through induction, as a function `Denotational: Exp -> Lang`, by making use of the operations on languages we have just defined: @@ -176,7 +178,7 @@ ghost predicate IsAlgebraHomomorphismPointwise(f: Exp -> Lang, e: Exp) } ``` -Note that we used the `ghost` modifier (which signals that an entity is for specification only, not for compilation) in `IsAlgebraHomomorphism`, since quantifiers in non-ghost contexts must be compilable, but Dafny's heuristics can't figure out how to produce a bounded set of values for `e`, and in `IsAlgebraHomomorphismPointwise` because a call to a greatest predicate is allowed only in specification contexts. +Note that we used the [`ghost`](https://dafny.org/latest/DafnyRef/DafnyRef#sec-declaration-modifier) modifier (which signals that an entity is meant for specification only, not for compilation) in `IsAlgebraHomomorphism`, since quantifiers in non-ghost contexts must be compilable, but Dafny's heuristics can't figure out how to produce a bounded set of values for `e`, and in `IsAlgebraHomomorphismPointwise` because a call to a greatest predicate is allowed only in specification contexts. The proof that `Denotational` is an algebra homomorphism is straightforward: it essentially follows from bisimilarity being reflexive: From 29bf113e347adc108c040f68010ec71e9e6e891e Mon Sep 17 00:00:00 2001 From: stefan-aws Date: Tue, 2 Jan 2024 17:15:49 +0000 Subject: [PATCH 19/62] Alexandra comment 2 --- _posts/2024-01-10-semantics-of-regular-expressions.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_posts/2024-01-10-semantics-of-regular-expressions.markdown b/_posts/2024-01-10-semantics-of-regular-expressions.markdown index abc7cc9..f85a36d 100644 --- a/_posts/2024-01-10-semantics-of-regular-expressions.markdown +++ b/_posts/2024-01-10-semantics-of-regular-expressions.markdown @@ -7,7 +7,7 @@ author: Stefan Zetzsche and Wojciech Różowski ## Introduction -[Regular expressions](https://en.wikipedia.org/wiki/Regular_expression) are one of the most ubiquitous formalisms of theoretical computer science. Commonly, they are understood in terms of their [*denotational* semantics](https://en.wikipedia.org/wiki/Denotational_semantics), that is, through formal languages — the [*regular* languages](https://en.wikipedia.org/wiki/Regular_language). This view is *inductive* in nature: two primitives are equivalent if they are *constructed* in the same way. Alternatively, regular expressions can be understood in terms of their [*operational* semantics](https://en.wikipedia.org/wiki/Operational_semantics), that is, through the [finite automata](https://en.wikipedia.org/wiki/Finite-state_machine) they induce. This view is *coinductive* in nature: two primitives are equivalent if they are *deconstructed* in the same way. It is implied by [Kleene’s famous theorem](http://www.dlsi.ua.es/~mlf/nnafmc/papers/kleene56representation.pdf) that both views are equivalent: regular languages are precisely the formal languages accepted by finite automata. In this blogpost, we utilise Dafny’s built-in inductive and coinductive reasoning capabilities to show that the two semantics of regular expressions are *[well-behaved](https://homepages.inf.ed.ac.uk/gdp/publications/Math_Op_Sem.pdf)*, in the sense they are in fact one and the same, up to pointwise [bisimulation](https://en.wikipedia.org/wiki/Bisimulation). +[Regular expressions](https://en.wikipedia.org/wiki/Regular_expression) are one of the most ubiquitous formalisms of theoretical computer science. Commonly, they are understood in terms of their [*denotational* semantics](https://en.wikipedia.org/wiki/Denotational_semantics), that is, through formal languages — the [*regular* languages](https://en.wikipedia.org/wiki/Regular_language). This view is *inductive* in nature: two primitives are equivalent if they are *constructed* in the same way. Alternatively, regular expressions can be understood in terms of their [*operational* semantics](https://en.wikipedia.org/wiki/Operational_semantics), that is, through [finite automata](https://en.wikipedia.org/wiki/Finite-state_machine). This view is *coinductive* in nature: two primitives are equivalent if they are *deconstructed* in the same way. It is implied by [Kleene’s famous theorem](http://www.dlsi.ua.es/~mlf/nnafmc/papers/kleene56representation.pdf) that both views are equivalent: regular languages are precisely the formal languages accepted by finite automata. In this blogpost, we utilise Dafny’s built-in inductive and coinductive reasoning capabilities to show that the two semantics of regular expressions are *[well-behaved](https://homepages.inf.ed.ac.uk/gdp/publications/Math_Op_Sem.pdf)*, in the sense they are in fact one and the same, up to pointwise [bisimulation](https://en.wikipedia.org/wiki/Bisimulation). ## Denotational Semantics From f493f9fe8590fd1463ebe530fd01d98f2c48b599 Mon Sep 17 00:00:00 2001 From: Stefan Zetzsche <120379523+stefan-aws@users.noreply.github.com> Date: Wed, 3 Jan 2024 11:25:54 +0000 Subject: [PATCH 20/62] Update assets/src/semantics-of-regular-expressions/Semantics.dfy Co-authored-by: Rustan Leino --- assets/src/semantics-of-regular-expressions/Semantics.dfy | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/assets/src/semantics-of-regular-expressions/Semantics.dfy b/assets/src/semantics-of-regular-expressions/Semantics.dfy index 87d8694..c413927 100644 --- a/assets/src/semantics-of-regular-expressions/Semantics.dfy +++ b/assets/src/semantics-of-regular-expressions/Semantics.dfy @@ -226,7 +226,9 @@ module Semantics { lemma OperationalIsCoalgebraHomomorphism() ensures IsCoalgebraHomomorphism(Operational) { - forall e, a ensures Bisimilar(Operational(e).delta(a), Operational(Delta(e)(a))) { + forall e, a + ensures Bisimilar(Operational(e).delta(a), Operational(Delta(e)(a))) + { BisimilarityIsReflexive(Operational(e).delta(a)); } } From 5c05c66b7e1ce91996a2597173d034dc03f92eb5 Mon Sep 17 00:00:00 2001 From: Stefan Zetzsche <120379523+stefan-aws@users.noreply.github.com> Date: Wed, 3 Jan 2024 11:26:12 +0000 Subject: [PATCH 21/62] Update assets/src/semantics-of-regular-expressions/Semantics.dfy Co-authored-by: Rustan Leino --- assets/src/semantics-of-regular-expressions/Semantics.dfy | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/assets/src/semantics-of-regular-expressions/Semantics.dfy b/assets/src/semantics-of-regular-expressions/Semantics.dfy index c413927..278835c 100644 --- a/assets/src/semantics-of-regular-expressions/Semantics.dfy +++ b/assets/src/semantics-of-regular-expressions/Semantics.dfy @@ -111,7 +111,9 @@ module Semantics { forall e ensures Denotational(e).eps == Eps(e) { DenotationalIsCoalgebraHomomorphismHelper1(e); } - forall e, a ensures Bisimilar(Denotational(e).delta(a), Denotational(Delta(e)(a))) { + forall e, a + ensures Bisimilar(Denotational(e).delta(a), Denotational(Delta(e)(a))) + { DenotationalIsCoalgebraHomomorphismHelper2(e, a); } } From a9b89656fc6a3ad141e7c894ce324110052e4120 Mon Sep 17 00:00:00 2001 From: Stefan Zetzsche <120379523+stefan-aws@users.noreply.github.com> Date: Wed, 3 Jan 2024 11:26:24 +0000 Subject: [PATCH 22/62] Update assets/src/semantics-of-regular-expressions/Semantics.dfy Co-authored-by: Rustan Leino --- assets/src/semantics-of-regular-expressions/Semantics.dfy | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/assets/src/semantics-of-regular-expressions/Semantics.dfy b/assets/src/semantics-of-regular-expressions/Semantics.dfy index 278835c..cec7eab 100644 --- a/assets/src/semantics-of-regular-expressions/Semantics.dfy +++ b/assets/src/semantics-of-regular-expressions/Semantics.dfy @@ -108,7 +108,9 @@ module Semantics { lemma DenotationalIsCoalgebraHomomorphism() ensures IsCoalgebraHomomorphism(Denotational) { - forall e ensures Denotational(e).eps == Eps(e) { + forall e + ensures Denotational(e).eps == Eps(e) + { DenotationalIsCoalgebraHomomorphismHelper1(e); } forall e, a From 6548587c5c3c04095410aa832e67db5575b2fbb2 Mon Sep 17 00:00:00 2001 From: Stefan Zetzsche <120379523+stefan-aws@users.noreply.github.com> Date: Wed, 3 Jan 2024 11:26:38 +0000 Subject: [PATCH 23/62] Update assets/src/semantics-of-regular-expressions/Semantics.dfy Co-authored-by: Rustan Leino --- assets/src/semantics-of-regular-expressions/Semantics.dfy | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/assets/src/semantics-of-regular-expressions/Semantics.dfy b/assets/src/semantics-of-regular-expressions/Semantics.dfy index cec7eab..bd36072 100644 --- a/assets/src/semantics-of-regular-expressions/Semantics.dfy +++ b/assets/src/semantics-of-regular-expressions/Semantics.dfy @@ -98,7 +98,9 @@ module Semantics { lemma DenotationalIsAlgebraHomomorphism() ensures IsAlgebraHomomorphism(Denotational) { - forall e ensures IsAlgebraHomomorphismPointwise(Denotational, e) { + forall e + ensures IsAlgebraHomomorphismPointwise(Denotational, e) + { BisimilarityIsReflexive(Denotational(e)); } } From e9ba7ed05392a2da1e96210636563207b694edf2 Mon Sep 17 00:00:00 2001 From: Stefan Zetzsche <120379523+stefan-aws@users.noreply.github.com> Date: Wed, 3 Jan 2024 11:26:52 +0000 Subject: [PATCH 24/62] Update assets/src/semantics-of-regular-expressions/Semantics.dfy Co-authored-by: Rustan Leino --- assets/src/semantics-of-regular-expressions/Semantics.dfy | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/assets/src/semantics-of-regular-expressions/Semantics.dfy b/assets/src/semantics-of-regular-expressions/Semantics.dfy index bd36072..7ddd7f7 100644 --- a/assets/src/semantics-of-regular-expressions/Semantics.dfy +++ b/assets/src/semantics-of-regular-expressions/Semantics.dfy @@ -85,7 +85,9 @@ module Semantics { { var e :| Bisimilar#[k](L1, f(e)) && Bisimilar#[k](L2, g(e)); if k != 0 { - forall a ensures Bisimilar#[k-1](L1.delta(a), L2.delta(a)) { + forall a + ensures Bisimilar#[k-1](L1.delta(a), L2.delta(a)) + { BisimilarityIsTransitivePointwise(k-1, L1.delta(a), f(e).delta(a), f(Delta(e)(a))); BisimilarityIsTransitivePointwise(k-1, L2.delta(a), g(e).delta(a), g(Delta(e)(a))); UniqueCoalgebraHomomorphismHelperPointwise(k-1, f, g, L1.delta(a), L2.delta(a)); From 0c0789eee5aaf1d5519a5c9847ab3952c59c2741 Mon Sep 17 00:00:00 2001 From: Stefan Zetzsche <120379523+stefan-aws@users.noreply.github.com> Date: Wed, 3 Jan 2024 11:27:48 +0000 Subject: [PATCH 25/62] Update _posts/2024-01-10-semantics-of-regular-expressions.markdown Co-authored-by: Rustan Leino --- _posts/2024-01-10-semantics-of-regular-expressions.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_posts/2024-01-10-semantics-of-regular-expressions.markdown b/_posts/2024-01-10-semantics-of-regular-expressions.markdown index f85a36d..7badde3 100644 --- a/_posts/2024-01-10-semantics-of-regular-expressions.markdown +++ b/_posts/2024-01-10-semantics-of-regular-expressions.markdown @@ -364,7 +364,7 @@ lemma OperationalAndDenotationalAreBisimilar(e: Exp) ### Operational Semantics as Algebra Homomorphism -As a little extra, for the sake of symmetry, let us also prove that `Operational` is an algebra homomorphism. (We already know that it is a coalgebra homomorphism, and that `Denotational` is both an algebra and coalgebra homomorphism.) +As a bonus, for the sake of symmetry, let us also prove that `Operational` is an algebra homomorphism. (We already know that it is a coalgebra homomorphism, and that `Denotational` is both an algebra and coalgebra homomorphism.) ``` lemma OperationalIsAlgebraHomomorphism() From a3b1d08fed353d6b10f34b0fa4bbe01b0bdddc48 Mon Sep 17 00:00:00 2001 From: Stefan Zetzsche <120379523+stefan-aws@users.noreply.github.com> Date: Wed, 3 Jan 2024 11:28:10 +0000 Subject: [PATCH 26/62] Update assets/src/semantics-of-regular-expressions/Semantics.dfy Co-authored-by: Rustan Leino --- assets/src/semantics-of-regular-expressions/Semantics.dfy | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/assets/src/semantics-of-regular-expressions/Semantics.dfy b/assets/src/semantics-of-regular-expressions/Semantics.dfy index 7ddd7f7..c3be654 100644 --- a/assets/src/semantics-of-regular-expressions/Semantics.dfy +++ b/assets/src/semantics-of-regular-expressions/Semantics.dfy @@ -70,7 +70,9 @@ module Semantics { requires exists e :: Bisimilar(L1, f(e)) && Bisimilar(L2, g(e)) ensures Bisimilar(L1, L2) { - forall k ensures Bisimilar#[k](L1, L2) { + forall k: nat + ensures Bisimilar#[k](L1, L2) + { if k != 0 { UniqueCoalgebraHomomorphismHelperPointwise(k, f, g, L1, L2); } From 927cbbbcb2ba93e2ee3eccddf5195cecf36a36b7 Mon Sep 17 00:00:00 2001 From: Stefan Zetzsche <120379523+stefan-aws@users.noreply.github.com> Date: Wed, 3 Jan 2024 11:28:41 +0000 Subject: [PATCH 27/62] Update _posts/2024-01-10-semantics-of-regular-expressions.markdown Co-authored-by: Rustan Leino --- _posts/2024-01-10-semantics-of-regular-expressions.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_posts/2024-01-10-semantics-of-regular-expressions.markdown b/_posts/2024-01-10-semantics-of-regular-expressions.markdown index 7badde3..80ff557 100644 --- a/_posts/2024-01-10-semantics-of-regular-expressions.markdown +++ b/_posts/2024-01-10-semantics-of-regular-expressions.markdown @@ -105,7 +105,7 @@ It is instructive to think of a `greatest predicate` as pure syntactic sugar. In /* Pseudo code for illustration purposes */ predicate Bisimilar(L1: Lang, L2: Lang) { - forall k :: Bisimilar#[k](L1, L2) + forall k: nat :: Bisimilar#[k](L1, L2) } predicate Bisimilar#[k: nat](L1: Lang, L2: Lang) From 62227befec9c7d2a0759c3b9fd0a6b3a5871e12a Mon Sep 17 00:00:00 2001 From: Stefan Zetzsche <120379523+stefan-aws@users.noreply.github.com> Date: Wed, 3 Jan 2024 11:29:04 +0000 Subject: [PATCH 28/62] Update assets/src/semantics-of-regular-expressions/Languages.dfy Co-authored-by: Rustan Leino --- assets/src/semantics-of-regular-expressions/Languages.dfy | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/assets/src/semantics-of-regular-expressions/Languages.dfy b/assets/src/semantics-of-regular-expressions/Languages.dfy index 9eca84a..d451731 100644 --- a/assets/src/semantics-of-regular-expressions/Languages.dfy +++ b/assets/src/semantics-of-regular-expressions/Languages.dfy @@ -193,7 +193,9 @@ module Languages { if k != 0 { BisimilarCuttingPrefixesPointwise(k, a, L1, L2); var k' :| k == k' + 1; - forall n: nat ensures n <= k' + 1 ==> Bisimilar#[n](Star(L1), Star(L2)) { + forall n: nat + ensures n <= k' + 1 ==> Bisimilar#[n](Star(L1), Star(L2)) + { if 0 < n <= k' + 1 { var n' :| n == n' + 1; StarCongruenceHelper(n', L1,L2); From edd1018977c4d85f8247fa7965b953bca1ff6a3c Mon Sep 17 00:00:00 2001 From: Stefan Zetzsche <120379523+stefan-aws@users.noreply.github.com> Date: Wed, 3 Jan 2024 11:29:56 +0000 Subject: [PATCH 29/62] Update assets/src/semantics-of-regular-expressions/Languages.dfy Co-authored-by: Rustan Leino --- assets/src/semantics-of-regular-expressions/Languages.dfy | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/assets/src/semantics-of-regular-expressions/Languages.dfy b/assets/src/semantics-of-regular-expressions/Languages.dfy index d451731..e1392dd 100644 --- a/assets/src/semantics-of-regular-expressions/Languages.dfy +++ b/assets/src/semantics-of-regular-expressions/Languages.dfy @@ -177,7 +177,9 @@ module Languages { requires Bisimilar(L1, L2) ensures Bisimilar(Star(L1), Star(L2)) { - forall k ensures Bisimilar#[k](Star(L1), Star(L2)) { + forall k: nat + ensures Bisimilar#[k](Star(L1), Star(L2)) + { if k != 0 { var k' :| k' + 1 == k; StarCongruenceHelper(k', L1, L2); From f9c6640f40c20bf2aae680d0d4b2f1d96e688d7f Mon Sep 17 00:00:00 2001 From: Stefan Zetzsche <120379523+stefan-aws@users.noreply.github.com> Date: Wed, 3 Jan 2024 11:30:14 +0000 Subject: [PATCH 30/62] Update assets/src/semantics-of-regular-expressions/Languages.dfy Co-authored-by: Rustan Leino --- assets/src/semantics-of-regular-expressions/Languages.dfy | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/assets/src/semantics-of-regular-expressions/Languages.dfy b/assets/src/semantics-of-regular-expressions/Languages.dfy index e1392dd..8ef0661 100644 --- a/assets/src/semantics-of-regular-expressions/Languages.dfy +++ b/assets/src/semantics-of-regular-expressions/Languages.dfy @@ -91,7 +91,9 @@ module Languages { requires forall n: nat :: n <= k + 1 ==> Bisimilar#[n](L1a, L1b) ensures forall n: nat :: n <= k ==> Bisimilar#[n](L1a.delta(a), L1b.delta(a)) { - forall n: nat ensures n <= k ==> Bisimilar#[n](L1a.delta(a), L1b.delta(a)) { + forall n: nat + ensures n <= k ==> Bisimilar#[n](L1a.delta(a), L1b.delta(a)) + { if n <= k { BisimilarCuttingPrefixes(n, L1a, L1b); } From 25332901ff04bb13b1ea2b0237daf2eaa03e8009 Mon Sep 17 00:00:00 2001 From: Stefan Zetzsche <120379523+stefan-aws@users.noreply.github.com> Date: Wed, 3 Jan 2024 11:30:46 +0000 Subject: [PATCH 31/62] Update assets/src/semantics-of-regular-expressions/Languages.dfy Co-authored-by: Rustan Leino --- assets/src/semantics-of-regular-expressions/Languages.dfy | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/assets/src/semantics-of-regular-expressions/Languages.dfy b/assets/src/semantics-of-regular-expressions/Languages.dfy index 8ef0661..12c7fc0 100644 --- a/assets/src/semantics-of-regular-expressions/Languages.dfy +++ b/assets/src/semantics-of-regular-expressions/Languages.dfy @@ -121,7 +121,9 @@ module Languages { requires Bisimilar(L2a, L2b) ensures Bisimilar(Comp(L1a, L2a), Comp(L1b, L2b)) { - forall k ensures Bisimilar#[k](Comp(L1a,L2a), Comp(L1b,L2b)) { + forall k: nat + ensures Bisimilar#[k](Comp(L1a,L2a), Comp(L1b,L2b)) + { if k != 0 { var k' :| k' + 1 == k; CompCongruenceHelper(k', L1a, L1b, L2a, L2b); From 91bf68e2cf0f50417ad57b24dbf642b2867a6c6e Mon Sep 17 00:00:00 2001 From: Stefan Zetzsche <120379523+stefan-aws@users.noreply.github.com> Date: Wed, 3 Jan 2024 11:31:13 +0000 Subject: [PATCH 32/62] Update assets/src/semantics-of-regular-expressions/Languages.dfy Co-authored-by: Rustan Leino --- assets/src/semantics-of-regular-expressions/Languages.dfy | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/assets/src/semantics-of-regular-expressions/Languages.dfy b/assets/src/semantics-of-regular-expressions/Languages.dfy index 12c7fc0..5c28d7f 100644 --- a/assets/src/semantics-of-regular-expressions/Languages.dfy +++ b/assets/src/semantics-of-regular-expressions/Languages.dfy @@ -79,7 +79,9 @@ module Languages { requires forall n: nat :: n <= k + 1 ==> Bisimilar#[n](L1, L2) ensures forall a :: Bisimilar#[k](L1.delta(a), L2.delta(a)) { - forall a ensures Bisimilar#[k](L1.delta(a), L2.delta(a)) { + forall a + ensures Bisimilar#[k](L1.delta(a), L2.delta(a)) + { if k != 0 { assert Bisimilar#[k + 1](L1, L2); } From 754b3b215636cc8206591f202a21e66292c82944 Mon Sep 17 00:00:00 2001 From: Stefan Zetzsche <120379523+stefan-aws@users.noreply.github.com> Date: Wed, 3 Jan 2024 11:31:38 +0000 Subject: [PATCH 33/62] Update _posts/2024-01-10-semantics-of-regular-expressions.markdown Co-authored-by: Rustan Leino --- _posts/2024-01-10-semantics-of-regular-expressions.markdown | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/_posts/2024-01-10-semantics-of-regular-expressions.markdown b/_posts/2024-01-10-semantics-of-regular-expressions.markdown index 80ff557..32c02a5 100644 --- a/_posts/2024-01-10-semantics-of-regular-expressions.markdown +++ b/_posts/2024-01-10-semantics-of-regular-expressions.markdown @@ -186,7 +186,9 @@ The proof that `Denotational` is an algebra homomorphism is straightforward: it lemma DenotationalIsAlgebraHomomorphism() ensures IsAlgebraHomomorphism(Denotational) { - forall e ensures IsAlgebraHomomorphismPointwise(Denotational, e) { + forall e + ensures IsAlgebraHomomorphismPointwise(Denotational, e) + { BisimilarityIsReflexive(Denotational(e)); } } From 8ef3ff8557f304d94430bb1b75a63b26a9147629 Mon Sep 17 00:00:00 2001 From: Stefan Zetzsche <120379523+stefan-aws@users.noreply.github.com> Date: Wed, 3 Jan 2024 11:32:01 +0000 Subject: [PATCH 34/62] Update assets/src/semantics-of-regular-expressions/Languages.dfy Co-authored-by: Rustan Leino --- assets/src/semantics-of-regular-expressions/Languages.dfy | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/assets/src/semantics-of-regular-expressions/Languages.dfy b/assets/src/semantics-of-regular-expressions/Languages.dfy index 5c28d7f..70ad233 100644 --- a/assets/src/semantics-of-regular-expressions/Languages.dfy +++ b/assets/src/semantics-of-regular-expressions/Languages.dfy @@ -62,7 +62,9 @@ module Languages { if k != 0 { if Bisimilar#[k](L1, L2) && Bisimilar#[k](L2, L3) { assert Bisimilar#[k](L1, L3) by { - forall a ensures Bisimilar#[k-1](L1.delta(a), L3.delta(a)) { + forall a + ensures Bisimilar#[k-1](L1.delta(a), L3.delta(a)) + { BisimilarityIsTransitivePointwise(k-1, L1.delta(a), L2.delta(a), L3.delta(a)); } } From 58652690de7bc8b45161a08e3f1e70e0f90da56a Mon Sep 17 00:00:00 2001 From: Stefan Zetzsche <120379523+stefan-aws@users.noreply.github.com> Date: Wed, 3 Jan 2024 11:32:46 +0000 Subject: [PATCH 35/62] Update _posts/2024-01-10-semantics-of-regular-expressions.markdown Co-authored-by: Rustan Leino --- _posts/2024-01-10-semantics-of-regular-expressions.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_posts/2024-01-10-semantics-of-regular-expressions.markdown b/_posts/2024-01-10-semantics-of-regular-expressions.markdown index 32c02a5..75c377c 100644 --- a/_posts/2024-01-10-semantics-of-regular-expressions.markdown +++ b/_posts/2024-01-10-semantics-of-regular-expressions.markdown @@ -90,7 +90,7 @@ function Denotational(e: Exp): Lang { ### Bisimilarity and Coinduction -Let us briefly introduce a notion of equality between formal languages that will be useful soon. A binary relation `R` between languages is called a *[bisimulation](https://en.wikipedia.org/wiki/Bisimulation),* if for any two languages `L1`, `L2` related by `R` the following holds: i) `L1` contains the empty word iff `L2` does; and ii) for any `a: A` the derivatives `L1.delta(a)` and `L2.delta(a)` are again related by `R`. As it turns out, the union of two bisimulations is again a bisimulation. In consequence, one can combine all possible bisimulations into a single relation: the *greatest* bisimulation. In Dafny, we can formalise the latter as a [`greatest predicate`](https://dafny.org/latest/DafnyRef/DafnyRef#sec-copredicates): +Let us briefly introduce a notion of equality between formal languages that will be useful soon. A binary relation `R` between languages is called a *[bisimulation](https://en.wikipedia.org/wiki/Bisimulation),* if for any two languages `L1`, `L2` related by `R` the following holds: i) `L1` contains the empty word iff `L2` does; and ii) for any `a: A`, the derivatives `L1.delta(a)` and `L2.delta(a)` are again related by `R`. As it turns out, the union of two bisimulations is again a bisimulation. In consequence, one can combine all possible bisimulations into a single relation: the *greatest* bisimulation. In Dafny, we can formalise the latter as a [`greatest predicate`](https://dafny.org/latest/DafnyRef/DafnyRef#sec-copredicates): ``` greatest predicate Bisimilar[nat](L1: Lang, L2: Lang) { From 22849e99b0761fbf74c23187767c08bb73c7e3b4 Mon Sep 17 00:00:00 2001 From: Stefan Zetzsche <120379523+stefan-aws@users.noreply.github.com> Date: Wed, 3 Jan 2024 11:33:20 +0000 Subject: [PATCH 36/62] Update _posts/2024-01-10-semantics-of-regular-expressions.markdown Co-authored-by: Rustan Leino --- _posts/2024-01-10-semantics-of-regular-expressions.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_posts/2024-01-10-semantics-of-regular-expressions.markdown b/_posts/2024-01-10-semantics-of-regular-expressions.markdown index 75c377c..c45bdfd 100644 --- a/_posts/2024-01-10-semantics-of-regular-expressions.markdown +++ b/_posts/2024-01-10-semantics-of-regular-expressions.markdown @@ -99,7 +99,7 @@ greatest predicate Bisimilar[nat](L1: Lang, L2: Lang) { } ``` -It is instructive to think of a `greatest predicate` as pure syntactic sugar. Indeed, under the hood, Dafny’s compiler uses above body to implicitly generate i) for any `k: nat`, a *[prefix predicate](https://dafny.org/latest/DafnyRef/DafnyRef#514361-properties-of-prefix-predicates)* `Bisimilar#[k](L1, L2)` that signifies that the languages `L1` and `L2` concur on the first `k`-unrollings of the definition above; and ii) a predicate `Bisimilar(L1, L2)` that is true iff `Bisimilar#[k](L1, L2)` is true for all `k: nat`: +It is instructive to think of a `greatest predicate` as pure syntactic sugar. Indeed, under the hood, Dafny’s compiler uses the body above to implicitly generate i) for any `k: nat`, a *[prefix predicate](https://dafny.org/latest/DafnyRef/DafnyRef#514361-properties-of-prefix-predicates)* `Bisimilar#[k](L1, L2)` that signifies that the languages `L1` and `L2` concur on the first `k`-unrollings of the definition above; and ii) a predicate `Bisimilar(L1, L2)` that is true iff `Bisimilar#[k](L1, L2)` is true for all `k: nat`: ``` /* Pseudo code for illustration purposes */ From 0c57aa7418f7ad5d7dbd73a3a2ce21367c722018 Mon Sep 17 00:00:00 2001 From: Stefan Zetzsche <120379523+stefan-aws@users.noreply.github.com> Date: Wed, 3 Jan 2024 11:33:55 +0000 Subject: [PATCH 37/62] Update _posts/2024-01-10-semantics-of-regular-expressions.markdown Co-authored-by: Rustan Leino --- _posts/2024-01-10-semantics-of-regular-expressions.markdown | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/_posts/2024-01-10-semantics-of-regular-expressions.markdown b/_posts/2024-01-10-semantics-of-regular-expressions.markdown index c45bdfd..3a8481e 100644 --- a/_posts/2024-01-10-semantics-of-regular-expressions.markdown +++ b/_posts/2024-01-10-semantics-of-regular-expressions.markdown @@ -135,7 +135,9 @@ Once again, it is instructive to think of a `greatest lemma` as pure syntactic s lemma BisimilarityIsReflexive(L: Lang) ensures Bisimilar(L, L) { - forall k ensures Bisimilar#[k](L, L) { + forall k: nat + ensures Bisimilar#[k](L, L) + { BisimilarityIsReflexive#[k](L); } } From 647307982a4960e8d450f5fbab734a8690f121b5 Mon Sep 17 00:00:00 2001 From: Stefan Zetzsche <120379523+stefan-aws@users.noreply.github.com> Date: Wed, 3 Jan 2024 11:34:29 +0000 Subject: [PATCH 38/62] Update _posts/2024-01-10-semantics-of-regular-expressions.markdown Co-authored-by: Rustan Leino --- _posts/2024-01-10-semantics-of-regular-expressions.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_posts/2024-01-10-semantics-of-regular-expressions.markdown b/_posts/2024-01-10-semantics-of-regular-expressions.markdown index 3a8481e..06fe3f1 100644 --- a/_posts/2024-01-10-semantics-of-regular-expressions.markdown +++ b/_posts/2024-01-10-semantics-of-regular-expressions.markdown @@ -108,7 +108,7 @@ predicate Bisimilar(L1: Lang, L2: Lang) { forall k: nat :: Bisimilar#[k](L1, L2) } -predicate Bisimilar#[k: nat](L1: Lang, L2: Lang) +predicate Bisimilar#[k: nat](L1: Lang, L2: Lang) decreases k { if k == 0 then From 22a0813fee06c410ffc19b793093a01a94f30bab Mon Sep 17 00:00:00 2001 From: Stefan Zetzsche <120379523+stefan-aws@users.noreply.github.com> Date: Wed, 3 Jan 2024 11:34:49 +0000 Subject: [PATCH 39/62] Update _posts/2024-01-10-semantics-of-regular-expressions.markdown Co-authored-by: Rustan Leino --- _posts/2024-01-10-semantics-of-regular-expressions.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_posts/2024-01-10-semantics-of-regular-expressions.markdown b/_posts/2024-01-10-semantics-of-regular-expressions.markdown index 06fe3f1..2e6a02b 100644 --- a/_posts/2024-01-10-semantics-of-regular-expressions.markdown +++ b/_posts/2024-01-10-semantics-of-regular-expressions.markdown @@ -142,7 +142,7 @@ lemma BisimilarityIsReflexive(L: Lang) } } -lemma BisimilarityIsReflexive#[k: nat](L: Lang) +lemma BisimilarityIsReflexive#[k: nat](L: Lang) ensures Bisimilar#[k](L, L) decreases k { From 69bc3da87bf3bb45758b5ad2d2560815be2b4a11 Mon Sep 17 00:00:00 2001 From: Stefan Zetzsche <120379523+stefan-aws@users.noreply.github.com> Date: Wed, 3 Jan 2024 11:35:12 +0000 Subject: [PATCH 40/62] Update _posts/2024-01-10-semantics-of-regular-expressions.markdown Co-authored-by: Rustan Leino --- _posts/2024-01-10-semantics-of-regular-expressions.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_posts/2024-01-10-semantics-of-regular-expressions.markdown b/_posts/2024-01-10-semantics-of-regular-expressions.markdown index 2e6a02b..9f2ac04 100644 --- a/_posts/2024-01-10-semantics-of-regular-expressions.markdown +++ b/_posts/2024-01-10-semantics-of-regular-expressions.markdown @@ -65,7 +65,7 @@ function {:abstemious} Comp(L1: Lang, L2: Lang): Lang { Alpha(L1.eps && L2.eps, (a: A) => Plus(Comp(L1.delta(a), L2), Comp(if L1.eps then One() else Zero(), L2.delta(a))) ) } -function Star(L: Lang): Lang { +function Star(L: Lang): Lang { Alpha(true, (a: A) => Comp(L.delta(a), Star(L))) } ``` From 4b9b9445631691e6292a2547b3a1d89405c07cef Mon Sep 17 00:00:00 2001 From: Stefan Zetzsche <120379523+stefan-aws@users.noreply.github.com> Date: Wed, 3 Jan 2024 11:37:12 +0000 Subject: [PATCH 41/62] Update _posts/2024-01-10-semantics-of-regular-expressions.markdown Co-authored-by: Rustan Leino --- _posts/2024-01-10-semantics-of-regular-expressions.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_posts/2024-01-10-semantics-of-regular-expressions.markdown b/_posts/2024-01-10-semantics-of-regular-expressions.markdown index 9f2ac04..e7ee4e9 100644 --- a/_posts/2024-01-10-semantics-of-regular-expressions.markdown +++ b/_posts/2024-01-10-semantics-of-regular-expressions.markdown @@ -180,7 +180,7 @@ ghost predicate IsAlgebraHomomorphismPointwise(f: Exp -> Lang, e: Exp) } ``` -Note that we used the [`ghost`](https://dafny.org/latest/DafnyRef/DafnyRef#sec-declaration-modifier) modifier (which signals that an entity is meant for specification only, not for compilation) in `IsAlgebraHomomorphism`, since quantifiers in non-ghost contexts must be compilable, but Dafny's heuristics can't figure out how to produce a bounded set of values for `e`, and in `IsAlgebraHomomorphismPointwise` because a call to a greatest predicate is allowed only in specification contexts. +Note that we used the [`ghost`](https://dafny.org/latest/DafnyRef/DafnyRef#sec-declaration-modifier) modifier (which signals that an entity is meant for specification only, not for compilation). A `greatest predicate` is always ghost, so `IsAlgebraHomomorphismPointwise` must be declared ghost to call `Bisimilar`, and `IsAlgebraHomomorphismPointwise` must be declared ghost to call `IsAlgebraHomomorphism`. The proof that `Denotational` is an algebra homomorphism is straightforward: it essentially follows from bisimilarity being reflexive: From 5e6c9672e486d9878dad76d38fba2dbbb760a7a2 Mon Sep 17 00:00:00 2001 From: Stefan Zetzsche <120379523+stefan-aws@users.noreply.github.com> Date: Wed, 3 Jan 2024 11:37:28 +0000 Subject: [PATCH 42/62] Update _posts/2024-01-10-semantics-of-regular-expressions.markdown Co-authored-by: Rustan Leino --- _posts/2024-01-10-semantics-of-regular-expressions.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_posts/2024-01-10-semantics-of-regular-expressions.markdown b/_posts/2024-01-10-semantics-of-regular-expressions.markdown index e7ee4e9..07f4dc3 100644 --- a/_posts/2024-01-10-semantics-of-regular-expressions.markdown +++ b/_posts/2024-01-10-semantics-of-regular-expressions.markdown @@ -279,7 +279,7 @@ lemma DenotationalIsCoalgebraHomomorphism() {...} ``` -The proof of the lemma is a bit more elaborated than the ones we have encountered so far. It can be divided into two subproofs, both of which make use of induction. One of the subproofs is straightforward, the other, more difficult one, again uses the reflexivity of bisimilarity, but also that the latter is a *congruence* relation with respect to `Plus` and `Comp`: +The proof of the lemma is a bit more elaborate than the ones we have encountered so far. It can be divided into two subproofs, both of which make use of induction. One of the subproofs is straightforward, the other, more difficult one, again uses the reflexivity of bisimilarity, but also that the latter is a *congruence* relation with respect to `Plus` and `Comp`: ``` greatest lemma PlusCongruence[nat](L1a: Lang, L1b: Lang, L2a: Lang, L2b: Lang) From 6e66760aff9dcbdbde11da418b06694635b2509f Mon Sep 17 00:00:00 2001 From: stefan-aws Date: Wed, 3 Jan 2024 11:38:55 +0000 Subject: [PATCH 43/62] view above --- _posts/2024-01-10-semantics-of-regular-expressions.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_posts/2024-01-10-semantics-of-regular-expressions.markdown b/_posts/2024-01-10-semantics-of-regular-expressions.markdown index 07f4dc3..c9a73cf 100644 --- a/_posts/2024-01-10-semantics-of-regular-expressions.markdown +++ b/_posts/2024-01-10-semantics-of-regular-expressions.markdown @@ -38,7 +38,7 @@ To some, this choice might seem odd at first sight. If you are familiar with the If you think of formal languages as the set of all sets of finite sequences, you will soon realise that languages admit quite a bit of algebraic structure. For example, there exist two languages of distinct importance (can you already guess which ones?), and one can obtain a new language by taking e.g. the union of two languages. In fact, if you think about it for a bit longer, you’ll realise that formal languages admit exactly the same [type of algebraic structure](https://en.wikipedia.org/wiki/F-algebra) as the one you’ve encountered when we defined regular expressions! -First, there exists the empty language `Zero()` that contains no words at all. Under above view, we find `Zero().eps == false` and `Zero().delta(a) == Zero()`, since the empty set does not contain the empty sequence, and the derivative `iset s | [a] + s in iset{}` with respect to any `a: A` yields again the empty set, respectively. We thus define: +First, there exists the empty language `Zero()` that contains no words at all. Under the view above, we find `Zero().eps == false` and `Zero().delta(a) == Zero()`, since the empty set does not contain the empty sequence, and the derivative `iset s | [a] + s in iset{}` with respect to any `a: A` yields again the empty set, respectively. We thus define: ``` function Zero(): Lang { From 06294c17f0972115aed6f59b1f1e6ece9b632d59 Mon Sep 17 00:00:00 2001 From: stefan-aws Date: Wed, 3 Jan 2024 11:42:10 +0000 Subject: [PATCH 44/62] bug --- _posts/2024-01-10-semantics-of-regular-expressions.markdown | 2 +- assets/src/semantics-of-regular-expressions/Languages.dfy | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/_posts/2024-01-10-semantics-of-regular-expressions.markdown b/_posts/2024-01-10-semantics-of-regular-expressions.markdown index c9a73cf..d01499c 100644 --- a/_posts/2024-01-10-semantics-of-regular-expressions.markdown +++ b/_posts/2024-01-10-semantics-of-regular-expressions.markdown @@ -122,7 +122,7 @@ predicate Bisimilar#[k: nat](L1: Lang, L2: Lang) Now that we have its definition in place, let us establish a property about bisimilarity, say, that it is a [*reflexive*](https://en.wikipedia.org/wiki/Reflexive_relation) relation. With the [`greatest lemma`](https://dafny.org/latest/DafnyRef/DafnyRef#sec-colemmas) construct, Dafny is able to able to derive a proof completely on its own: ``` -greatest lemma BisimilarityIsReflexive[nat](L: Lang) +greatest lemma BisimilarityIsReflexive[nat](L: Lang) ensures Bisimilar(L, L) {} ``` diff --git a/assets/src/semantics-of-regular-expressions/Languages.dfy b/assets/src/semantics-of-regular-expressions/Languages.dfy index 70ad233..efd6aa1 100644 --- a/assets/src/semantics-of-regular-expressions/Languages.dfy +++ b/assets/src/semantics-of-regular-expressions/Languages.dfy @@ -37,7 +37,7 @@ module Languages { /* Bisimilarity */ - greatest lemma BisimilarityIsReflexive[nat](L: Lang) + greatest lemma BisimilarityIsReflexive[nat](L: Lang) ensures Bisimilar(L, L) {} From a31fbfceac02cc9966a7549c5689168a12d81ed1 Mon Sep 17 00:00:00 2001 From: stefan-aws Date: Wed, 3 Jan 2024 11:44:00 +0000 Subject: [PATCH 45/62] singleton equality --- _posts/2024-01-10-semantics-of-regular-expressions.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_posts/2024-01-10-semantics-of-regular-expressions.markdown b/_posts/2024-01-10-semantics-of-regular-expressions.markdown index d01499c..a5929a5 100644 --- a/_posts/2024-01-10-semantics-of-regular-expressions.markdown +++ b/_posts/2024-01-10-semantics-of-regular-expressions.markdown @@ -53,7 +53,7 @@ function One(): Lang { Alpha(true, (a: A) => Zero()) } -function Singleton(a: A): Lang { +function Singleton(a: A): Lang { Alpha(false, (b: A) => if a == b then One() else Zero()) } From 6549ca28f6292d3dbf186477f009e7440e3b9229 Mon Sep 17 00:00:00 2001 From: stefan-aws Date: Wed, 3 Jan 2024 12:03:54 +0000 Subject: [PATCH 46/62] type-parameter mode --- _posts/2024-01-10-semantics-of-regular-expressions.markdown | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/_posts/2024-01-10-semantics-of-regular-expressions.markdown b/_posts/2024-01-10-semantics-of-regular-expressions.markdown index a5929a5..1122f95 100644 --- a/_posts/2024-01-10-semantics-of-regular-expressions.markdown +++ b/_posts/2024-01-10-semantics-of-regular-expressions.markdown @@ -32,7 +32,9 @@ We define the set of formal languages parametric in an alphabet `A` as an coindu codatatype Lang = Alpha(eps: bool, delta: A -> Lang) ``` -To some, this choice might seem odd at first sight. If you are familiar with the topic, you likely have expected the set of formal languages to be defined more concretely as the set of all sets of finite sequences (sometimes called *words*), `iset>`. Rest assured, we agree — up to an appropriate notion of equality! Whereas you characterise languages intrinsically, we treat them extrinsically, in terms of their universal property: [it is well known](https://ir.cwi.nl/pub/28550/rutten.pdf) that `iset>` forms the *greatest* [coalgebraic](https://en.wikipedia.org/wiki/F-coalgebra) structure (think of a deterministic automaton without initial state) `S` that is equipped with functions `eps: S -> bool` and `delta: S → (A → S)`. Indeed, for any set `U` of finite sequences, we can verify whether `U` contains the empty sequence, `U.eps == ([] in U)`, and for any `a: A`, we can transition to the set `U.delta(a) == (iset s | [a] + s in U)`. Here, we choose the more abstract perspective on formal languages as it hides irrelevant specifics and thus allows us to write more elegant proofs. +Note that we used the type-parameter mode [`!`](https://dafny.org/latest/DafnyRef/DafnyRef#sec-type-parameter-variance), which indicates that there could be strictly more values of type `Lang` than values of type `A`, for any type `A`, and that there is no subtype relation between `Lang` and `Lang`, for any two types `A, B`. A more detailed explanation of the topic can be found [here](https://leino.science/papers/krml280.html). + +To some, the choice above might seem odd at first sight. If you are familiar with the topic, you likely have expected the set of formal languages to be defined more concretely as the set of all sets of finite sequences (sometimes called *words*), `iset>`. Rest assured, we agree — up to an appropriate notion of equality! Whereas you characterise languages intrinsically, we treat them extrinsically, in terms of their universal property: [it is well known](https://ir.cwi.nl/pub/28550/rutten.pdf) that `iset>` forms the *greatest* [coalgebraic](https://en.wikipedia.org/wiki/F-coalgebra) structure (think of a deterministic automaton without initial state) `S` that is equipped with functions `eps: S -> bool` and `delta: S → (A → S)`. Indeed, for any set `U` of finite sequences, we can verify whether `U` contains the empty sequence, `U.eps == ([] in U)`, and for any `a: A`, we can transition to the set `U.delta(a) == (iset s | [a] + s in U)`. Here, we choose the more abstract perspective on formal languages as it hides irrelevant specifics and thus allows us to write more elegant proofs. ### An Algebra of Formal Languages From 55c36f49c0dc3e9db35e0ece70ef72b46ea8aa65 Mon Sep 17 00:00:00 2001 From: stefan-aws Date: Wed, 3 Jan 2024 12:04:29 +0000 Subject: [PATCH 47/62] typo --- _posts/2024-01-10-semantics-of-regular-expressions.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_posts/2024-01-10-semantics-of-regular-expressions.markdown b/_posts/2024-01-10-semantics-of-regular-expressions.markdown index 1122f95..6ba5b98 100644 --- a/_posts/2024-01-10-semantics-of-regular-expressions.markdown +++ b/_posts/2024-01-10-semantics-of-regular-expressions.markdown @@ -182,7 +182,7 @@ ghost predicate IsAlgebraHomomorphismPointwise(f: Exp -> Lang, e: Exp) } ``` -Note that we used the [`ghost`](https://dafny.org/latest/DafnyRef/DafnyRef#sec-declaration-modifier) modifier (which signals that an entity is meant for specification only, not for compilation). A `greatest predicate` is always ghost, so `IsAlgebraHomomorphismPointwise` must be declared ghost to call `Bisimilar`, and `IsAlgebraHomomorphismPointwise` must be declared ghost to call `IsAlgebraHomomorphism`. +Note that we used the [`ghost`](https://dafny.org/latest/DafnyRef/DafnyRef#sec-declaration-modifier) modifier (which signals that an entity is meant for specification only, not for compilation). A `greatest predicate` is always ghost, so `IsAlgebraHomomorphismPointwise` must be declared ghost to call `Bisimilar`, and `IsAlgebraHomomorphism` must be declared ghost to call `IsAlgebraHomomorphismPointwise`. The proof that `Denotational` is an algebra homomorphism is straightforward: it essentially follows from bisimilarity being reflexive: From 4c63cb6e053b0621217ac6e701ac4883675282f0 Mon Sep 17 00:00:00 2001 From: stefan-aws Date: Wed, 3 Jan 2024 12:57:45 +0000 Subject: [PATCH 48/62] type parameter completion --- _posts/2024-01-10-semantics-of-regular-expressions.markdown | 2 ++ 1 file changed, 2 insertions(+) diff --git a/_posts/2024-01-10-semantics-of-regular-expressions.markdown b/_posts/2024-01-10-semantics-of-regular-expressions.markdown index 6ba5b98..0904e27 100644 --- a/_posts/2024-01-10-semantics-of-regular-expressions.markdown +++ b/_posts/2024-01-10-semantics-of-regular-expressions.markdown @@ -22,6 +22,8 @@ datatype Exp = Zero | One | Char(A) | Plus(Exp, Exp) | Comp(Exp, Exp) | Star(Exp) ``` +Note that above, and later, we make use of Dafny's [type parameter completion](https://leino.science/papers/krml270.html). + The definition captures that a regular expression is either a primitive character `Char(a)`, a non-deterministic choice between two regular expressions `Plus(e1, e2)`, a sequential composition of two regular expressions `Comp(e1, e2)`, a finite number of self-iterations `Star(e)`, or one of the constants `Zero` (the unit of `Plus`) and `One` (the unit of `Comp`). At a higher level, the above defines `Exp` as the *smallest* algebraic structure that is equipped with two constants, contains all elements of type `A`, and is closed under two binary operations and one unary operation. ### Formal Languages as Codatatype From 4979fd24880ade799c786edd78097ed64339bd7c Mon Sep 17 00:00:00 2001 From: stefan-aws Date: Wed, 3 Jan 2024 13:05:58 +0000 Subject: [PATCH 49/62] abstemious --- _posts/2024-01-10-semantics-of-regular-expressions.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_posts/2024-01-10-semantics-of-regular-expressions.markdown b/_posts/2024-01-10-semantics-of-regular-expressions.markdown index 0904e27..b9cd7b8 100644 --- a/_posts/2024-01-10-semantics-of-regular-expressions.markdown +++ b/_posts/2024-01-10-semantics-of-regular-expressions.markdown @@ -74,7 +74,7 @@ function Star(L: Lang): Lang { } ``` -Note that the [`{:abstemious}`](https://dafny.org/latest/DafnyRef/DafnyRef#sec-abstemious) attribute above signals that a function does not need to unfold a codatatype instance very far (perhaps just one destructor call) to prove a relevant property. Knowing this is the case can aid the proofs of properties about the function. +Note that the [`{:abstemious}`](https://dafny.org/latest/DafnyRef/DafnyRef#sec-abstemious) attribute above signals that a function does not need to unfold a codatatype instance very far (perhaps just one destructor call) to prove a relevant property. Knowing this is the case can aid the proofs of properties about the function. In this case, it is needed to convince Dafny that the corecursive calls in `Comp` and `Star` are logically consistent. ### Denotational Semantics as Induced Morphism From 8fb23efec2e63857f931d280d31b1290c5f13e34 Mon Sep 17 00:00:00 2001 From: stefan-aws Date: Wed, 3 Jan 2024 13:38:55 +0000 Subject: [PATCH 50/62] formal language --- _posts/2024-01-10-semantics-of-regular-expressions.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_posts/2024-01-10-semantics-of-regular-expressions.markdown b/_posts/2024-01-10-semantics-of-regular-expressions.markdown index b9cd7b8..fb1bed4 100644 --- a/_posts/2024-01-10-semantics-of-regular-expressions.markdown +++ b/_posts/2024-01-10-semantics-of-regular-expressions.markdown @@ -36,7 +36,7 @@ codatatype Lang = Alpha(eps: bool, delta: A -> Lang) Note that we used the type-parameter mode [`!`](https://dafny.org/latest/DafnyRef/DafnyRef#sec-type-parameter-variance), which indicates that there could be strictly more values of type `Lang` than values of type `A`, for any type `A`, and that there is no subtype relation between `Lang` and `Lang`, for any two types `A, B`. A more detailed explanation of the topic can be found [here](https://leino.science/papers/krml280.html). -To some, the choice above might seem odd at first sight. If you are familiar with the topic, you likely have expected the set of formal languages to be defined more concretely as the set of all sets of finite sequences (sometimes called *words*), `iset>`. Rest assured, we agree — up to an appropriate notion of equality! Whereas you characterise languages intrinsically, we treat them extrinsically, in terms of their universal property: [it is well known](https://ir.cwi.nl/pub/28550/rutten.pdf) that `iset>` forms the *greatest* [coalgebraic](https://en.wikipedia.org/wiki/F-coalgebra) structure (think of a deterministic automaton without initial state) `S` that is equipped with functions `eps: S -> bool` and `delta: S → (A → S)`. Indeed, for any set `U` of finite sequences, we can verify whether `U` contains the empty sequence, `U.eps == ([] in U)`, and for any `a: A`, we can transition to the set `U.delta(a) == (iset s | [a] + s in U)`. Here, we choose the more abstract perspective on formal languages as it hides irrelevant specifics and thus allows us to write more elegant proofs. +To some, the choice above might seem odd at first sight. If you are familiar with the topic, you likely have expected a formal language to be defined more concretely as a set of finite sequences (sometimes called *words*), `iset>`. Rest assured, we agree — up to an appropriate notion of equality! Whereas you characterise languages intrinsically, we treat them extrinsically, in terms of their universal property: [it is well known](https://ir.cwi.nl/pub/28550/rutten.pdf) that `iset>` forms the *greatest* [coalgebraic](https://en.wikipedia.org/wiki/F-coalgebra) structure (think of a deterministic automaton without initial state) `S` that is equipped with functions `eps: S -> bool` and `delta: S → (A → S)`. Indeed, for any set `U` of finite sequences, we can verify whether `U` contains the empty sequence, `U.eps == ([] in U)`, and for any `a: A`, we can transition to the set `U.delta(a) == (iset s | [a] + s in U)`. Here, we choose the more abstract perspective on formal languages as it hides irrelevant specifics and thus allows us to write more elegant proofs. ### An Algebra of Formal Languages From f8f2cbb7982ab1a714abcefe73ea79c1ab9c9b53 Mon Sep 17 00:00:00 2001 From: stefan-aws Date: Wed, 3 Jan 2024 13:59:40 +0000 Subject: [PATCH 51/62] consistency with source code --- ...-semantics-of-regular-expressions.markdown | 48 +++++++++++-------- .../Languages.dfy | 8 ++-- 2 files changed, 31 insertions(+), 25 deletions(-) diff --git a/_posts/2024-01-10-semantics-of-regular-expressions.markdown b/_posts/2024-01-10-semantics-of-regular-expressions.markdown index fb1bed4..f2a594b 100644 --- a/_posts/2024-01-10-semantics-of-regular-expressions.markdown +++ b/_posts/2024-01-10-semantics-of-regular-expressions.markdown @@ -66,7 +66,7 @@ function {:abstemious} Plus(L1: Lang, L2: Lang): Lang { } function {:abstemious} Comp(L1: Lang, L2: Lang): Lang { - Alpha(L1.eps && L2.eps, (a: A) => Plus(Comp(L1.delta(a), L2), Comp(if L1.eps then One() else Zero(), L2.delta(a))) ) + Alpha(L1.eps && L2.eps, (a: A) => Plus(Comp(L1.delta(a), L2), Comp(if L1.eps then One() else Zero(), L2.delta(a)))) } function Star(L: Lang): Lang { @@ -81,7 +81,7 @@ Note that the [`{:abstemious}`](https://dafny.org/latest/DafnyRef/DafnyRef#sec-a The denotational semantics of regular expressions can now be defined through induction, as a function `Denotational: Exp -> Lang`, by making use of the operations on languages we have just defined: ``` -function Denotational(e: Exp): Lang { +function Denotational(e: Exp): Lang { match e case Zero => Languages.Zero() case One => Languages.One() @@ -210,24 +210,24 @@ In [An Algebra of Formal Languages](#an-algebra-of-formal-languagesn Algebra of ``` function Eps(e: Exp): bool { - match e - case Zero => false - case One => true - case Char(a) => false - case Plus(e1, e2) => Eps(e1) || Eps(e2) - case Comp(e1, e2) => Eps(e1) && Eps(e2) - case Star(e1) => true + match e + case Zero => false + case One => true + case Char(a) => false + case Plus(e1, e2) => Eps(e1) || Eps(e2) + case Comp(e1, e2) => Eps(e1) && Eps(e2) + case Star(e1) => true } -function Delta(e: Exp): A -> Exp { - (a: A) => - match e - case Zero => Zero - case One => Zero - case Char(b) => if a == b then One else Zero - case Plus(e1, e2) => Plus(Delta(e1)(a), Delta(e2)(a)) - case Comp(e1, e2) => Plus(Comp(Delta(e1)(a), e2), Comp(if Eps(e1) then One else Zero, Delta(e2)(a))) - case Star(e1) => Comp(Delta(e1)(a), Star(e1)) +function Delta(e: Exp): A -> Exp { + (a: A) => + match e + case Zero => Zero + case One => Zero + case Char(b) => if a == b then One else Zero + case Plus(e1, e2) => Plus(Delta(e1)(a), Delta(e2)(a)) + case Comp(e1, e2) => Plus(Comp(Delta(e1)(a), e2), Comp(if Eps(e1) then One else Zero, Delta(e2)(a))) + case Star(e1) => Comp(Delta(e1)(a), Star(e1)) } ``` @@ -258,7 +258,9 @@ It is straightforward to prove that `Operational` is a coalgebra homomorphism: o lemma OperationalIsCoalgebraHomomorphism() ensures IsCoalgebraHomomorphism(Operational) { - forall e, a ensures Bisimilar(Operational(e).delta(a), Operational(Delta(e)(a))) { + forall e, a + ensures Bisimilar(Operational(e).delta(a), Operational(Delta(e)(a))) + { BisimilarityIsReflexive(Operational(e).delta(a)); } } @@ -333,7 +335,9 @@ lemma UniqueCoalgebraHomomorphismHelperPointwise(k: nat, f: Exp -> Lang { var e :| Bisimilar#[k](L1, f(e)) && Bisimilar#[k](L2, g(e)); if k != 0 { - forall a ensures Bisimilar#[k-1](L1.delta(a), L2.delta(a)) { + forall a + ensures Bisimilar#[k-1](L1.delta(a), L2.delta(a)) + { BisimilarityIsTransitivePointwise(k-1, L1.delta(a), f(e).delta(a), f(Delta(e)(a))); BisimilarityIsTransitivePointwise(k-1, L2.delta(a), g(e).delta(a), g(Delta(e)(a))); UniqueCoalgebraHomomorphismHelperPointwise(k-1, f, g, L1.delta(a), L2.delta(a)); @@ -347,7 +351,9 @@ lemma BisimilarityIsTransitivePointwise(k: nat, L1: Lang, L2: Lang, L3: if k != 0 { if Bisimilar#[k](L1, L2) && Bisimilar#[k](L2, L3) { assert Bisimilar#[k](L1, L3) by { - forall a ensures Bisimilar#[k-1](L1.delta(a), L3.delta(a)) { + forall a + ensures Bisimilar#[k-1](L1.delta(a), L3.delta(a)) + { BisimilarityIsTransitivePointwise(k-1, L1.delta(a), L2.delta(a), L3.delta(a)); } } diff --git a/assets/src/semantics-of-regular-expressions/Languages.dfy b/assets/src/semantics-of-regular-expressions/Languages.dfy index efd6aa1..790d0ad 100644 --- a/assets/src/semantics-of-regular-expressions/Languages.dfy +++ b/assets/src/semantics-of-regular-expressions/Languages.dfy @@ -24,7 +24,7 @@ module Languages { Alpha(L1.eps && L2.eps, (a: A) => Plus(Comp(L1.delta(a), L2), Comp(if L1.eps then One() else Zero(), L2.delta(a)))) } - function Star(L: Lang): Lang { + function Star(L: Lang): Lang { Alpha(true, (a: A) => Comp(L.delta(a), Star(L))) } @@ -41,17 +41,17 @@ module Languages { ensures Bisimilar(L, L) {} - lemma BisimilarityIsTransitive(L1: Lang, L2: Lang, L3: Lang) + lemma BisimilarityIsTransitiveAlternative(L1: Lang, L2: Lang, L3: Lang) ensures Bisimilar(L1, L2) && Bisimilar(L2, L3) ==> Bisimilar(L1, L3) { if Bisimilar(L1,L2) && Bisimilar(L2, L3) { assert Bisimilar(L1, L3) by { - BisimilarityIsTransitiveAlternative(L1, L2, L3); + BisimilarityIsTransitive(L1, L2, L3); } } } - greatest lemma BisimilarityIsTransitiveAlternative[nat](L1: Lang, L2: Lang, L3: Lang) + greatest lemma BisimilarityIsTransitive[nat](L1: Lang, L2: Lang, L3: Lang) requires Bisimilar(L1, L2) && Bisimilar(L2, L3) ensures Bisimilar(L1, L3) {} From 3cc70c4006758dea0cf1f7fe4ccdb62e574800f9 Mon Sep 17 00:00:00 2001 From: stefan-aws Date: Wed, 3 Jan 2024 14:03:17 +0000 Subject: [PATCH 52/62] remove variable --- _posts/2024-01-10-semantics-of-regular-expressions.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_posts/2024-01-10-semantics-of-regular-expressions.markdown b/_posts/2024-01-10-semantics-of-regular-expressions.markdown index f2a594b..c632d6c 100644 --- a/_posts/2024-01-10-semantics-of-regular-expressions.markdown +++ b/_posts/2024-01-10-semantics-of-regular-expressions.markdown @@ -243,7 +243,7 @@ function Operational(e: Exp): Lang { ### Operational Semantics as Coalgebra Homomorphism -In [Denotational Semantics as Algebra Homomorphism](#denotational-semantics-as-algebra-homomorphism) we defined algebra homomorphisms as functions `f: Exp -> Lang` that commute with the algebraic structures of regular expressions and formal languages, respectively. Analogously, let us now call a function `f` of the same type a *coalgebra homomorphism*, if it commutes with the *coalgebraic* structures of regular expressions and formal languages, respectively: +In [Denotational Semantics as Algebra Homomorphism](#denotational-semantics-as-algebra-homomorphism) we defined algebra homomorphisms as functions `f: Exp -> Lang` that commute with the algebraic structures of regular expressions and formal languages, respectively. Analogously, let us now call a function of the same type a *coalgebra homomorphism*, if it commutes with the *coalgebraic* structures of regular expressions and formal languages, respectively: ``` ghost predicate IsCoalgebraHomomorphism(f: Exp -> Lang) { From d1b7e64620e6e2835ddea3be5b51ea17efd569b2 Mon Sep 17 00:00:00 2001 From: stefan-aws Date: Wed, 3 Jan 2024 14:11:14 +0000 Subject: [PATCH 53/62] forall ensures --- _posts/2024-01-10-semantics-of-regular-expressions.markdown | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/_posts/2024-01-10-semantics-of-regular-expressions.markdown b/_posts/2024-01-10-semantics-of-regular-expressions.markdown index c632d6c..b1e51f1 100644 --- a/_posts/2024-01-10-semantics-of-regular-expressions.markdown +++ b/_posts/2024-01-10-semantics-of-regular-expressions.markdown @@ -152,7 +152,9 @@ lemma BisimilarityIsReflexive#[k: nat](L: Lang) { if k == 0 { } else { - forall a ensures Bisimilar#[k-1](L.delta(a), L.delta(a)) { + forall a + ensures Bisimilar#[k-1](L.delta(a), L.delta(a)) + { BisimilarityIsReflexive#[k-1](L.delta(a)); } } From 395cef946c5c3a49de207102837b084212c5a77a Mon Sep 17 00:00:00 2001 From: stefan-aws Date: Wed, 3 Jan 2024 14:18:42 +0000 Subject: [PATCH 54/62] forall --- assets/src/semantics-of-regular-expressions/Languages.dfy | 8 ++++++-- assets/src/semantics-of-regular-expressions/Semantics.dfy | 4 +++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/assets/src/semantics-of-regular-expressions/Languages.dfy b/assets/src/semantics-of-regular-expressions/Languages.dfy index 790d0ad..6353236 100644 --- a/assets/src/semantics-of-regular-expressions/Languages.dfy +++ b/assets/src/semantics-of-regular-expressions/Languages.dfy @@ -147,7 +147,9 @@ module Languages { assert Bisimilar#[1](L2a, L2b); assert lhs.eps == rhs.eps; - forall a ensures (Bisimilar#[k](lhs.delta(a), rhs.delta(a))) { + forall a + ensures (Bisimilar#[k](lhs.delta(a), rhs.delta(a))) + { var x1 := Comp(L1a.delta(a), L2a); var x2 := Comp(L1b.delta(a), L2b); assert Bisimilar#[k](x1, x2) by { @@ -199,7 +201,9 @@ module Languages { requires forall n: nat :: n <= k + 1 ==> Bisimilar#[n](L1, L2) ensures Bisimilar#[k+1](Star(L1), Star(L2)) { - forall a ensures Bisimilar#[k](Star(L1).delta(a), Star(L2).delta(a)) { + forall a + ensures Bisimilar#[k](Star(L1).delta(a), Star(L2).delta(a)) + { if k != 0 { BisimilarCuttingPrefixesPointwise(k, a, L1, L2); var k' :| k == k' + 1; diff --git a/assets/src/semantics-of-regular-expressions/Semantics.dfy b/assets/src/semantics-of-regular-expressions/Semantics.dfy index c3be654..c769762 100644 --- a/assets/src/semantics-of-regular-expressions/Semantics.dfy +++ b/assets/src/semantics-of-regular-expressions/Semantics.dfy @@ -194,7 +194,9 @@ module Semantics { lemma OperationalIsAlgebraHomomorphism() ensures IsAlgebraHomomorphism(Operational) { - forall e ensures IsAlgebraHomomorphismPointwise(Operational, e) { + forall e + ensures IsAlgebraHomomorphismPointwise(Operational, e) + { OperationalAndDenotationalAreBisimilar(e); assert IsAlgebraHomomorphismPointwise(Denotational, e) by { DenotationalIsAlgebraHomomorphism(); From 7c4339ea8e0ddb6264ff5ce760b4688105c82283 Mon Sep 17 00:00:00 2001 From: stefan-aws Date: Wed, 3 Jan 2024 14:22:30 +0000 Subject: [PATCH 55/62] spacing --- assets/src/semantics-of-regular-expressions/Languages.dfy | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/assets/src/semantics-of-regular-expressions/Languages.dfy b/assets/src/semantics-of-regular-expressions/Languages.dfy index 6353236..a04faa8 100644 --- a/assets/src/semantics-of-regular-expressions/Languages.dfy +++ b/assets/src/semantics-of-regular-expressions/Languages.dfy @@ -90,7 +90,7 @@ module Languages { } } - lemma BisimilarCuttingPrefixesPointwise(k: nat, a: A, L1a: Lang, L1b: Lang) + lemma BisimilarCuttingPrefixesPointwise(k: nat, a: A, L1a: Lang, L1b: Lang) requires k != 0 requires forall n: nat :: n <= k + 1 ==> Bisimilar#[n](L1a, L1b) ensures forall n: nat :: n <= k ==> Bisimilar#[n](L1a.delta(a), L1b.delta(a)) @@ -126,7 +126,7 @@ module Languages { ensures Bisimilar(Comp(L1a, L2a), Comp(L1b, L2b)) { forall k: nat - ensures Bisimilar#[k](Comp(L1a,L2a), Comp(L1b,L2b)) + ensures Bisimilar#[k](Comp(L1a, L2a), Comp(L1b, L2b)) { if k != 0 { var k' :| k' + 1 == k; @@ -212,7 +212,7 @@ module Languages { { if 0 < n <= k' + 1 { var n' :| n == n' + 1; - StarCongruenceHelper(n', L1,L2); + StarCongruenceHelper(n', L1, L2); } } CompCongruenceHelper(k', L1.delta(a), L2.delta(a), Star(L1), Star(L2)); From 62f320d23f6f7907a61b748b9b61252f711ce9b3 Mon Sep 17 00:00:00 2001 From: stefan-aws Date: Wed, 3 Jan 2024 14:27:18 +0000 Subject: [PATCH 56/62] verificaiton --- Makefile | 1 + .../verify.sh | 20 +++++++++++++++++++ 2 files changed, 21 insertions(+) create mode 100755 assets/src/semantics-of-regular-expressions/verify.sh diff --git a/Makefile b/Makefile index 5f3023e..f120b95 100644 --- a/Makefile +++ b/Makefile @@ -14,6 +14,7 @@ check: assets/src/test-generation/verify.sh assets/src/insertion-sort/verify.sh assets/src/proof-dependencies/verify.sh + assets/src/semantics-of-regular-expressions/verify.sh generate: node builders/verification-compelling-verify.js --regenerate _includes/verification-compelling-intro.html diff --git a/assets/src/semantics-of-regular-expressions/verify.sh b/assets/src/semantics-of-regular-expressions/verify.sh new file mode 100755 index 0000000..cb64eb9 --- /dev/null +++ b/assets/src/semantics-of-regular-expressions/verify.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +set -e + +if command -v dafny > /dev/null 2>&1 +then + echo + echo "*** Verification of the Well-Behaved (Co)algebraic Semantics of Regular Expressions in Dafny blog post" +else + echo "Verification requires dafny to be installed" + exit 1 +fi + +cd "$(dirname "$0")" + +for file in `ls *.dfy` +do + echo "Verifying $file..." + dafny verify $file +done \ No newline at end of file From 460ba932b6de49e7dcff74a7cb041b79c16607ee Mon Sep 17 00:00:00 2001 From: Stefan Zetzsche <120379523+stefan-aws@users.noreply.github.com> Date: Thu, 4 Jan 2024 00:06:20 +0000 Subject: [PATCH 57/62] Update _posts/2024-01-10-semantics-of-regular-expressions.markdown Co-authored-by: Aaron Tomb --- _posts/2024-01-10-semantics-of-regular-expressions.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_posts/2024-01-10-semantics-of-regular-expressions.markdown b/_posts/2024-01-10-semantics-of-regular-expressions.markdown index b1e51f1..37f4778 100644 --- a/_posts/2024-01-10-semantics-of-regular-expressions.markdown +++ b/_posts/2024-01-10-semantics-of-regular-expressions.markdown @@ -28,7 +28,7 @@ The definition captures that a regular expression is either a primitive characte ### Formal Languages as Codatatype -We define the set of formal languages parametric in an alphabet `A` as an coinductive [`codatatype`](https://dafny.org/latest/DafnyRef/DafnyRef#sec-coinductive-datatypes): +We define the set of formal languages parametric in an alphabet `A` as a coinductive [`codatatype`](https://dafny.org/latest/DafnyRef/DafnyRef#sec-coinductive-datatypes): ``` codatatype Lang = Alpha(eps: bool, delta: A -> Lang) From fd0dbc26d342d0fc202a4a76c50343a33c7c644c Mon Sep 17 00:00:00 2001 From: stefan-aws Date: Thu, 4 Jan 2024 00:12:41 +0000 Subject: [PATCH 58/62] indent --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index a57cdc3..b0b5097 100644 --- a/Makefile +++ b/Makefile @@ -17,7 +17,7 @@ check: assets/src/brittleness/verify.sh assets/src/teaching-material/verify.sh assets/src/standard-libraries/test.sh - assets/src/semantics-of-regular-expressions/verify.sh + assets/src/semantics-of-regular-expressions/verify.sh (cd assets/src/clear-specification-and-implementation && ./verify.sh) generate: From fe3b31a323666dce61cdfc083ff8bdb754117a67 Mon Sep 17 00:00:00 2001 From: stefan-aws Date: Wed, 10 Jan 2024 13:39:18 +0000 Subject: [PATCH 59/62] madoko --- .../semantics-of-regular-expressions.html | 672 ++++++++++++++++++ ...-semantics-of-regular-expressions.markdown | 403 +---------- .../mdk/semantics-of-regular-expressions.mdk | 217 ++++++ .../Expressions.dfy | 10 +- .../Languages.dfy | 213 +++--- .../Semantics.dfy | 352 +++++---- 6 files changed, 1250 insertions(+), 617 deletions(-) create mode 100644 _includes/semantics-of-regular-expressions.html create mode 100644 assets/mdk/semantics-of-regular-expressions.mdk diff --git a/_includes/semantics-of-regular-expressions.html b/_includes/semantics-of-regular-expressions.html new file mode 100644 index 0000000..a970dc7 --- /dev/null +++ b/_includes/semantics-of-regular-expressions.html @@ -0,0 +1,672 @@ + + + + + + + + + + + +
+

Introduction

+

Regular expressions are one of the most ubiquitous formalisms of theoretical computer science. Commonly, they are understood in terms of their denotational semantics, that is, through formal languages — the regular languages. This view is inductive in nature: two primitives are equivalent if they are constructed in the same way. Alternatively, regular expressions can be understood in terms of their operational semantics, that is, through finite automata. This view is coinductive in nature: two primitives are equivalent if they are deconstructed in the same way. It is implied by Kleene’s famous theorem that both views are equivalent: regular languages are precisely the formal languages accepted by finite automata. In this blogpost, we utilise Dafny’s built-in inductive and coinductive reasoning capabilities to show that the two semantics of regular expressions are well-behaved, in the sense they are in fact one and the same, up to pointwise bisimulation. +

Denotational Semantics

+

In this section, we define regular expressions and formal languages, introduce the concept of bisimilarity, formalise the denotational semantics of regular expressions as a function from regular expressions to formal languages, and prove that the latter is an algebra homomorphism. +

Regular Expressions as Datatype

+

We define the set of regular expressions parametric in an alphabet A as an inductive datatype: +

+ + + +
  datatype Exp<A> = Zero | One | Char(A) | Plus(Exp, Exp) | Comp(Exp, Exp) | Star(Exp)
+

Note that above, and later, we make use of Dafny's type parameter completion. +

+

The definition captures that a regular expression is either a primitive character Char(a), a non-deterministic choice between two regular expressions Plus(e1, e2), a sequential composition of two regular expressions Comp(e1, e2), a finite number of self-iterations Star(e), or one of the constants Zero (the unit of Plus) and One (the unit of Comp). At a higher level, the above defines Exp<A> as the smallest algebraic structure that is equipped with two constants, contains all elements of type A, and is closed under two binary operations and one unary operation. +

Formal Languages as Codatatype

+

We define the set of formal languages parametric in an alphabet A as a coinductive codatatype: +

+ + + +
  codatatype Lang<!A> = Alpha(eps: bool, delta: A -> Lang<A>)
+

Note that we used the type-parameter mode !, which indicates that there could be strictly more values of type Lang<A> than values of type A, for any type A, and that there is no subtype relation between Lang<A> and Lang<B>, for any two types A, B. A more detailed explanation of the topic can be found here. +

+

To some, the choice above might seem odd at first sight. If you are familiar with the topic, you likely have expected a formal language to be defined more concretely as a set of finite sequences (sometimes called words), iset<seq<A>>. Rest assured, we agree — up to an appropriate notion of equality! Whereas you characterise languages intrinsically, we treat them extrinsically, in terms of their universal property: it is well known that iset<seq<A>> forms the greatest coalgebraic structure (think of a deterministic automaton without initial state) S that is equipped with functions eps: S -> bool and delta: S → (A → S). Indeed, for any set U of finite sequences, we can verify whether U contains the empty sequence, U.eps == ([] in U), and for any a: A, we can transition to the set U.delta(a) == (iset s | [a] + s in U). Here, we choose the more abstract perspective on formal languages as it hides irrelevant specifics and thus allows us to write more elegant proofs. +

An Algebra of Formal Languages

+

If you think of formal languages as the set of all sets of finite sequences, you will soon realise that languages admit quite a bit of algebraic structure. For example, there exist two languages of distinct importance (can you already guess which ones?), and one can obtain a new language by taking e.g. the union of two languages. In fact, if you think about it for a bit longer, you’ll realise that formal languages admit exactly the same type of algebraic structure as the one you’ve encountered when we defined regular expressions! +

+

First, there exists the empty language Zero() that contains no words at all. Under the view above, we find Zero().eps == false and Zero().delta(a) == Zero(), since the empty set does not contain the empty sequence, and the derivative iset s | [a] + s in iset{} with respect to any a: A yields again the empty set, respectively. We thus define: +

+ + + +
  function Zero<A>(): Lang {
+    Alpha(false, (a: A) => Zero())
+  }
+

Using similar reasoning, we additionally derive the following definitions. In order, we formalise i) the language One() that contains only the empty sequence; ii) for any a: A the language Singleton(a) that consists of only the word [a]; iii) the language Plus(L1, L2) which consists of the union of the languages L1 and L2; iv) the language Comp(L1, L2) that consists of all possible concatenation of words in L1 and L2; and v) the language Star(L) that consists of all finite compositions of L with itself. Our definitions match what is well-known as Brzozowski derivatives. +

+ + + +
  function One<A>(): Lang {
+    Alpha(true, (a: A) => Zero())
+  }
+
+  function Singleton<A(==)>(a: A): Lang {
+    Alpha(false, (b: A) => if a == b then One() else Zero())
+  }
+
+  function {:abstemious} Plus<A>(L1: Lang, L2: Lang): Lang {
+    Alpha(L1.eps || L2.eps, (a: A) => Plus(L1.delta(a), L2.delta(a)))
+  }
+
+  function {:abstemious} Comp<A>(L1: Lang, L2: Lang): Lang {
+    Alpha(
+      L1.eps && L2.eps,
+      (a: A) => Plus(Comp(L1.delta(a), L2), Comp(if L1.eps then One() else Zero(), L2.delta(a)))
+    )
+  }
+
+  function Star<A>(L: Lang): Lang {
+    Alpha(true, (a: A) => Comp(L.delta(a), Star(L)))
+  }
+

Note that the {:abstemious} attribute above signals that a function does not need to unfold a codatatype instance very far (perhaps just one destructor call) to prove a relevant property. Knowing this is the case can aid the proofs of properties about the function. In this case, it is needed to convince Dafny that the corecursive calls in Comp and Star are logically consistent. +

Denotational Semantics as Induced Morphism

+

The denotational semantics of regular expressions can now be defined through induction, as a function Denotational: Exp -> Lang, by making use of the operations on languages we have just defined: +

+ + + +
  function Denotational<A(==)>(e: Exp): Lang {
+    match e
+    case Zero => Languages1.Zero()
+    case One => Languages2.One()
+    case Char(a) => Languages2.Singleton(a)
+    case Plus(e1, e2) => Languages2.Plus(Denotational(e1), Denotational(e2))
+    case Comp(e1, e2) => Languages2.Comp(Denotational(e1), Denotational(e2))
+    case Star(e1) => Languages2.Star(Denotational(e1))
+  }

Bisimilarity and Coinduction

+

Let us briefly introduce a notion of equality between formal languages that will be useful soon. A binary relation R between languages is called a bisimulation, if for any two languages L1, L2 related by R the following holds: i) L1 contains the empty word iff L2 does; and ii) for any a: A, the derivatives L1.delta(a) and L2.delta(a) are again related by R. As it turns out, the union of two bisimulations is again a bisimulation. In consequence, one can combine all possible bisimulations into a single relation: the greatest bisimulation. In Dafny, we can formalise the latter as a greatest predicate: +

+ + + +
  greatest predicate Bisimilar<A(!new)>[nat](L1: Lang, L2: Lang) {
+    && (L1.eps == L2.eps)
+    && (forall a :: Bisimilar(L1.delta(a), L2.delta(a)))
+  }
+

It is instructive to think of a greatest predicate as pure syntactic sugar. Indeed, under the hood, Dafny’s compiler uses the body above to implicitly generate i) for any k: nat, a prefix predicate Bisimilar#[k](L1, L2) that signifies that the languages L1 and L2 concur on the first k-unrollings of the definition above; and ii) a predicate Bisimilar(L1, L2) that is true iff Bisimilar#[k](L1, L2) is true for all k: nat: +

+
  /* Pseudo code for illustration purposes */
+
+  predicate Bisimilar<A(!new)>(L1: Lang, L2: Lang) {
+    forall k: nat :: Bisimilar#[k](L1, L2)
+  }
+
+  predicate Bisimilar#<A(!new)>[k: nat](L1: Lang, L2: Lang) 
+    decreases k
+  {
+    if k == 0 then
+      true
+    else
+      && (L1.eps == L2.eps)
+      && (forall a :: Bisimilar#[k-1](L1.delta(a), L2.delta(a)))
+  }
+

Now that we have its definition in place, let us establish a property about bisimilarity, say, that it is a reflexive relation. With the greatest lemma construct, Dafny is able to able to derive a proof completely on its own: +

+ + + +
  greatest lemma BisimilarityIsReflexive<A(!new)>[nat](L: Lang)
+    ensures Bisimilar(L, L)
+  {}
+

Once again, it is instructive to think of a greatest lemma as pure syntactic sugar. Under the hood, Dafny’s compiler uses the body of the greatest lemma to generate i) for any k: nat, a prefix lemma BisimilarityIsReflexive#[k](L) that ensures the prefix predicate Bisimilar#[k](L, L); and ii) a lemma BisimilarityIsReflexive(L) that ensures Bisimilar(L, L) by calling Bisimilar#[k](L, L) for all k: nat: +

+
  /* Pseudo code for illustration purposes */
+
+  lemma BisimilarityIsReflexive<A(!new)>(L: Lang) 
+    ensures Bisimilar(L, L)
+  {
+    forall k: nat
+      ensures Bisimilar#[k](L, L)
+    {
+      BisimilarityIsReflexive#[k](L);
+    }
+  }
+
+  lemma BisimilarityIsReflexive#<A(!new)>[k: nat](L: Lang)
+    ensures Bisimilar#[k](L, L)
+    decreases k
+  {
+    if k == 0 {
+    } else {
+      forall a
+        ensures Bisimilar#[k-1](L.delta(a), L.delta(a)) 
+      {
+        BisimilarityIsReflexive#[k-1](L.delta(a));
+      }
+    }
+  } 
+

If you are interested in the full details, we recommend taking a look at this note on coinduction, predicates, and ordinals. +

Denotational Semantics as Algebra Homomorphism

+

For a moment, consider the function var f: nat -> nat := (n: nat) => n + n which maps a natural number to twice its value. The function f is structure-preserving: for any m: nat we have f(m * n) == m * f(n), i.e. f commutes with the (left-)multiplication of naturals. In this section, we are interested in functions of type f: Exp -> Lang (more precisely, in Denotational: Exp -> Lang) that commute with the algebraic structures we encountered in Regular Expressions as Datatype and An Algebra of Formal Languages, respectively. We call such structure-preserving functions algebra homomorphisms. To define pointwise commutativity in this context, we’ll have to be able to compare languages for equality. As you probably guessed, bisimilarity will do the job: +

+ + + +
  ghost predicate IsAlgebraHomomorphism<A(!new)>(f: Exp -> Lang) {
+    forall e :: IsAlgebraHomomorphismPointwise(f, e)
+  }
+
+  ghost predicate IsAlgebraHomomorphismPointwise<A(!new)>(f: Exp -> Lang, e: Exp) {
+    Bisimilar<A>(
+      f(e),
+      match e
+      case Zero => Languages1.Zero()
+      case One => Languages2.One()
+      case Char(a) => Languages2.Singleton(a)
+      case Plus(e1, e2) => Languages2.Plus(f(e1), f(e2))
+      case Comp(e1, e2) => Languages2.Comp(f(e1), f(e2))
+      case Star(e1) => Languages2.Star(f(e1))
+    )
+  }
+

Note that we used the ghost modifier (which signals that an entity is meant for specification only, not for compilation). A greatest predicate is always ghost, so IsAlgebraHomomorphismPointwise must be declared ghost to call Bisimilar, and IsAlgebraHomomorphism must be declared ghost to call IsAlgebraHomomorphismPointwise. +

+

The proof that Denotational is an algebra homomorphism is straightforward: it essentially follows from bisimilarity being reflexive: +

+ + + +
  lemma DenotationalIsAlgebraHomomorphism<A(!new)>()
+    ensures IsAlgebraHomomorphism<A>(Denotational)
+  {
+    forall e
+      ensures IsAlgebraHomomorphismPointwise<A>(Denotational, e)
+    {
+      BisimilarityIsReflexive<A>(Denotational(e));
+    }
+  }

Operational Semantics

+

In this section, we provide an alternative perspective on the semantics of regular expressions. We equip the set of regular expressions with a coalgebraic structure, formalise its operational semantics as a function from regular expressions to formal languages, and prove that the latter is a coalgebra homomorphism. +

A Coalgebra of Regular Expressions

+

In An Algebra of Formal Languages we equipped the set of formal languages with an algebraic structure that resembles the one of regular expressions. Now, we are aiming for the reverse: we would like to equip the set of regular expressions with a coalgebraic structure that resembles the one of formal languages. More concretely, we would like to turn the set of regular expressions into a deterministic automaton (without initial state) in which a state e is i) accepting iff Eps(e) == true and ii) transitions to a state Delta(e)(a) if given the input a: A. Note how our definitions resemble the Brzozowski derivatives we previously encountered: +

+ + + +
  function Eps<A>(e: Exp): bool {
+    match e
+    case Zero => false
+    case One => true
+    case Char(a) => false
+    case Plus(e1, e2) => Eps(e1) || Eps(e2)
+    case Comp(e1, e2) => Eps(e1) && Eps(e2)
+    case Star(e1) => true
+  }
+
+  function Delta<A(==)>(e: Exp): A -> Exp {
+    (a: A) =>
+      match e
+      case Zero => Zero
+      case One => Zero
+      case Char(b) => if a == b then One else Zero
+      case Plus(e1, e2) => Plus(Delta(e1)(a), Delta(e2)(a))
+      case Comp(e1, e2) => Plus(Comp(Delta(e1)(a), e2), Comp(if Eps(e1) then One else Zero, Delta(e2)(a)))
+      case Star(e1) => Comp(Delta(e1)(a), Star(e1))
+  }
+}

Operational Semantics as Induced Morphism

+

The operational semantics of regular expressions can now be defined via coinduction, as a function Operational: Exp -> Lang, by making use of the coalgebraic structure on expressions we have just defined: +

+ + + +
  function Operational<A(==)>(e: Exp): Lang {
+    Alpha(Eps(e), (a: A) => Operational(Delta(e)(a)))
+  }

Operational Semantics as Coalgebra Homomorphism

+

In Denotational Semantics as Algebra Homomorphism we defined algebra homomorphisms as functions f: Exp -> Lang that commute with the algebraic structures of regular expressions and formal languages, respectively. Analogously, let us now call a function of the same type a coalgebra homomorphism, if it commutes with the coalgebraic structures of regular expressions and formal languages, respectively: +

+ + + +
  ghost predicate IsCoalgebraHomomorphism<A(!new)>(f: Exp -> Lang) {
+    && (forall e :: f(e).eps == Eps(e))
+    && (forall e, a :: Bisimilar(f(e).delta(a), f(Delta(e)(a))))
+  }
+

It is straightforward to prove that Operational is a coalgebra homomorphism: once again, the central argument is that bisimilarity is a reflexive relation. +

+ + + +
  lemma OperationalIsCoalgebraHomomorphism<A(!new)>()
+    ensures IsCoalgebraHomomorphism<A>(Operational)
+  {
+    forall e, a
+      ensures Bisimilar<A>(Operational(e).delta(a), Operational(Delta(e)(a)))
+    {
+      BisimilarityIsReflexive(Operational(e).delta(a));
+    }
+  }

Well-Behaved Semantics

+

So far, we have seen two dual approaches for assigning a formal language semantics to regular expressions: +

+
    +
  • Denotational: an algebra homomorphism obtained via induction +
  • +
  • Operational: a coalgebra homomorphism obtained via coinduction +
+ +

Next, we show that the denotational and operational semantics of regular expressions are well-behaved: they constitute two sides of the same coin. First, we show that Denotational is also a coalgebra homomorphism, and that coalgebra homomorphisms are unique up to bisimulation. We then deduce from the former that Denotational and Operational coincide pointwise, up to bisimulation. Finally, we show that Operational is also an algebra homomorphism. +

Denotational Semantics as Coalgebra Homomorphism

+

In this section, we establish that Denotational not only commutes with the algebraic structures of regular expressions and formal languages, but also with their coalgebraic structures: +

+ + + +
  lemma DenotationalIsCoalgebraHomomorphism<A(!new)>()
+    ensures IsCoalgebraHomomorphism<A>(Denotational)
+

The proof of the lemma is a bit more elaborate than the ones we have encountered so far. It can be divided into two subproofs, both of which make use of induction. One of the subproofs is straightforward, the other, more difficult one, again uses the reflexivity of bisimilarity, but also that the latter is a congruence relation with respect to Plus and Comp: +

+ + + +
  greatest lemma PlusCongruence<A(!new)>[nat](L1a: Lang, L1b: Lang, L2a: Lang, L2b: Lang)
+    requires Bisimilar(L1a, L1b)
+    requires Bisimilar(L2a, L2b)
+    ensures Bisimilar(Plus(L1a, L2a), Plus(L1b, L2b))
+  {}
+
+  lemma CompCongruence<A(!new)>(L1a: Lang, L1b: Lang, L2a: Lang, L2b: Lang)
+    requires Bisimilar(L1a, L1b)
+    requires Bisimilar(L2a, L2b)
+    ensures Bisimilar(Comp(L1a, L2a), Comp(L1b, L2b))
+

Dafny is able to prove PlusCongruence on its own, as it can take advantage of the syntactic sugaring of the greatest lemma construct. For CompCongruence we have to put in a bit of manual work ourselves. +

Coalgebra Homomorphisms Are Unique

+

The aim of this section is to show that, up to pointwise bisimilarity, there only exists one coalgebra homomorphism of type Exp -> Lang: +

+ + + +
  lemma UniqueCoalgebraHomomorphism<A(!new)>(f: Exp -> Lang, g: Exp -> Lang, e: Exp)
+    requires IsCoalgebraHomomorphism(f)
+    requires IsCoalgebraHomomorphism(g)
+    ensures Bisimilar(f(e), g(e))
+

As is well-known, the statement may in fact be strengthened to: for any coalgebra C there exists exactly one coalgebra homomorphism of type C -> Lang , up to pointwise bisimulation. For our purposes, the weaker statement above will be sufficient. At the heart of the proof lies the observation that bisimilarity is transitive: +

+ + + +
  greatest lemma BisimilarityIsTransitive<A>[nat](L1: Lang, L2: Lang, L3: Lang)
+    requires Bisimilar(L1, L2) && Bisimilar(L2, L3)
+    ensures Bisimilar(L1, L3)
+  {}
+

In fact, in practice, we actually use a slightly more fine grained formalisation of transitivity, as is illustrated below by the proof of UniqueCoalgebraHomomorphismHelperPointwise, which is used in the proof of UniqueCoalgebraHomomorphism: +

+ + + +
  lemma UniqueCoalgebraHomomorphismHelperPointwise<A(!new)>(k: nat, f: Exp -> Lang, g: Exp -> Lang, L1: Lang, L2: Lang)
+    requires IsCoalgebraHomomorphism(f)
+    requires IsCoalgebraHomomorphism(g)
+    requires exists e :: Bisimilar#[k](L1, f(e)) && Bisimilar#[k](L2, g(e))
+    ensures Bisimilar#[k](L1, L2)
+  {
+    var e :| Bisimilar#[k](L1, f(e)) && Bisimilar#[k](L2, g(e));
+    if k != 0 {
+      forall a
+        ensures Bisimilar#[k-1](L1.delta(a), L2.delta(a))
+      {
+        BisimilarityIsTransitivePointwise(k-1, L1.delta(a),  f(e).delta(a), f(Delta(e)(a)));
+        BisimilarityIsTransitivePointwise(k-1, L2.delta(a),  g(e).delta(a), g(Delta(e)(a)));
+        UniqueCoalgebraHomomorphismHelperPointwise(k-1, f, g, L1.delta(a), L2.delta(a));
+      }
+    }
+  }
+
+  lemma BisimilarityIsTransitivePointwise<A(!new)>(k: nat, L1: Lang, L2: Lang, L3: Lang)
+    ensures Bisimilar#[k](L1, L2) && Bisimilar#[k](L2, L3) ==> Bisimilar#[k](L1, L3)
+  {
+    if k != 0 {
+      if Bisimilar#[k](L1, L2) && Bisimilar#[k](L2, L3) {
+        assert Bisimilar#[k](L1, L3) by {
+          forall a
+            ensures Bisimilar#[k-1](L1.delta(a), L3.delta(a))
+          {
+            BisimilarityIsTransitivePointwise(k-1, L1.delta(a), L2.delta(a), L3.delta(a));
+          }
+        }
+      }
+    }
+  }

Denotational and Operational Semantics Are Bisimilar

+

We are done! From the previous results, we can immediately deduce that denotational and operational semantics are the same, up to pointwise bisimilarity: +

+ + + +
  lemma OperationalAndDenotationalAreBisimilar<A(!new)>(e: Exp)
+    ensures Bisimilar<A>(Operational(e), Denotational(e))
+  {
+    OperationalIsCoalgebraHomomorphism<A>();
+    DenotationalIsCoalgebraHomomorphism<A>();
+    UniqueCoalgebraHomomorphism<A>(Operational, Denotational, e);
+  }

Operational Semantics as Algebra Homomorphism

+

As a bonus, for the sake of symmetry, let us also prove that Operational is an algebra homomorphism. (We already know that it is a coalgebra homomorphism, and that Denotational is both an algebra and coalgebra homomorphism.) +

+ + + +
  lemma OperationalIsAlgebraHomomorphism<A(!new)>()
+    ensures IsAlgebraHomomorphism<A>(Operational)
+

The idea of the proof is to take advantage of Denotational being an algebra homomorphism, by translating its properties to Operational via the lemma in Denotational and Operational Semantics Are Bisimilar. The relevant new statements capture that bisimilarity is symmetric and a congruence with respect to the Star operation: +

+ + + +
  greatest lemma BisimilarityIsSymmetric<A(!new)>[nat](L1: Lang, L2: Lang)
+    ensures Bisimilar(L1, L2) ==> Bisimilar(L2, L1)
+    ensures Bisimilar(L1, L2) <== Bisimilar(L2, L1)
+  {}
+
+  lemma StarCongruence<A(!new)>(L1: Lang, L2: Lang)
+    requires Bisimilar(L1, L2)
+    ensures Bisimilar(Star(L1), Star(L2))

Conclusion

+

We have used Dafny’s built-in inductive and coinductive reasoning capabilities to define two language semantics for regular expressions: denotational and operational semantics. Through a number of dualities — construction and deconstruction, algebras and coalgebras, and congruence and bisimilarity — we have proven the semantics to be two sides of the same coin. The blogpost is inspired by research in the field of Coalgebra, which was pioneered by Rutten, Gumm, and others. The concept of well-behaved semantics goes back to Turi and Plotkin and was adapted by Jacobs to the case of regular expressions. We heavily used automata theoretic constructions from the 1960s, originally investigated by Brzozowski (a more modern presentation can be found e.g. here). Our presentation focused on the most important intuitive aspects of the proofs. To dive deep, please take a look at the full Dafny source code, which is available here, here, and here. +

+ + + diff --git a/_posts/2024-01-10-semantics-of-regular-expressions.markdown b/_posts/2024-01-10-semantics-of-regular-expressions.markdown index 37f4778..5795659 100644 --- a/_posts/2024-01-10-semantics-of-regular-expressions.markdown +++ b/_posts/2024-01-10-semantics-of-regular-expressions.markdown @@ -1,407 +1,8 @@ --- layout: post title: "Well-Behaved (Co)algebraic Semantics of Regular Expressions in Dafny" -date: 2024-01-10 18:00:00 +0100 author: Stefan Zetzsche and Wojciech Różowski +date: 2024-01-10 18:00:00 +0100 --- -## Introduction - -[Regular expressions](https://en.wikipedia.org/wiki/Regular_expression) are one of the most ubiquitous formalisms of theoretical computer science. Commonly, they are understood in terms of their [*denotational* semantics](https://en.wikipedia.org/wiki/Denotational_semantics), that is, through formal languages — the [*regular* languages](https://en.wikipedia.org/wiki/Regular_language). This view is *inductive* in nature: two primitives are equivalent if they are *constructed* in the same way. Alternatively, regular expressions can be understood in terms of their [*operational* semantics](https://en.wikipedia.org/wiki/Operational_semantics), that is, through [finite automata](https://en.wikipedia.org/wiki/Finite-state_machine). This view is *coinductive* in nature: two primitives are equivalent if they are *deconstructed* in the same way. It is implied by [Kleene’s famous theorem](http://www.dlsi.ua.es/~mlf/nnafmc/papers/kleene56representation.pdf) that both views are equivalent: regular languages are precisely the formal languages accepted by finite automata. In this blogpost, we utilise Dafny’s built-in inductive and coinductive reasoning capabilities to show that the two semantics of regular expressions are *[well-behaved](https://homepages.inf.ed.ac.uk/gdp/publications/Math_Op_Sem.pdf)*, in the sense they are in fact one and the same, up to pointwise [bisimulation](https://en.wikipedia.org/wiki/Bisimulation). - -## Denotational Semantics - -In this section, we define regular expressions and formal languages, introduce the concept of bisimilarity, formalise the *denotational* semantics of regular expressions as a function from regular expressions to formal languages, and prove that the latter is an algebra homomorphism. - -### Regular Expressions as Datatype - -We define the set of regular expressions parametric in an alphabet `A` as an inductive [`datatype`](https://dafny.org/latest/DafnyRef/DafnyRef#sec-inductive-datatypes): - -``` -datatype Exp = Zero | One | Char(A) | Plus(Exp, Exp) | - Comp(Exp, Exp) | Star(Exp) -``` - -Note that above, and later, we make use of Dafny's [type parameter completion](https://leino.science/papers/krml270.html). - -The definition captures that a regular expression is either a primitive character `Char(a)`, a non-deterministic choice between two regular expressions `Plus(e1, e2)`, a sequential composition of two regular expressions `Comp(e1, e2)`, a finite number of self-iterations `Star(e)`, or one of the constants `Zero` (the unit of `Plus`) and `One` (the unit of `Comp`). At a higher level, the above defines `Exp` as the *smallest* algebraic structure that is equipped with two constants, contains all elements of type `A`, and is closed under two binary operations and one unary operation. - -### Formal Languages as Codatatype - -We define the set of formal languages parametric in an alphabet `A` as a coinductive [`codatatype`](https://dafny.org/latest/DafnyRef/DafnyRef#sec-coinductive-datatypes): - -``` -codatatype Lang = Alpha(eps: bool, delta: A -> Lang) -``` - -Note that we used the type-parameter mode [`!`](https://dafny.org/latest/DafnyRef/DafnyRef#sec-type-parameter-variance), which indicates that there could be strictly more values of type `Lang` than values of type `A`, for any type `A`, and that there is no subtype relation between `Lang` and `Lang`, for any two types `A, B`. A more detailed explanation of the topic can be found [here](https://leino.science/papers/krml280.html). - -To some, the choice above might seem odd at first sight. If you are familiar with the topic, you likely have expected a formal language to be defined more concretely as a set of finite sequences (sometimes called *words*), `iset>`. Rest assured, we agree — up to an appropriate notion of equality! Whereas you characterise languages intrinsically, we treat them extrinsically, in terms of their universal property: [it is well known](https://ir.cwi.nl/pub/28550/rutten.pdf) that `iset>` forms the *greatest* [coalgebraic](https://en.wikipedia.org/wiki/F-coalgebra) structure (think of a deterministic automaton without initial state) `S` that is equipped with functions `eps: S -> bool` and `delta: S → (A → S)`. Indeed, for any set `U` of finite sequences, we can verify whether `U` contains the empty sequence, `U.eps == ([] in U)`, and for any `a: A`, we can transition to the set `U.delta(a) == (iset s | [a] + s in U)`. Here, we choose the more abstract perspective on formal languages as it hides irrelevant specifics and thus allows us to write more elegant proofs. - -### An Algebra of Formal Languages - -If you think of formal languages as the set of all sets of finite sequences, you will soon realise that languages admit quite a bit of algebraic structure. For example, there exist two languages of distinct importance (can you already guess which ones?), and one can obtain a new language by taking e.g. the union of two languages. In fact, if you think about it for a bit longer, you’ll realise that formal languages admit exactly the same [type of algebraic structure](https://en.wikipedia.org/wiki/F-algebra) as the one you’ve encountered when we defined regular expressions! - -First, there exists the empty language `Zero()` that contains no words at all. Under the view above, we find `Zero().eps == false` and `Zero().delta(a) == Zero()`, since the empty set does not contain the empty sequence, and the derivative `iset s | [a] + s in iset{}` with respect to any `a: A` yields again the empty set, respectively. We thus define: - -``` -function Zero(): Lang { - Alpha(false, (a: A) => Zero()) -} -``` - -Using similar reasoning, we additionally derive the following definitions. In order, we formalise i) the language `One()` that contains only the empty sequence; ii) for any `a: A` the language `Singleton(a)` that consists of only the word `[a]`; iii) the language `Plus(L1, L2)` which consists of the union of the languages `L1` and `L2`; iv) the language `Comp(L1, L2)` that consists of all possible concatenation of words in `L1` and `L2`; and v) the language `Star(L)` that consists of all finite compositions of `L` with itself. Our definitions match what is well-known as *[Brzozowski derivatives](https://en.wikipedia.org/wiki/Brzozowski_derivative)*. - -``` -function One(): Lang { - Alpha(true, (a: A) => Zero()) -} - -function Singleton(a: A): Lang { - Alpha(false, (b: A) => if a == b then One() else Zero()) -} - -function {:abstemious} Plus(L1: Lang, L2: Lang): Lang { - Alpha(L1.eps || L2.eps, (a: A) => Plus(L1.delta(a), L2.delta(a))) -} - -function {:abstemious} Comp(L1: Lang, L2: Lang): Lang { - Alpha(L1.eps && L2.eps, (a: A) => Plus(Comp(L1.delta(a), L2), Comp(if L1.eps then One() else Zero(), L2.delta(a)))) -} - -function Star(L: Lang): Lang { - Alpha(true, (a: A) => Comp(L.delta(a), Star(L))) -} -``` - -Note that the [`{:abstemious}`](https://dafny.org/latest/DafnyRef/DafnyRef#sec-abstemious) attribute above signals that a function does not need to unfold a codatatype instance very far (perhaps just one destructor call) to prove a relevant property. Knowing this is the case can aid the proofs of properties about the function. In this case, it is needed to convince Dafny that the corecursive calls in `Comp` and `Star` are logically consistent. - -### Denotational Semantics as Induced Morphism - -The denotational semantics of regular expressions can now be defined through induction, as a function `Denotational: Exp -> Lang`, by making use of the operations on languages we have just defined: - -``` -function Denotational(e: Exp): Lang { - match e - case Zero => Languages.Zero() - case One => Languages.One() - case Char(a) => Languages.Singleton(a) - case Plus(e1, e2) => Languages.Plus(Denotational(e1), Denotational(e2)) - case Comp(e1, e2) => Languages.Comp(Denotational(e1), Denotational(e2)) - case Star(e1) => Languages.Star(Denotational(e1)) -} -``` - -### Bisimilarity and Coinduction - -Let us briefly introduce a notion of equality between formal languages that will be useful soon. A binary relation `R` between languages is called a *[bisimulation](https://en.wikipedia.org/wiki/Bisimulation),* if for any two languages `L1`, `L2` related by `R` the following holds: i) `L1` contains the empty word iff `L2` does; and ii) for any `a: A`, the derivatives `L1.delta(a)` and `L2.delta(a)` are again related by `R`. As it turns out, the union of two bisimulations is again a bisimulation. In consequence, one can combine all possible bisimulations into a single relation: the *greatest* bisimulation. In Dafny, we can formalise the latter as a [`greatest predicate`](https://dafny.org/latest/DafnyRef/DafnyRef#sec-copredicates): - -``` -greatest predicate Bisimilar[nat](L1: Lang, L2: Lang) { - && (L1.eps == L2.eps) - && (forall a :: Bisimilar(L1.delta(a), L2.delta(a))) -} -``` - -It is instructive to think of a `greatest predicate` as pure syntactic sugar. Indeed, under the hood, Dafny’s compiler uses the body above to implicitly generate i) for any `k: nat`, a *[prefix predicate](https://dafny.org/latest/DafnyRef/DafnyRef#514361-properties-of-prefix-predicates)* `Bisimilar#[k](L1, L2)` that signifies that the languages `L1` and `L2` concur on the first `k`-unrollings of the definition above; and ii) a predicate `Bisimilar(L1, L2)` that is true iff `Bisimilar#[k](L1, L2)` is true for all `k: nat`: - -``` -/* Pseudo code for illustration purposes */ - -predicate Bisimilar(L1: Lang, L2: Lang) { - forall k: nat :: Bisimilar#[k](L1, L2) -} - -predicate Bisimilar#[k: nat](L1: Lang, L2: Lang) - decreases k -{ - if k == 0 then - true - else - && (L1.eps == L2.eps) - && (forall a :: Bisimilar#[k-1](L1.delta(a), L2.delta(a))) -} -``` - -Now that we have its definition in place, let us establish a property about bisimilarity, say, that it is a [*reflexive*](https://en.wikipedia.org/wiki/Reflexive_relation) relation. With the [`greatest lemma`](https://dafny.org/latest/DafnyRef/DafnyRef#sec-colemmas) construct, Dafny is able to able to derive a proof completely on its own: - -``` -greatest lemma BisimilarityIsReflexive[nat](L: Lang) - ensures Bisimilar(L, L) -{} -``` - -Once again, it is instructive to think of a `greatest lemma` as pure syntactic sugar. Under the hood, Dafny’s compiler uses the body of the `greatest lemma` to generate i) for any `k: nat`, a *[prefix lemma](https://dafny.org/latest/DafnyRef/DafnyRef#sec-prefix-lemmas)* `BisimilarityIsReflexive#[k](L)` that ensures the prefix predicate `Bisimilar#[k](L, L)`; and ii) a lemma `BisimilarityIsReflexive(L)` that ensures `Bisimilar(L, L)` by calling `Bisimilar#[k](L, L)` for all `k: nat`: - -``` -/* Pseudo code for illustration purposes */ - -lemma BisimilarityIsReflexive(L: Lang) - ensures Bisimilar(L, L) -{ - forall k: nat - ensures Bisimilar#[k](L, L) - { - BisimilarityIsReflexive#[k](L); - } -} - -lemma BisimilarityIsReflexive#[k: nat](L: Lang) - ensures Bisimilar#[k](L, L) - decreases k -{ - if k == 0 { - } else { - forall a - ensures Bisimilar#[k-1](L.delta(a), L.delta(a)) - { - BisimilarityIsReflexive#[k-1](L.delta(a)); - } - } -} -``` - -If you are interested in the full details, we recommend taking a look at [this note on coinduction, predicates, and ordinals](https://leino.science/papers/krml285.html). - -### Denotational Semantics as Algebra Homomorphism - -For a moment, consider the function `var f: nat -> nat := (n: nat) => n + n` which maps a natural number to twice its value. The function `f` is *structure-preserving*: for any `m: nat` we have `f(m * n) == m * f(n)`, i.e. `f` commutes with the (left-)multiplication of naturals. In this section, we are interested in functions of type `f: Exp -> Lang` (more precisely, in `Denotational: Exp -> Lang`) that commute with the algebraic structures we encountered in [Regular Expressions as Datatype](#regular-expressions-as-datatype) and [An Algebra of Formal Languages](#an-algebra-of-formal-languages), respectively. We call such structure-preserving functions *algebra homomorphisms*. To define pointwise commutativity in this context, we’ll have to be able to compare languages for equality. As you probably guessed, bisimilarity will do the job: - -``` -ghost predicate IsAlgebraHomomorphism(f: Exp -> Lang) { - forall e :: IsAlgebraHomomorphismPointwise(f, e) -} - -ghost predicate IsAlgebraHomomorphismPointwise(f: Exp -> Lang, e: Exp) { - Bisimilar( - f(e), - match e - case Zero => Languages.Zero() - case One => Languages.One() - case Char(a) => Languages.Singleton(a) - case Plus(e1, e2) => Languages.Plus(f(e1), f(e2)) - case Comp(e1, e2) => Languages.Comp(f(e1), f(e2)) - case Star(e1) => Languages.Star(f(e1)) - ) -} -``` - -Note that we used the [`ghost`](https://dafny.org/latest/DafnyRef/DafnyRef#sec-declaration-modifier) modifier (which signals that an entity is meant for specification only, not for compilation). A `greatest predicate` is always ghost, so `IsAlgebraHomomorphismPointwise` must be declared ghost to call `Bisimilar`, and `IsAlgebraHomomorphism` must be declared ghost to call `IsAlgebraHomomorphismPointwise`. - -The proof that `Denotational` is an algebra homomorphism is straightforward: it essentially follows from bisimilarity being reflexive: - -``` -lemma DenotationalIsAlgebraHomomorphism() - ensures IsAlgebraHomomorphism(Denotational) -{ - forall e - ensures IsAlgebraHomomorphismPointwise(Denotational, e) - { - BisimilarityIsReflexive(Denotational(e)); - } -} -``` - -## Operational Semantics - -In this section, we provide an alternative perspective on the semantics of regular expressions. We equip the set of regular expressions with a coalgebraic structure, formalise its *operational* semantics as a function from regular expressions to formal languages, and prove that the latter is a coalgebra homomorphism. - -### A Coalgebra of Regular Expressions - -In [An Algebra of Formal Languages](#an-algebra-of-formal-languagesn Algebra of Formal Languages) we equipped the set of formal languages with an algebraic structure that resembles the one of regular expressions. Now, we are aiming for the reverse: we would like to equip the set of regular expressions with a coalgebraic structure that resembles the one of formal languages. More concretely, we would like to turn the set of regular expressions into a deterministic automaton (without initial state) in which a state `e` is i) accepting iff `Eps(e) == true` and ii) transitions to a state `Delta(e)(a)` if given the input `a: A`. Note how our definitions resemble the Brzozowski derivatives we previously encountered: - -``` -function Eps(e: Exp): bool { - match e - case Zero => false - case One => true - case Char(a) => false - case Plus(e1, e2) => Eps(e1) || Eps(e2) - case Comp(e1, e2) => Eps(e1) && Eps(e2) - case Star(e1) => true -} - -function Delta(e: Exp): A -> Exp { - (a: A) => - match e - case Zero => Zero - case One => Zero - case Char(b) => if a == b then One else Zero - case Plus(e1, e2) => Plus(Delta(e1)(a), Delta(e2)(a)) - case Comp(e1, e2) => Plus(Comp(Delta(e1)(a), e2), Comp(if Eps(e1) then One else Zero, Delta(e2)(a))) - case Star(e1) => Comp(Delta(e1)(a), Star(e1)) -} -``` - -### Operational Semantics as Induced Morphism - -The operational semantics of regular expressions can now be defined via coinduction, as a function `Operational: Exp -> Lang`, by making use of the coalgebraic structure on expressions we have just defined: - -``` -function Operational(e: Exp): Lang { - Alpha(Eps(e), (a: A) => Operational(Delta(e)(a))) -} -``` - -### Operational Semantics as Coalgebra Homomorphism - -In [Denotational Semantics as Algebra Homomorphism](#denotational-semantics-as-algebra-homomorphism) we defined algebra homomorphisms as functions `f: Exp -> Lang` that commute with the algebraic structures of regular expressions and formal languages, respectively. Analogously, let us now call a function of the same type a *coalgebra homomorphism*, if it commutes with the *coalgebraic* structures of regular expressions and formal languages, respectively: - -``` -ghost predicate IsCoalgebraHomomorphism(f: Exp -> Lang) { - && (forall e :: f(e).eps == Eps(e)) - && (forall e, a :: Bisimilar(f(e).delta(a), f(Delta(e)(a)))) -} -``` - -It is straightforward to prove that `Operational` is a coalgebra homomorphism: once again, the central argument is that bisimilarity is a reflexive relation. - -``` -lemma OperationalIsCoalgebraHomomorphism() - ensures IsCoalgebraHomomorphism(Operational) -{ - forall e, a - ensures Bisimilar(Operational(e).delta(a), Operational(Delta(e)(a))) - { - BisimilarityIsReflexive(Operational(e).delta(a)); - } -} -``` - -## Well-Behaved Semantics - -So far, we have seen two dual approaches for assigning a formal language semantics to regular expressions: - -* `Denotational`: an algebra homomorphism obtained via induction -* `Operational`: a coalgebra homomorphism obtained via coinduction - -Next, we show that the denotational and operational semantics of regular expressions are *well-behaved*: they constitute two sides of the same coin. First, we show that `Denotational` is also a coalgebra homomorphism, and that coalgebra homomorphisms are unique up to bisimulation. We then deduce from the former that `Denotational` and `Operational` coincide pointwise, up to bisimulation. Finally, we show that `Operational` is also an algebra homomorphism. - -### Denotational Semantics as Coalgebra Homomorphism - -In this section, we establish that `Denotational` not only commutes with the algebraic structures of regular expressions and formal languages, but also with their coalgebraic structures: - -``` -lemma DenotationalIsCoalgebraHomomorphism() - ensures IsCoalgebraHomomorphism(Denotational) -{...} -``` - -The proof of the lemma is a bit more elaborate than the ones we have encountered so far. It can be divided into two subproofs, both of which make use of induction. One of the subproofs is straightforward, the other, more difficult one, again uses the reflexivity of bisimilarity, but also that the latter is a *congruence* relation with respect to `Plus` and `Comp`: - -``` -greatest lemma PlusCongruence[nat](L1a: Lang, L1b: Lang, L2a: Lang, L2b: Lang) - requires Bisimilar(L1a, L1b) - requires Bisimilar(L2a, L2b) - ensures Bisimilar(Plus(L1a, L2a), Plus(L1b, L2b)) -{} - -lemma CompCongruence(L1a: Lang, L1b: Lang, L2a: Lang, L2b: Lang) - requires Bisimilar(L1a, L1b) - requires Bisimilar(L2a, L2b) - ensures Bisimilar(Comp(L1a, L2a), Comp(L1b, L2b)) -{...} -``` - -Dafny is able to prove `PlusCongruence` on its own, as it can take advantage of the syntactic sugaring of the `greatest lemma` construct. For `CompCongruence` we have to put in a bit of manual work ourselves. - -### Coalgebra Homomorphisms Are Unique - -The aim of this section is to show that, up to pointwise bisimilarity, there only exists *one* coalgebra homomorphism of type `Exp -> Lang`: - -``` -lemma UniqueCoalgebraHomomorphism(f: Exp -> Lang, g: Exp -> Lang, e: Exp) - requires IsCoalgebraHomomorphism(f) - requires IsCoalgebraHomomorphism(g) - ensures Bisimilar(f(e), g(e)) -{...} -``` - -[As is well-known](https://ir.cwi.nl/pub/28550/rutten.pdf), the statement may in fact be strengthened to: for *any* coalgebra `C` there exists exactly one coalgebra homomorphism of type `C -> Lang` , up to pointwise bisimulation. For our purposes, the weaker statement above will be sufficient. At the heart of the proof lies the observation that bisimilarity is transitive: - -``` -greatest lemma BisimilarityIsTransitive[nat](L1: Lang, L2: Lang, L3: Lang) - requires Bisimilar(L1, L2) && Bisimilar(L2, L3) - ensures Bisimilar(L1, L3) -{} -``` - -In fact, in practice, we actually use a slightly more fine grained formalisation of transitivity, as is illustrated below by the proof of `UniqueCoalgebraHomomorphismHelperPointwise`, which is used in the proof of `UniqueCoalgebraHomomorphism`: - -``` -lemma UniqueCoalgebraHomomorphismHelperPointwise(k: nat, f: Exp -> Lang, g: Exp -> Lang, L1: Lang, L2: Lang) - requires IsCoalgebraHomomorphism(f) - requires IsCoalgebraHomomorphism(g) - requires exists e :: Bisimilar#[k](L1, f(e)) && Bisimilar#[k](L2, g(e)) - ensures Bisimilar#[k](L1, L2) -{ - var e :| Bisimilar#[k](L1, f(e)) && Bisimilar#[k](L2, g(e)); - if k != 0 { - forall a - ensures Bisimilar#[k-1](L1.delta(a), L2.delta(a)) - { - BisimilarityIsTransitivePointwise(k-1, L1.delta(a), f(e).delta(a), f(Delta(e)(a))); - BisimilarityIsTransitivePointwise(k-1, L2.delta(a), g(e).delta(a), g(Delta(e)(a))); - UniqueCoalgebraHomomorphismHelperPointwise(k-1, f, g, L1.delta(a), L2.delta(a)); - } - } -} - -lemma BisimilarityIsTransitivePointwise(k: nat, L1: Lang, L2: Lang, L3: Lang) - ensures Bisimilar#[k](L1, L2) && Bisimilar#[k](L2, L3) ==> Bisimilar#[k](L1, L3) -{ - if k != 0 { - if Bisimilar#[k](L1, L2) && Bisimilar#[k](L2, L3) { - assert Bisimilar#[k](L1, L3) by { - forall a - ensures Bisimilar#[k-1](L1.delta(a), L3.delta(a)) - { - BisimilarityIsTransitivePointwise(k-1, L1.delta(a), L2.delta(a), L3.delta(a)); - } - } - } - } -} -``` - -### Denotational and Operational Semantics Are Bisimilar - -We are done! From the previous results, we can immediately deduce that denotational and operational semantics are the same, up to pointwise bisimilarity: - -``` -lemma OperationalAndDenotationalAreBisimilar(e: Exp) - ensures Bisimilar(Operational(e), Denotational(e)) -{ - OperationalIsCoalgebraHomomorphism(); - DenotationalIsCoalgebraHomomorphism(); - UniqueCoalgebraHomomorphism(Operational, Denotational, e); -} -``` - -### Operational Semantics as Algebra Homomorphism - -As a bonus, for the sake of symmetry, let us also prove that `Operational` is an algebra homomorphism. (We already know that it is a coalgebra homomorphism, and that `Denotational` is both an algebra and coalgebra homomorphism.) - -``` -lemma OperationalIsAlgebraHomomorphism() - ensures IsAlgebraHomomorphism(Operational) -{...} -``` - -The idea of the proof is to take advantage of `Denotational` being an algebra homomorphism, by translating its properties to `Operational` via the lemma in [Denotational and Operational Semantics Are Bisimilar](#denotational-and-operational-semantics-are-bisimilar). The relevant new statements capture that bisimilarity is symmetric and a congruence with respect to the `Star` operation: - -``` -greatest lemma BisimilarityIsSymmetric[nat](L1: Lang, L2: Lang) - ensures Bisimilar(L1, L2) ==> Bisimilar(L2, L1) - ensures Bisimilar(L1, L2) <== Bisimilar(L2, L1) -{} - -lemma StarCongruence(L1: Lang, L2: Lang) - requires Bisimilar(L1, L2) - ensures Bisimilar(Star(L1), Star(L2)) -{...} -``` - -## Conclusion - -We have used Dafny’s built-in inductive and coinductive reasoning capabilities to define two language semantics for regular expressions: denotational and operational semantics. Through a number of dualities — construction and deconstruction, algebras and coalgebras, and congruence and bisimilarity — we have proven the semantics to be two sides of the same coin. The blogpost is inspired by research in the field of [Coalgebra](https://en.wikipedia.org/wiki/F-coalgebra), which was pioneered by [Rutten](https://pdf.sciencedirectassets.com/271538/1-s2.0-S0304397500X01466/1-s2.0-S0304397500000566/main.pdf), [Gumm](https://www.researchgate.net/publication/2614339_Elements_Of_The_General_Theory_Of_Coalgebras), and others. The concept of well-behaved semantics goes back to [Turi and Plotkin](https://homepages.inf.ed.ac.uk/gdp/publications/Math_Op_Sem.pdf) and was adapted by [Jacobs](https://link.springer.com/chapter/10.1007/11780274_20) to the case of regular expressions. We heavily used automata theoretic constructions from the 1960s, originally investigated by [Brzozowski](https://dl.acm.org/doi/10.1145/321239.321249) (a more modern presentation can be found e.g. [here](https://alexandrasilva.org/files/thesis.pdf)). Our presentation focused on the most important intuitive aspects of the proofs. To dive deep, please take a look at the full Dafny source code, which is available [here](../../../../assets/src/semantics-of-regular-expressions/Languages.dfy), [here](../../../../assets/src/semantics-of-regular-expressions/Semantics.dfy), and [here](../../../../assets/src/semantics-of-regular-expressions/Expressions.dfy). \ No newline at end of file +{% include semantics-of-regular-expressions.html %} \ No newline at end of file diff --git a/assets/mdk/semantics-of-regular-expressions.mdk b/assets/mdk/semantics-of-regular-expressions.mdk new file mode 100644 index 0000000..a6e7daf --- /dev/null +++ b/assets/mdk/semantics-of-regular-expressions.mdk @@ -0,0 +1,217 @@ + +Colorizer : dafnyx + +Heading Depth: 0 + +Css Header: + body.madoko { + max-width: 100%; + margin: 0; + padding: 0; + } + .page-content { + padding: 0; + } + +## Introduction + +[Regular expressions](https://en.wikipedia.org/wiki/Regular_expression) are one of the most ubiquitous formalisms of theoretical computer science. Commonly, they are understood in terms of their [*denotational* semantics](https://en.wikipedia.org/wiki/Denotational_semantics), that is, through formal languages — the [*regular* languages](https://en.wikipedia.org/wiki/Regular_language). This view is *inductive* in nature: two primitives are equivalent if they are *constructed* in the same way. Alternatively, regular expressions can be understood in terms of their [*operational* semantics](https://en.wikipedia.org/wiki/Operational_semantics), that is, through [finite automata](https://en.wikipedia.org/wiki/Finite-state_machine). This view is *coinductive* in nature: two primitives are equivalent if they are *deconstructed* in the same way. It is implied by [Kleene’s famous theorem](http://www.dlsi.ua.es/~mlf/nnafmc/papers/kleene56representation.pdf) that both views are equivalent: regular languages are precisely the formal languages accepted by finite automata. In this blogpost, we utilise Dafny’s built-in inductive and coinductive reasoning capabilities to show that the two semantics of regular expressions are *[well-behaved](https://homepages.inf.ed.ac.uk/gdp/publications/Math_Op_Sem.pdf)*, in the sense they are in fact one and the same, up to pointwise [bisimulation](https://en.wikipedia.org/wiki/Bisimulation). + +## Denotational Semantics + +In this section, we define regular expressions and formal languages, introduce the concept of bisimilarity, formalise the *denotational* semantics of regular expressions as a function from regular expressions to formal languages, and prove that the latter is an algebra homomorphism. + +### Regular Expressions as Datatype + +We define the set of regular expressions parametric in an alphabet `A` as an inductive [`datatype`](https://dafny.org/latest/DafnyRef/DafnyRef#sec-inductive-datatypes): + + + +Note that above, and later, we make use of Dafny's [type parameter completion](https://leino.science/papers/krml270.html). + +The definition captures that a regular expression is either a primitive character `Char(a)`, a non-deterministic choice between two regular expressions `Plus(e1, e2)`, a sequential composition of two regular expressions `Comp(e1, e2)`, a finite number of self-iterations `Star(e)`, or one of the constants `Zero` (the unit of `Plus`) and `One` (the unit of `Comp`). At a higher level, the above defines `Exp` as the *smallest* algebraic structure that is equipped with two constants, contains all elements of type `A`, and is closed under two binary operations and one unary operation. + +### Formal Languages as Codatatype + +We define the set of formal languages parametric in an alphabet `A` as a coinductive [`codatatype`](https://dafny.org/latest/DafnyRef/DafnyRef#sec-coinductive-datatypes): + + + +Note that we used the type-parameter mode [`!`](https://dafny.org/latest/DafnyRef/DafnyRef#sec-type-parameter-variance), which indicates that there could be strictly more values of type `Lang` than values of type `A`, for any type `A`, and that there is no subtype relation between `Lang` and `Lang`, for any two types `A, B`. A more detailed explanation of the topic can be found [here](https://leino.science/papers/krml280.html). + +To some, the choice above might seem odd at first sight. If you are familiar with the topic, you likely have expected a formal language to be defined more concretely as a set of finite sequences (sometimes called *words*), `iset>`. Rest assured, we agree — up to an appropriate notion of equality! Whereas you characterise languages intrinsically, we treat them extrinsically, in terms of their universal property: [it is well known](https://ir.cwi.nl/pub/28550/rutten.pdf) that `iset>` forms the *greatest* [coalgebraic](https://en.wikipedia.org/wiki/F-coalgebra) structure (think of a deterministic automaton without initial state) `S` that is equipped with functions `eps: S -> bool` and `delta: S → (A → S)`. Indeed, for any set `U` of finite sequences, we can verify whether `U` contains the empty sequence, `U.eps == ([] in U)`, and for any `a: A`, we can transition to the set `U.delta(a) == (iset s | [a] + s in U)`. Here, we choose the more abstract perspective on formal languages as it hides irrelevant specifics and thus allows us to write more elegant proofs. + +### An Algebra of Formal Languages + +If you think of formal languages as the set of all sets of finite sequences, you will soon realise that languages admit quite a bit of algebraic structure. For example, there exist two languages of distinct importance (can you already guess which ones?), and one can obtain a new language by taking e.g. the union of two languages. In fact, if you think about it for a bit longer, you’ll realise that formal languages admit exactly the same [type of algebraic structure](https://en.wikipedia.org/wiki/F-algebra) as the one you’ve encountered when we defined regular expressions! + +First, there exists the empty language `Zero()` that contains no words at all. Under the view above, we find `Zero().eps == false` and `Zero().delta(a) == Zero()`, since the empty set does not contain the empty sequence, and the derivative `iset s | [a] + s in iset{}` with respect to any `a: A` yields again the empty set, respectively. We thus define: + + + +Using similar reasoning, we additionally derive the following definitions. In order, we formalise i) the language `One()` that contains only the empty sequence; ii) for any `a: A` the language `Singleton(a)` that consists of only the word `[a]`; iii) the language `Plus(L1, L2)` which consists of the union of the languages `L1` and `L2`; iv) the language `Comp(L1, L2)` that consists of all possible concatenation of words in `L1` and `L2`; and v) the language `Star(L)` that consists of all finite compositions of `L` with itself. Our definitions match what is well-known as *[Brzozowski derivatives](https://en.wikipedia.org/wiki/Brzozowski_derivative)*. + + + +Note that the [`{:abstemious}`](https://dafny.org/latest/DafnyRef/DafnyRef#sec-abstemious) attribute above signals that a function does not need to unfold a codatatype instance very far (perhaps just one destructor call) to prove a relevant property. Knowing this is the case can aid the proofs of properties about the function. In this case, it is needed to convince Dafny that the corecursive calls in `Comp` and `Star` are logically consistent. + +### Denotational Semantics as Induced Morphism + +The denotational semantics of regular expressions can now be defined through induction, as a function `Denotational: Exp -> Lang`, by making use of the operations on languages we have just defined: + + + +### Bisimilarity and Coinduction + +Let us briefly introduce a notion of equality between formal languages that will be useful soon. A binary relation `R` between languages is called a *[bisimulation](https://en.wikipedia.org/wiki/Bisimulation),* if for any two languages `L1`, `L2` related by `R` the following holds: i) `L1` contains the empty word iff `L2` does; and ii) for any `a: A`, the derivatives `L1.delta(a)` and `L2.delta(a)` are again related by `R`. As it turns out, the union of two bisimulations is again a bisimulation. In consequence, one can combine all possible bisimulations into a single relation: the *greatest* bisimulation. In Dafny, we can formalise the latter as a [`greatest predicate`](https://dafny.org/latest/DafnyRef/DafnyRef#sec-copredicates): + + + +It is instructive to think of a `greatest predicate` as pure syntactic sugar. Indeed, under the hood, Dafny’s compiler uses the body above to implicitly generate i) for any `k: nat`, a *[prefix predicate](https://dafny.org/latest/DafnyRef/DafnyRef#514361-properties-of-prefix-predicates)* `Bisimilar#[k](L1, L2)` that signifies that the languages `L1` and `L2` concur on the first `k`-unrollings of the definition above; and ii) a predicate `Bisimilar(L1, L2)` that is true iff `Bisimilar#[k](L1, L2)` is true for all `k: nat`: + +``` dafny + /* Pseudo code for illustration purposes */ + + predicate Bisimilar(L1: Lang, L2: Lang) { + forall k: nat :: Bisimilar#[k](L1, L2) + } + + predicate Bisimilar#[k: nat](L1: Lang, L2: Lang) + decreases k + { + if k == 0 then + true + else + && (L1.eps == L2.eps) + && (forall a :: Bisimilar#[k-1](L1.delta(a), L2.delta(a))) + } +``` + +Now that we have its definition in place, let us establish a property about bisimilarity, say, that it is a [*reflexive*](https://en.wikipedia.org/wiki/Reflexive_relation) relation. With the [`greatest lemma`](https://dafny.org/latest/DafnyRef/DafnyRef#sec-colemmas) construct, Dafny is able to able to derive a proof completely on its own: + + + +Once again, it is instructive to think of a `greatest lemma` as pure syntactic sugar. Under the hood, Dafny’s compiler uses the body of the `greatest lemma` to generate i) for any `k: nat`, a *[prefix lemma](https://dafny.org/latest/DafnyRef/DafnyRef#sec-prefix-lemmas)* `BisimilarityIsReflexive#[k](L)` that ensures the prefix predicate `Bisimilar#[k](L, L)`; and ii) a lemma `BisimilarityIsReflexive(L)` that ensures `Bisimilar(L, L)` by calling `Bisimilar#[k](L, L)` for all `k: nat`: + +``` dafny + /* Pseudo code for illustration purposes */ + + lemma BisimilarityIsReflexive(L: Lang) + ensures Bisimilar(L, L) + { + forall k: nat + ensures Bisimilar#[k](L, L) + { + BisimilarityIsReflexive#[k](L); + } + } + + lemma BisimilarityIsReflexive#[k: nat](L: Lang) + ensures Bisimilar#[k](L, L) + decreases k + { + if k == 0 { + } else { + forall a + ensures Bisimilar#[k-1](L.delta(a), L.delta(a)) + { + BisimilarityIsReflexive#[k-1](L.delta(a)); + } + } + } +``` + +If you are interested in the full details, we recommend taking a look at [this note on coinduction, predicates, and ordinals](https://leino.science/papers/krml285.html). + +### Denotational Semantics as Algebra Homomorphism + +For a moment, consider the function `var f: nat -> nat := (n: nat) => n + n` which maps a natural number to twice its value. The function `f` is *structure-preserving*: for any `m: nat` we have `f(m * n) == m * f(n)`, i.e. `f` commutes with the (left-)multiplication of naturals. In this section, we are interested in functions of type `f: Exp -> Lang` (more precisely, in `Denotational: Exp -> Lang`) that commute with the algebraic structures we encountered in [Regular Expressions as Datatype](#sec-regular-expressions-as-datatype) and [An Algebra of Formal Languages](#sec-an-algebra-of-formal-languages), respectively. We call such structure-preserving functions *algebra homomorphisms*. To define pointwise commutativity in this context, we’ll have to be able to compare languages for equality. As you probably guessed, bisimilarity will do the job: + + + +Note that we used the [`ghost`](https://dafny.org/latest/DafnyRef/DafnyRef#sec-declaration-modifier) modifier (which signals that an entity is meant for specification only, not for compilation). A `greatest predicate` is always ghost, so `IsAlgebraHomomorphismPointwise` must be declared ghost to call `Bisimilar`, and `IsAlgebraHomomorphism` must be declared ghost to call `IsAlgebraHomomorphismPointwise`. + +The proof that `Denotational` is an algebra homomorphism is straightforward: it essentially follows from bisimilarity being reflexive: + + + +## Operational Semantics + +In this section, we provide an alternative perspective on the semantics of regular expressions. We equip the set of regular expressions with a coalgebraic structure, formalise its *operational* semantics as a function from regular expressions to formal languages, and prove that the latter is a coalgebra homomorphism. + +### A Coalgebra of Regular Expressions + +In [An Algebra of Formal Languages](#sec-an-algebra-of-formal-languages) we equipped the set of formal languages with an algebraic structure that resembles the one of regular expressions. Now, we are aiming for the reverse: we would like to equip the set of regular expressions with a coalgebraic structure that resembles the one of formal languages. More concretely, we would like to turn the set of regular expressions into a deterministic automaton (without initial state) in which a state `e` is i) accepting iff `Eps(e) == true` and ii) transitions to a state `Delta(e)(a)` if given the input `a: A`. Note how our definitions resemble the Brzozowski derivatives we previously encountered: + + + +### Operational Semantics as Induced Morphism + +The operational semantics of regular expressions can now be defined via coinduction, as a function `Operational: Exp -> Lang`, by making use of the coalgebraic structure on expressions we have just defined: + + + +### Operational Semantics as Coalgebra Homomorphism + +In [Denotational Semantics as Algebra Homomorphism](#sec-denotational-semantics-as-algebra-homomorphism) we defined algebra homomorphisms as functions `f: Exp -> Lang` that commute with the algebraic structures of regular expressions and formal languages, respectively. Analogously, let us now call a function of the same type a *coalgebra homomorphism*, if it commutes with the *coalgebraic* structures of regular expressions and formal languages, respectively: + + + +It is straightforward to prove that `Operational` is a coalgebra homomorphism: once again, the central argument is that bisimilarity is a reflexive relation. + + + +## Well-Behaved Semantics + +So far, we have seen two dual approaches for assigning a formal language semantics to regular expressions: + +* `Denotational`: an algebra homomorphism obtained via induction +* `Operational`: a coalgebra homomorphism obtained via coinduction + +Next, we show that the denotational and operational semantics of regular expressions are *well-behaved*: they constitute two sides of the same coin. First, we show that `Denotational` is also a coalgebra homomorphism, and that coalgebra homomorphisms are unique up to bisimulation. We then deduce from the former that `Denotational` and `Operational` coincide pointwise, up to bisimulation. Finally, we show that `Operational` is also an algebra homomorphism. + +### Denotational Semantics as Coalgebra Homomorphism + +In this section, we establish that `Denotational` not only commutes with the algebraic structures of regular expressions and formal languages, but also with their coalgebraic structures: + + + +The proof of the lemma is a bit more elaborate than the ones we have encountered so far. It can be divided into two subproofs, both of which make use of induction. One of the subproofs is straightforward, the other, more difficult one, again uses the reflexivity of bisimilarity, but also that the latter is a *congruence* relation with respect to `Plus` and `Comp`: + + + +Dafny is able to prove `PlusCongruence` on its own, as it can take advantage of the syntactic sugaring of the `greatest lemma` construct. For `CompCongruence` we have to put in a bit of manual work ourselves. + +### Coalgebra Homomorphisms Are Unique + +The aim of this section is to show that, up to pointwise bisimilarity, there only exists *one* coalgebra homomorphism of type `Exp -> Lang`: + + + +[As is well-known](https://ir.cwi.nl/pub/28550/rutten.pdf), the statement may in fact be strengthened to: for *any* coalgebra `C` there exists exactly one coalgebra homomorphism of type `C -> Lang` , up to pointwise bisimulation. For our purposes, the weaker statement above will be sufficient. At the heart of the proof lies the observation that bisimilarity is transitive: + + + +In fact, in practice, we actually use a slightly more fine grained formalisation of transitivity, as is illustrated below by the proof of `UniqueCoalgebraHomomorphismHelperPointwise`, which is used in the proof of `UniqueCoalgebraHomomorphism`: + + + +### Denotational and Operational Semantics Are Bisimilar + +We are done! From the previous results, we can immediately deduce that denotational and operational semantics are the same, up to pointwise bisimilarity: + + + +### Operational Semantics as Algebra Homomorphism + +As a bonus, for the sake of symmetry, let us also prove that `Operational` is an algebra homomorphism. (We already know that it is a coalgebra homomorphism, and that `Denotational` is both an algebra and coalgebra homomorphism.) + + + +The idea of the proof is to take advantage of `Denotational` being an algebra homomorphism, by translating its properties to `Operational` via the lemma in [Denotational and Operational Semantics Are Bisimilar](#sec-denotational-and-operational-semantics-are-bisimilar). The relevant new statements capture that bisimilarity is symmetric and a congruence with respect to the `Star` operation: + + + +## Conclusion + +We have used Dafny’s built-in inductive and coinductive reasoning capabilities to define two language semantics for regular expressions: denotational and operational semantics. Through a number of dualities — construction and deconstruction, algebras and coalgebras, and congruence and bisimilarity — we have proven the semantics to be two sides of the same coin. The blogpost is inspired by research in the field of [Coalgebra](https://en.wikipedia.org/wiki/F-coalgebra), which was pioneered by [Rutten](https://pdf.sciencedirectassets.com/271538/1-s2.0-S0304397500X01466/1-s2.0-S0304397500000566/main.pdf), [Gumm](https://www.researchgate.net/publication/2614339_Elements_Of_The_General_Theory_Of_Coalgebras), and others. The concept of well-behaved semantics goes back to [Turi and Plotkin](https://homepages.inf.ed.ac.uk/gdp/publications/Math_Op_Sem.pdf) and was adapted by [Jacobs](https://link.springer.com/chapter/10.1007/11780274_20) to the case of regular expressions. We heavily used automata theoretic constructions from the 1960s, originally investigated by [Brzozowski](https://dl.acm.org/doi/10.1145/321239.321249) (a more modern presentation can be found e.g. [here](https://alexandrasilva.org/files/thesis.pdf)). Our presentation focused on the most important intuitive aspects of the proofs. To dive deep, please take a look at the full Dafny source code, which is available [here](../../../../assets/src/semantics-of-regular-expressions/Languages.dfy), [here](../../../../assets/src/semantics-of-regular-expressions/Semantics.dfy), and [here](../../../../assets/src/semantics-of-regular-expressions/Expressions.dfy). \ No newline at end of file diff --git a/assets/src/semantics-of-regular-expressions/Expressions.dfy b/assets/src/semantics-of-regular-expressions/Expressions.dfy index 45877ff..1e12fc4 100644 --- a/assets/src/semantics-of-regular-expressions/Expressions.dfy +++ b/assets/src/semantics-of-regular-expressions/Expressions.dfy @@ -1,8 +1,9 @@ -module Expressions { - - /* Definitions */ - +module Expressions0 { datatype Exp = Zero | One | Char(A) | Plus(Exp, Exp) | Comp(Exp, Exp) | Star(Exp) +} + +module Expressions1 { + import opened Expressions0 function Eps(e: Exp): bool { match e @@ -24,5 +25,4 @@ module Expressions { case Comp(e1, e2) => Plus(Comp(Delta(e1)(a), e2), Comp(if Eps(e1) then One else Zero, Delta(e2)(a))) case Star(e1) => Comp(Delta(e1)(a), Star(e1)) } - } \ No newline at end of file diff --git a/assets/src/semantics-of-regular-expressions/Languages.dfy b/assets/src/semantics-of-regular-expressions/Languages.dfy index a04faa8..0275910 100644 --- a/assets/src/semantics-of-regular-expressions/Languages.dfy +++ b/assets/src/semantics-of-regular-expressions/Languages.dfy @@ -1,12 +1,18 @@ -module Languages { - - /* Definitions */ - +module Languages0 { codatatype Lang = Alpha(eps: bool, delta: A -> Lang) +} + +module Languages1 { + import opened Languages0 function Zero(): Lang { Alpha(false, (a: A) => Zero()) } +} + +module Languages2 { + import opened Languages0 + import opened Languages1 function One(): Lang { Alpha(true, (a: A) => Zero()) @@ -21,90 +27,45 @@ module Languages { } function {:abstemious} Comp(L1: Lang, L2: Lang): Lang { - Alpha(L1.eps && L2.eps, (a: A) => Plus(Comp(L1.delta(a), L2), Comp(if L1.eps then One() else Zero(), L2.delta(a)))) + Alpha( + L1.eps && L2.eps, + (a: A) => Plus(Comp(L1.delta(a), L2), Comp(if L1.eps then One() else Zero(), L2.delta(a))) + ) } function Star(L: Lang): Lang { Alpha(true, (a: A) => Comp(L.delta(a), Star(L))) } +} + +module Languages3 { + import opened Languages0 + import opened Languages1 + import opened Languages2 greatest predicate Bisimilar[nat](L1: Lang, L2: Lang) { && (L1.eps == L2.eps) && (forall a :: Bisimilar(L1.delta(a), L2.delta(a))) } +} - /* Lemmas */ - - /* Bisimilarity */ +module Languages4 { + import opened Languages0 + import opened Languages1 + import opened Languages2 + import opened Languages3 greatest lemma BisimilarityIsReflexive[nat](L: Lang) ensures Bisimilar(L, L) {} +} - lemma BisimilarityIsTransitiveAlternative(L1: Lang, L2: Lang, L3: Lang) - ensures Bisimilar(L1, L2) && Bisimilar(L2, L3) ==> Bisimilar(L1, L3) - { - if Bisimilar(L1,L2) && Bisimilar(L2, L3) { - assert Bisimilar(L1, L3) by { - BisimilarityIsTransitive(L1, L2, L3); - } - } - } - - greatest lemma BisimilarityIsTransitive[nat](L1: Lang, L2: Lang, L3: Lang) - requires Bisimilar(L1, L2) && Bisimilar(L2, L3) - ensures Bisimilar(L1, L3) - {} - - lemma BisimilarityIsTransitivePointwise(k: nat, L1: Lang, L2: Lang, L3: Lang) - ensures Bisimilar#[k](L1, L2) && Bisimilar#[k](L2, L3) ==> Bisimilar#[k](L1, L3) - { - if k != 0 { - if Bisimilar#[k](L1, L2) && Bisimilar#[k](L2, L3) { - assert Bisimilar#[k](L1, L3) by { - forall a - ensures Bisimilar#[k-1](L1.delta(a), L3.delta(a)) - { - BisimilarityIsTransitivePointwise(k-1, L1.delta(a), L2.delta(a), L3.delta(a)); - } - } - } - } - } - - greatest lemma BisimilarityIsSymmetric[nat](L1: Lang, L2: Lang) - ensures Bisimilar(L1, L2) ==> Bisimilar(L2, L1) - ensures Bisimilar(L1, L2) <== Bisimilar(L2, L1) - {} - - lemma BisimilarCuttingPrefixes(k: nat, L1: Lang, L2: Lang) - requires forall n: nat :: n <= k + 1 ==> Bisimilar#[n](L1, L2) - ensures forall a :: Bisimilar#[k](L1.delta(a), L2.delta(a)) - { - forall a - ensures Bisimilar#[k](L1.delta(a), L2.delta(a)) - { - if k != 0 { - assert Bisimilar#[k + 1](L1, L2); - } - } - } - - lemma BisimilarCuttingPrefixesPointwise(k: nat, a: A, L1a: Lang, L1b: Lang) - requires k != 0 - requires forall n: nat :: n <= k + 1 ==> Bisimilar#[n](L1a, L1b) - ensures forall n: nat :: n <= k ==> Bisimilar#[n](L1a.delta(a), L1b.delta(a)) - { - forall n: nat - ensures n <= k ==> Bisimilar#[n](L1a.delta(a), L1b.delta(a)) - { - if n <= k { - BisimilarCuttingPrefixes(n, L1a, L1b); - } - } - } - - /* Congruence of Plus */ +module Languages5 { + import opened Languages0 + import opened Languages1 + import opened Languages2 + import opened Languages3 + import opened Languages4 greatest lemma PlusCongruence[nat](L1a: Lang, L1b: Lang, L2a: Lang, L2b: Lang) requires Bisimilar(L1a, L1b) @@ -112,18 +73,15 @@ module Languages { ensures Bisimilar(Plus(L1a, L2a), Plus(L1b, L2b)) {} - lemma PlusCongruenceAlternative(k: nat, L1a: Lang, L1b: Lang, L2a: Lang, L2b: Lang) - requires Bisimilar#[k](L1a, L1b) - requires Bisimilar#[k](L2a, L2b) - ensures Bisimilar#[k](Plus(L1a, L2a), Plus(L1b, L2b)) - {} - - /* Congruence of Comp */ - lemma CompCongruence(L1a: Lang, L1b: Lang, L2a: Lang, L2b: Lang) requires Bisimilar(L1a, L1b) requires Bisimilar(L2a, L2b) ensures Bisimilar(Comp(L1a, L2a), Comp(L1b, L2b)) +} + +module Languages5WithProof refines Languages5 { + lemma CompCongruence(L1a: Lang, L1b: Lang, L2a: Lang, L2b: Lang) + ensures Bisimilar(Comp(L1a, L2a), Comp(L1b, L2b)) { forall k: nat ensures Bisimilar#[k](Comp(L1a, L2a), Comp(L1b, L2b)) @@ -147,8 +105,8 @@ module Languages { assert Bisimilar#[1](L2a, L2b); assert lhs.eps == rhs.eps; - forall a - ensures (Bisimilar#[k](lhs.delta(a), rhs.delta(a))) + forall a + ensures (Bisimilar#[k](lhs.delta(a), rhs.delta(a))) { var x1 := Comp(L1a.delta(a), L2a); var x2 := Comp(L1b.delta(a), L2b); @@ -181,11 +139,76 @@ module Languages { } } - /* Congruence of Star */ + lemma PlusCongruenceAlternative(k: nat, L1a: Lang, L1b: Lang, L2a: Lang, L2b: Lang) + requires Bisimilar#[k](L1a, L1b) + requires Bisimilar#[k](L2a, L2b) + ensures Bisimilar#[k](Plus(L1a, L2a), Plus(L1b, L2b)) + {} + + + lemma BisimilarCuttingPrefixesPointwise(k: nat, a: A, L1a: Lang, L1b: Lang) + requires k != 0 + requires forall n: nat :: n <= k + 1 ==> Bisimilar#[n](L1a, L1b) + ensures forall n: nat :: n <= k ==> Bisimilar#[n](L1a.delta(a), L1b.delta(a)) + { + forall n: nat + ensures n <= k ==> Bisimilar#[n](L1a.delta(a), L1b.delta(a)) + { + if n <= k { + BisimilarCuttingPrefixes(n, L1a, L1b); + } + } + } + + lemma BisimilarCuttingPrefixes(k: nat, L1: Lang, L2: Lang) + requires forall n: nat :: n <= k + 1 ==> Bisimilar#[n](L1, L2) + ensures forall a :: Bisimilar#[k](L1.delta(a), L2.delta(a)) + { + forall a + ensures Bisimilar#[k](L1.delta(a), L2.delta(a)) + { + if k != 0 { + assert Bisimilar#[k + 1](L1, L2); + } + } + } +} + +module Languages6 { + import opened Languages0 + import opened Languages1 + import opened Languages2 + import opened Languages3 + import opened Languages4 + import opened Languages5WithProof + + greatest lemma BisimilarityIsTransitive[nat](L1: Lang, L2: Lang, L3: Lang) + requires Bisimilar(L1, L2) && Bisimilar(L2, L3) + ensures Bisimilar(L1, L3) + {} +} + +module Languages7 { + import opened Languages0 + import opened Languages1 + import opened Languages2 + import opened Languages3 + import opened Languages4 + import opened Languages5WithProof + import opened Languages6 + greatest lemma BisimilarityIsSymmetric[nat](L1: Lang, L2: Lang) + ensures Bisimilar(L1, L2) ==> Bisimilar(L2, L1) + ensures Bisimilar(L1, L2) <== Bisimilar(L2, L1) + {} lemma StarCongruence(L1: Lang, L2: Lang) requires Bisimilar(L1, L2) ensures Bisimilar(Star(L1), Star(L2)) +} + +module Languages7WithProof refines Languages7 { + lemma StarCongruence(L1: Lang, L2: Lang) + ensures Bisimilar(Star(L1), Star(L2)) { forall k: nat ensures Bisimilar#[k](Star(L1), Star(L2)) @@ -201,8 +224,8 @@ module Languages { requires forall n: nat :: n <= k + 1 ==> Bisimilar#[n](L1, L2) ensures Bisimilar#[k+1](Star(L1), Star(L2)) { - forall a - ensures Bisimilar#[k](Star(L1).delta(a), Star(L2).delta(a)) + forall a + ensures Bisimilar#[k](Star(L1).delta(a), Star(L2).delta(a)) { if k != 0 { BisimilarCuttingPrefixesPointwise(k, a, L1, L2); @@ -219,5 +242,25 @@ module Languages { } } } +} + +module Languages8 { + import opened Languages0 + import opened Languages1 + import opened Languages2 + import opened Languages3 + import opened Languages4 + import opened Languages5WithProof + import opened Languages6 + import opened Languages7WithProof + lemma BisimilarityIsTransitiveAlternative(L1: Lang, L2: Lang, L3: Lang) + ensures Bisimilar(L1, L2) && Bisimilar(L2, L3) ==> Bisimilar(L1, L3) + { + if Bisimilar(L1,L2) && Bisimilar(L2, L3) { + assert Bisimilar(L1, L3) by { + BisimilarityIsTransitive(L1, L2, L3); + } + } + } } \ No newline at end of file diff --git a/assets/src/semantics-of-regular-expressions/Semantics.dfy b/assets/src/semantics-of-regular-expressions/Semantics.dfy index c769762..431566d 100644 --- a/assets/src/semantics-of-regular-expressions/Semantics.dfy +++ b/assets/src/semantics-of-regular-expressions/Semantics.dfy @@ -1,37 +1,27 @@ include "Expressions.dfy" include "Languages.dfy" -module Semantics { - - import opened Expressions - import opened Languages - - /* Definitions */ - - /* Denotational Semantics */ +module Semantics0 { + import opened Languages0 + import opened Languages1 + import opened Languages2 + import opened Expressions0 function Denotational(e: Exp): Lang { match e - case Zero => Languages.Zero() - case One => Languages.One() - case Char(a) => Languages.Singleton(a) - case Plus(e1, e2) => Languages.Plus(Denotational(e1), Denotational(e2)) - case Comp(e1, e2) => Languages.Comp(Denotational(e1), Denotational(e2)) - case Star(e1) => Languages.Star(Denotational(e1)) + case Zero => Languages1.Zero() + case One => Languages2.One() + case Char(a) => Languages2.Singleton(a) + case Plus(e1, e2) => Languages2.Plus(Denotational(e1), Denotational(e2)) + case Comp(e1, e2) => Languages2.Comp(Denotational(e1), Denotational(e2)) + case Star(e1) => Languages2.Star(Denotational(e1)) } +} - /* Operational Semantics */ - - function Operational(e: Exp): Lang { - Alpha(Eps(e), (a: A) => Operational(Delta(e)(a))) - } - - /* (Co)algebra homomorphisms f: Exp -> Lang */ - - ghost predicate IsCoalgebraHomomorphism(f: Exp -> Lang) { - && (forall e :: f(e).eps == Eps(e)) - && (forall e, a :: Bisimilar(f(e).delta(a), f(Delta(e)(a)))) - } +module Semantics1 { + import opened Languages0 + import opened Languages3 + import opened Expressions0 ghost predicate IsAlgebraHomomorphism(f: Exp -> Lang) { forall e :: IsAlgebraHomomorphismPointwise(f, e) @@ -41,75 +31,90 @@ module Semantics { Bisimilar( f(e), match e - case Zero => Languages.Zero() - case One => Languages.One() - case Char(a) => Languages.Singleton(a) - case Plus(e1, e2) => Languages.Plus(f(e1), f(e2)) - case Comp(e1, e2) => Languages.Comp(f(e1), f(e2)) - case Star(e1) => Languages.Star(f(e1)) + case Zero => Languages1.Zero() + case One => Languages2.One() + case Char(a) => Languages2.Singleton(a) + case Plus(e1, e2) => Languages2.Plus(f(e1), f(e2)) + case Comp(e1, e2) => Languages2.Comp(f(e1), f(e2)) + case Star(e1) => Languages2.Star(f(e1)) ) } +} - /* Lemmas */ - - /* Any two coalgebra homomorphisms f,g: Exp -> Lang are equal up to bisimulation */ - - lemma UniqueCoalgebraHomomorphism(f: Exp -> Lang, g: Exp -> Lang, e: Exp) - requires IsCoalgebraHomomorphism(f) - requires IsCoalgebraHomomorphism(g) - ensures Bisimilar(f(e), g(e)) - { - BisimilarityIsReflexive(f(e)); - BisimilarityIsReflexive(g(e)); - UniqueCoalgebraHomomorphismHelper(f, g, f(e), g(e)); - } +module Semantics2 { + import opened Semantics0 + import opened Semantics1 + import opened Languages4 - lemma UniqueCoalgebraHomomorphismHelper(f: Exp -> Lang, g: Exp -> Lang, L1: Lang, L2: Lang) - requires IsCoalgebraHomomorphism(f) - requires IsCoalgebraHomomorphism(g) - requires exists e :: Bisimilar(L1, f(e)) && Bisimilar(L2, g(e)) - ensures Bisimilar(L1, L2) + lemma DenotationalIsAlgebraHomomorphism() + ensures IsAlgebraHomomorphism(Denotational) { - forall k: nat - ensures Bisimilar#[k](L1, L2) + forall e + ensures IsAlgebraHomomorphismPointwise(Denotational, e) { - if k != 0 { - UniqueCoalgebraHomomorphismHelperPointwise(k, f, g, L1, L2); - } + BisimilarityIsReflexive(Denotational(e)); } } +} - lemma UniqueCoalgebraHomomorphismHelperPointwise(k: nat, f: Exp -> Lang, g: Exp -> Lang, L1: Lang, L2: Lang) - requires IsCoalgebraHomomorphism(f) - requires IsCoalgebraHomomorphism(g) - requires exists e :: Bisimilar#[k](L1, f(e)) && Bisimilar#[k](L2, g(e)) - ensures Bisimilar#[k](L1, L2) - { - var e :| Bisimilar#[k](L1, f(e)) && Bisimilar#[k](L2, g(e)); - if k != 0 { - forall a - ensures Bisimilar#[k-1](L1.delta(a), L2.delta(a)) - { - BisimilarityIsTransitivePointwise(k-1, L1.delta(a), f(e).delta(a), f(Delta(e)(a))); - BisimilarityIsTransitivePointwise(k-1, L2.delta(a), g(e).delta(a), g(Delta(e)(a))); - UniqueCoalgebraHomomorphismHelperPointwise(k-1, f, g, L1.delta(a), L2.delta(a)); - } - } +module Semantics3 { + import opened Expressions0 + import opened Expressions1 + import opened Languages0 + + function Operational(e: Exp): Lang { + Alpha(Eps(e), (a: A) => Operational(Delta(e)(a))) } +} - /* Denotational is an algebra homomorphism */ +module Semantics4 { + import opened Expressions0 + import opened Expressions1 + import opened Languages0 + import opened Languages3 - lemma DenotationalIsAlgebraHomomorphism() - ensures IsAlgebraHomomorphism(Denotational) + ghost predicate IsCoalgebraHomomorphism(f: Exp -> Lang) { + && (forall e :: f(e).eps == Eps(e)) + && (forall e, a :: Bisimilar(f(e).delta(a), f(Delta(e)(a)))) + } +} + +module Semantics5 { + import opened Semantics3 + import opened Semantics4 + import opened Languages3 + import opened Languages4 + import opened Expressions1 + + lemma OperationalIsCoalgebraHomomorphism() + ensures IsCoalgebraHomomorphism(Operational) { - forall e - ensures IsAlgebraHomomorphismPointwise(Denotational, e) + forall e, a + ensures Bisimilar(Operational(e).delta(a), Operational(Delta(e)(a))) { - BisimilarityIsReflexive(Denotational(e)); + BisimilarityIsReflexive(Operational(e).delta(a)); } } +} - /* Denotational is a coalgebra homomorphism */ +module Semantics6 { + import opened Semantics0 + import opened Semantics1 + import opened Semantics2 + import opened Semantics3 + import opened Semantics4 + + lemma DenotationalIsCoalgebraHomomorphism() + ensures IsCoalgebraHomomorphism(Denotational) +} + +module Semantics6WithProof refines Semantics6 { + import opened Expressions0 + import opened Expressions1 + import opened Languages0 + import opened Languages3 + import opened Languages4 + import opened Languages5 lemma DenotationalIsCoalgebraHomomorphism() ensures IsCoalgebraHomomorphism(Denotational) @@ -147,13 +152,13 @@ module Semantics { ensures Bisimilar(Denotational(e).delta(a), Denotational(Delta(e)(a))) { match e - case Zero => BisimilarityIsReflexive(Languages.Zero()); - case One => BisimilarityIsReflexive(Languages.One()); + case Zero => BisimilarityIsReflexive(Languages1.Zero()); + case One => BisimilarityIsReflexive(Languages2.One()); case Char(b) => if a == b { - BisimilarityIsReflexive(Languages.One()); + BisimilarityIsReflexive(Languages2.One()); } else { - BisimilarityIsReflexive(Languages.Zero()); + BisimilarityIsReflexive(Languages1.Zero()); } case Plus(e1, e2) => DenotationalIsCoalgebraHomomorphismHelper2(e1, a); @@ -164,7 +169,7 @@ module Semantics { DenotationalIsCoalgebraHomomorphismHelper2(e1, a); DenotationalIsCoalgebraHomomorphismHelper2(e2, a); BisimilarityIsReflexive(Denotational(e2)); - BisimilarityIsReflexive(if Eps(e1) then Languages.One() else Languages.Zero()); + BisimilarityIsReflexive(if Eps(e1) then Languages2.One() else Languages1.Zero()); CompCongruence( Denotational(e1).delta(a), Denotational(Delta(e1)(a)), @@ -172,30 +177,148 @@ module Semantics { Denotational(e2) ); CompCongruence( - if Eps(e1) then Languages.One() else Languages.Zero(), - if Eps(e1) then Languages.One() else Languages.Zero(), + if Eps(e1) then Languages2.One() else Languages1.Zero(), + if Eps(e1) then Languages2.One() else Languages1.Zero(), Denotational(e2).delta(a), Denotational(Delta(e2)(a)) ); PlusCongruence( - Languages.Comp(Denotational(e1).delta(a), Denotational(e2)), - Languages.Comp(Denotational(Delta(e1)(a)), Denotational(e2)), - Languages.Comp(if Eps(e1) then Languages.One() else Languages.Zero(), Denotational(e2).delta(a)), - Languages.Comp(if Eps(e1) then Languages.One() else Languages.Zero(), Denotational(Delta(e2)(a))) + Languages2.Comp(Denotational(e1).delta(a), Denotational(e2)), + Languages2.Comp(Denotational(Delta(e1)(a)), Denotational(e2)), + Languages2.Comp(if Eps(e1) then Languages2.One() else Languages1.Zero(), Denotational(e2).delta(a)), + Languages2.Comp(if Eps(e1) then Languages2.One() else Languages1.Zero(), Denotational(Delta(e2)(a))) ); case Star(e1) => DenotationalIsCoalgebraHomomorphismHelper2(e1, a); - BisimilarityIsReflexive(Languages.Star(Denotational(e1))); - CompCongruence(Denotational(e1).delta(a), Denotational(Delta(e1)(a)), Languages.Star(Denotational(e1)), Languages.Star(Denotational(e1))); + BisimilarityIsReflexive(Languages2.Star(Denotational(e1))); + CompCongruence(Denotational(e1).delta(a), Denotational(Delta(e1)(a)), Languages2.Star(Denotational(e1)), Languages2.Star(Denotational(e1))); } +} + +module Semantics7 { + import opened Expressions0 + import opened Languages0 + import opened Languages3 + import opened Semantics4 + + lemma UniqueCoalgebraHomomorphism(f: Exp -> Lang, g: Exp -> Lang, e: Exp) + requires IsCoalgebraHomomorphism(f) + requires IsCoalgebraHomomorphism(g) + ensures Bisimilar(f(e), g(e)) +} - /* Operational is an algebra homomorphism */ +module Semantics7WithProof refines Semantics7 { + import opened Languages4 + import opened Semantics8 + + lemma UniqueCoalgebraHomomorphism(f: Exp -> Lang, g: Exp -> Lang, e: Exp) + ensures Bisimilar(f(e), g(e)) + { + BisimilarityIsReflexive(f(e)); + BisimilarityIsReflexive(g(e)); + UniqueCoalgebraHomomorphismHelper(f, g, f(e), g(e)); + } + + lemma UniqueCoalgebraHomomorphismHelper(f: Exp -> Lang, g: Exp -> Lang, L1: Lang, L2: Lang) + requires IsCoalgebraHomomorphism(f) + requires IsCoalgebraHomomorphism(g) + requires exists e :: Bisimilar(L1, f(e)) && Bisimilar(L2, g(e)) + ensures Bisimilar(L1, L2) + { + forall k: nat + ensures Bisimilar#[k](L1, L2) + { + if k != 0 { + UniqueCoalgebraHomomorphismHelperPointwise(k, f, g, L1, L2); + } + } + } +} + +module Semantics8 { + import opened Expressions0 + import opened Expressions1 + import opened Languages0 + import opened Languages3 + import opened Semantics4 + + lemma UniqueCoalgebraHomomorphismHelperPointwise(k: nat, f: Exp -> Lang, g: Exp -> Lang, L1: Lang, L2: Lang) + requires IsCoalgebraHomomorphism(f) + requires IsCoalgebraHomomorphism(g) + requires exists e :: Bisimilar#[k](L1, f(e)) && Bisimilar#[k](L2, g(e)) + ensures Bisimilar#[k](L1, L2) + { + var e :| Bisimilar#[k](L1, f(e)) && Bisimilar#[k](L2, g(e)); + if k != 0 { + forall a + ensures Bisimilar#[k-1](L1.delta(a), L2.delta(a)) + { + BisimilarityIsTransitivePointwise(k-1, L1.delta(a), f(e).delta(a), f(Delta(e)(a))); + BisimilarityIsTransitivePointwise(k-1, L2.delta(a), g(e).delta(a), g(Delta(e)(a))); + UniqueCoalgebraHomomorphismHelperPointwise(k-1, f, g, L1.delta(a), L2.delta(a)); + } + } + } + + lemma BisimilarityIsTransitivePointwise(k: nat, L1: Lang, L2: Lang, L3: Lang) + ensures Bisimilar#[k](L1, L2) && Bisimilar#[k](L2, L3) ==> Bisimilar#[k](L1, L3) + { + if k != 0 { + if Bisimilar#[k](L1, L2) && Bisimilar#[k](L2, L3) { + assert Bisimilar#[k](L1, L3) by { + forall a + ensures Bisimilar#[k-1](L1.delta(a), L3.delta(a)) + { + BisimilarityIsTransitivePointwise(k-1, L1.delta(a), L2.delta(a), L3.delta(a)); + } + } + } + } + } +} + +module Semantics9 { + import opened Expressions0 + import opened Languages3 + import opened Semantics5 + import opened Semantics0 + import opened Semantics3 + import opened Semantics6WithProof + import opened Semantics7WithProof + + lemma OperationalAndDenotationalAreBisimilar(e: Exp) + ensures Bisimilar(Operational(e), Denotational(e)) + { + OperationalIsCoalgebraHomomorphism(); + DenotationalIsCoalgebraHomomorphism(); + UniqueCoalgebraHomomorphism(Operational, Denotational, e); + } +} + +module Semantics10 { + import opened Semantics1 + import opened Semantics3 + + lemma OperationalIsAlgebraHomomorphism() + ensures IsAlgebraHomomorphism(Operational) +} + +module Semantics10WithProof refines Semantics10 { + import opened Expressions0 + import opened Semantics0 + import opened Semantics2 + import opened Semantics9 + import opened Languages1 + import opened Languages2 + import opened Languages6 + import opened Languages5 + import opened Languages7 lemma OperationalIsAlgebraHomomorphism() ensures IsAlgebraHomomorphism(Operational) { - forall e - ensures IsAlgebraHomomorphismPointwise(Operational, e) + forall e + ensures IsAlgebraHomomorphismPointwise(Operational, e) { OperationalAndDenotationalAreBisimilar(e); assert IsAlgebraHomomorphismPointwise(Denotational, e) by { @@ -203,56 +326,33 @@ module Semantics { } match e case Zero => - BisimilarityIsTransitive(Operational(Zero), Denotational(Zero), Languages.Zero()); + BisimilarityIsTransitive(Operational(Zero), Denotational(Zero), Languages1.Zero()); case One => - BisimilarityIsTransitive(Operational(One), Denotational(One), Languages.One()); + BisimilarityIsTransitive(Operational(One), Denotational(One), Languages2.One()); case Char(a) => - BisimilarityIsTransitive(Operational(Char(a)), Denotational(Char(a)), Languages.Singleton(a)); + BisimilarityIsTransitive(Operational(Char(a)), Denotational(Char(a)), Languages2.Singleton(a)); case Plus(e1, e2) => - BisimilarityIsTransitive(Operational(Plus(e1, e2)), Denotational(Plus(e1, e2)), Languages.Plus(Denotational(e1), Denotational(e2))); + BisimilarityIsTransitive(Operational(Plus(e1, e2)), Denotational(Plus(e1, e2)), Languages2.Plus(Denotational(e1), Denotational(e2))); OperationalAndDenotationalAreBisimilar(e1); BisimilarityIsSymmetric(Denotational(e1), Operational(e1)); OperationalAndDenotationalAreBisimilar(e2); BisimilarityIsSymmetric(Denotational(e2), Operational(e2)); PlusCongruence(Denotational(e1), Operational(e1), Denotational(e2), Operational(e2)); - BisimilarityIsTransitive(Operational(Plus(e1, e2)), Languages.Plus(Denotational(e1), Denotational(e2)), Languages.Plus(Operational(e1), Operational(e2))); + BisimilarityIsTransitive(Operational(Plus(e1, e2)), Languages2.Plus(Denotational(e1), Denotational(e2)), Languages2.Plus(Operational(e1), Operational(e2))); case Comp(e1, e2) => - BisimilarityIsTransitive(Operational(Comp(e1, e2)), Denotational(Comp(e1, e2)), Languages.Comp(Denotational(e1), Denotational(e2))); + BisimilarityIsTransitive(Operational(Comp(e1, e2)), Denotational(Comp(e1, e2)), Languages2.Comp(Denotational(e1), Denotational(e2))); OperationalAndDenotationalAreBisimilar(e1); BisimilarityIsSymmetric(Denotational(e1), Operational(e1)); OperationalAndDenotationalAreBisimilar(e2); BisimilarityIsSymmetric(Denotational(e2), Operational(e2)); CompCongruence(Denotational(e1), Operational(e1), Denotational(e2), Operational(e2)); - BisimilarityIsTransitive(Operational(Comp(e1, e2)), Languages.Comp(Denotational(e1), Denotational(e2)), Languages.Comp(Operational(e1), Operational(e2))); + BisimilarityIsTransitive(Operational(Comp(e1, e2)), Languages2.Comp(Denotational(e1), Denotational(e2)), Languages2.Comp(Operational(e1), Operational(e2))); case Star(e1) => - BisimilarityIsTransitive(Operational(Star(e1)), Denotational(Star(e1)), Languages.Star(Denotational(e1))); + BisimilarityIsTransitive(Operational(Star(e1)), Denotational(Star(e1)), Languages2.Star(Denotational(e1))); OperationalAndDenotationalAreBisimilar(e1); BisimilarityIsSymmetric(Denotational(e1), Operational(e1)); StarCongruence(Denotational(e1), Operational(e1)); - BisimilarityIsTransitive(Operational(Star(e1)), Languages.Star(Denotational(e1)), Languages.Star(Operational(e1))); + BisimilarityIsTransitive(Operational(Star(e1)), Languages2.Star(Denotational(e1)), Languages2.Star(Operational(e1))); } } - - /* Operational is a coalgebra homomorphism */ - - lemma OperationalIsCoalgebraHomomorphism() - ensures IsCoalgebraHomomorphism(Operational) - { - forall e, a - ensures Bisimilar(Operational(e).delta(a), Operational(Delta(e)(a))) - { - BisimilarityIsReflexive(Operational(e).delta(a)); - } - } - - /* Operational and Denotational are equal, up to bisimulation */ - - lemma OperationalAndDenotationalAreBisimilar(e: Exp) - ensures Bisimilar(Operational(e), Denotational(e)) - { - OperationalIsCoalgebraHomomorphism(); - DenotationalIsCoalgebraHomomorphism(); - UniqueCoalgebraHomomorphism(Operational, Denotational, e); - } - } \ No newline at end of file From abd80af73e17bc992f613cec1519229ac0c6b382 Mon Sep 17 00:00:00 2001 From: stefan-aws Date: Wed, 10 Jan 2024 13:48:49 +0000 Subject: [PATCH 60/62] new line --- .../semantics-of-regular-expressions.html | 75 +++++++++---------- .../Expressions.dfy | 2 +- 2 files changed, 38 insertions(+), 39 deletions(-) diff --git a/_includes/semantics-of-regular-expressions.html b/_includes/semantics-of-regular-expressions.html index a970dc7..a37b32d 100644 --- a/_includes/semantics-of-regular-expressions.html +++ b/_includes/semantics-of-regular-expressions.html @@ -510,31 +510,30 @@

case Plus(e1, e2) => Plus(Delta(e1)(a), Delta(e2)(a)) case Comp(e1, e2) => Plus(Comp(Delta(e1)(a), e2), Comp(if Eps(e1) then One else Zero, Delta(e2)(a))) case Star(e1) => Comp(Delta(e1)(a), Star(e1)) - } -}

Operational Semantics as Induced Morphism

-

The operational semantics of regular expressions can now be defined via coinduction, as a function Operational: Exp -> Lang, by making use of the coalgebraic structure on expressions we have just defined: + }

Operational Semantics as Induced Morphism

+

The operational semantics of regular expressions can now be defined via coinduction, as a function Operational: Exp -> Lang, by making use of the coalgebraic structure on expressions we have just defined:

-
  function Operational<A(==)>(e: Exp): Lang {
+
  function Operational<A(==)>(e: Exp): Lang {
     Alpha(Eps(e), (a: A) => Operational(Delta(e)(a)))
-  }

Operational Semantics as Coalgebra Homomorphism

-

In Denotational Semantics as Algebra Homomorphism we defined algebra homomorphisms as functions f: Exp -> Lang that commute with the algebraic structures of regular expressions and formal languages, respectively. Analogously, let us now call a function of the same type a coalgebra homomorphism, if it commutes with the coalgebraic structures of regular expressions and formal languages, respectively: + }

Operational Semantics as Coalgebra Homomorphism

+

In Denotational Semantics as Algebra Homomorphism we defined algebra homomorphisms as functions f: Exp -> Lang that commute with the algebraic structures of regular expressions and formal languages, respectively. Analogously, let us now call a function of the same type a coalgebra homomorphism, if it commutes with the coalgebraic structures of regular expressions and formal languages, respectively:

-
  ghost predicate IsCoalgebraHomomorphism<A(!new)>(f: Exp -> Lang) {
+
  ghost predicate IsCoalgebraHomomorphism<A(!new)>(f: Exp -> Lang) {
     && (forall e :: f(e).eps == Eps(e))
     && (forall e, a :: Bisimilar(f(e).delta(a), f(Delta(e)(a))))
   }
-

It is straightforward to prove that Operational is a coalgebra homomorphism: once again, the central argument is that bisimilarity is a reflexive relation. +

It is straightforward to prove that Operational is a coalgebra homomorphism: once again, the central argument is that bisimilarity is a reflexive relation.

-
  lemma OperationalIsCoalgebraHomomorphism<A(!new)>()
+
  lemma OperationalIsCoalgebraHomomorphism<A(!new)>()
     ensures IsCoalgebraHomomorphism<A>(Operational)
   {
     forall e, a
@@ -542,30 +541,30 @@ 

Well-Behaved Semantics

-

So far, we have seen two dual approaches for assigning a formal language semantics to regular expressions: + }

Well-Behaved Semantics

+

So far, we have seen two dual approaches for assigning a formal language semantics to regular expressions:

-
    -
  • Denotational: an algebra homomorphism obtained via induction +
      +
    • Denotational: an algebra homomorphism obtained via induction
    • -
    • Operational: a coalgebra homomorphism obtained via coinduction +
    • Operational: a coalgebra homomorphism obtained via coinduction
    -

    Next, we show that the denotational and operational semantics of regular expressions are well-behaved: they constitute two sides of the same coin. First, we show that Denotational is also a coalgebra homomorphism, and that coalgebra homomorphisms are unique up to bisimulation. We then deduce from the former that Denotational and Operational coincide pointwise, up to bisimulation. Finally, we show that Operational is also an algebra homomorphism. -

    Denotational Semantics as Coalgebra Homomorphism

    -

    In this section, we establish that Denotational not only commutes with the algebraic structures of regular expressions and formal languages, but also with their coalgebraic structures: +

    Next, we show that the denotational and operational semantics of regular expressions are well-behaved: they constitute two sides of the same coin. First, we show that Denotational is also a coalgebra homomorphism, and that coalgebra homomorphisms are unique up to bisimulation. We then deduce from the former that Denotational and Operational coincide pointwise, up to bisimulation. Finally, we show that Operational is also an algebra homomorphism. +

    Denotational Semantics as Coalgebra Homomorphism

    +

    In this section, we establish that Denotational not only commutes with the algebraic structures of regular expressions and formal languages, but also with their coalgebraic structures:

    -
      lemma DenotationalIsCoalgebraHomomorphism<A(!new)>()
    +
      lemma DenotationalIsCoalgebraHomomorphism<A(!new)>()
         ensures IsCoalgebraHomomorphism<A>(Denotational)
    -

    The proof of the lemma is a bit more elaborate than the ones we have encountered so far. It can be divided into two subproofs, both of which make use of induction. One of the subproofs is straightforward, the other, more difficult one, again uses the reflexivity of bisimilarity, but also that the latter is a congruence relation with respect to Plus and Comp: +

    The proof of the lemma is a bit more elaborate than the ones we have encountered so far. It can be divided into two subproofs, both of which make use of induction. One of the subproofs is straightforward, the other, more difficult one, again uses the reflexivity of bisimilarity, but also that the latter is a congruence relation with respect to Plus and Comp:

    -
      greatest lemma PlusCongruence<A(!new)>[nat](L1a: Lang, L1b: Lang, L2a: Lang, L2b: Lang)
    +
      greatest lemma PlusCongruence<A(!new)>[nat](L1a: Lang, L1b: Lang, L2a: Lang, L2b: Lang)
         requires Bisimilar(L1a, L1b)
         requires Bisimilar(L2a, L2b)
         ensures Bisimilar(Plus(L1a, L2a), Plus(L1b, L2b))
    @@ -575,32 +574,32 @@ 

    requires Bisimilar(L1a, L1b) requires Bisimilar(L2a, L2b) ensures Bisimilar(Comp(L1a, L2a), Comp(L1b, L2b))

    -

    Dafny is able to prove PlusCongruence on its own, as it can take advantage of the syntactic sugaring of the greatest lemma construct. For CompCongruence we have to put in a bit of manual work ourselves. -

    Coalgebra Homomorphisms Are Unique

    -

    The aim of this section is to show that, up to pointwise bisimilarity, there only exists one coalgebra homomorphism of type Exp -> Lang: +

    Dafny is able to prove PlusCongruence on its own, as it can take advantage of the syntactic sugaring of the greatest lemma construct. For CompCongruence we have to put in a bit of manual work ourselves. +

    Coalgebra Homomorphisms Are Unique

    +

    The aim of this section is to show that, up to pointwise bisimilarity, there only exists one coalgebra homomorphism of type Exp -> Lang:

    -
      lemma UniqueCoalgebraHomomorphism<A(!new)>(f: Exp -> Lang, g: Exp -> Lang, e: Exp)
    +
      lemma UniqueCoalgebraHomomorphism<A(!new)>(f: Exp -> Lang, g: Exp -> Lang, e: Exp)
         requires IsCoalgebraHomomorphism(f)
         requires IsCoalgebraHomomorphism(g)
         ensures Bisimilar(f(e), g(e))
    -

    As is well-known, the statement may in fact be strengthened to: for any coalgebra C there exists exactly one coalgebra homomorphism of type C -> Lang , up to pointwise bisimulation. For our purposes, the weaker statement above will be sufficient. At the heart of the proof lies the observation that bisimilarity is transitive: +

    As is well-known, the statement may in fact be strengthened to: for any coalgebra C there exists exactly one coalgebra homomorphism of type C -> Lang , up to pointwise bisimulation. For our purposes, the weaker statement above will be sufficient. At the heart of the proof lies the observation that bisimilarity is transitive:

    -
      greatest lemma BisimilarityIsTransitive<A>[nat](L1: Lang, L2: Lang, L3: Lang)
    +
      greatest lemma BisimilarityIsTransitive<A>[nat](L1: Lang, L2: Lang, L3: Lang)
         requires Bisimilar(L1, L2) && Bisimilar(L2, L3)
         ensures Bisimilar(L1, L3)
       {}
    -

    In fact, in practice, we actually use a slightly more fine grained formalisation of transitivity, as is illustrated below by the proof of UniqueCoalgebraHomomorphismHelperPointwise, which is used in the proof of UniqueCoalgebraHomomorphism: +

    In fact, in practice, we actually use a slightly more fine grained formalisation of transitivity, as is illustrated below by the proof of UniqueCoalgebraHomomorphismHelperPointwise, which is used in the proof of UniqueCoalgebraHomomorphism:

    -
      lemma UniqueCoalgebraHomomorphismHelperPointwise<A(!new)>(k: nat, f: Exp -> Lang, g: Exp -> Lang, L1: Lang, L2: Lang)
    +
      lemma UniqueCoalgebraHomomorphismHelperPointwise<A(!new)>(k: nat, f: Exp -> Lang, g: Exp -> Lang, L1: Lang, L2: Lang)
         requires IsCoalgebraHomomorphism(f)
         requires IsCoalgebraHomomorphism(g)
         requires exists e :: Bisimilar#[k](L1, f(e)) && Bisimilar#[k](L2, g(e))
    @@ -632,40 +631,40 @@ 

    Denotational and Operational Semantics Are Bisimilar

    -

    We are done! From the previous results, we can immediately deduce that denotational and operational semantics are the same, up to pointwise bisimilarity: + }

    Denotational and Operational Semantics Are Bisimilar

    +

    We are done! From the previous results, we can immediately deduce that denotational and operational semantics are the same, up to pointwise bisimilarity:

    -
      lemma OperationalAndDenotationalAreBisimilar<A(!new)>(e: Exp)
    +
      lemma OperationalAndDenotationalAreBisimilar<A(!new)>(e: Exp)
         ensures Bisimilar<A>(Operational(e), Denotational(e))
       {
         OperationalIsCoalgebraHomomorphism<A>();
         DenotationalIsCoalgebraHomomorphism<A>();
         UniqueCoalgebraHomomorphism<A>(Operational, Denotational, e);
    -  }

    Operational Semantics as Algebra Homomorphism

    -

    As a bonus, for the sake of symmetry, let us also prove that Operational is an algebra homomorphism. (We already know that it is a coalgebra homomorphism, and that Denotational is both an algebra and coalgebra homomorphism.) + }

    Operational Semantics as Algebra Homomorphism

    +

    As a bonus, for the sake of symmetry, let us also prove that Operational is an algebra homomorphism. (We already know that it is a coalgebra homomorphism, and that Denotational is both an algebra and coalgebra homomorphism.)

    -
      lemma OperationalIsAlgebraHomomorphism<A(!new)>()
    +
      lemma OperationalIsAlgebraHomomorphism<A(!new)>()
         ensures IsAlgebraHomomorphism<A>(Operational)
    -

    The idea of the proof is to take advantage of Denotational being an algebra homomorphism, by translating its properties to Operational via the lemma in Denotational and Operational Semantics Are Bisimilar. The relevant new statements capture that bisimilarity is symmetric and a congruence with respect to the Star operation: +

    The idea of the proof is to take advantage of Denotational being an algebra homomorphism, by translating its properties to Operational via the lemma in Denotational and Operational Semantics Are Bisimilar. The relevant new statements capture that bisimilarity is symmetric and a congruence with respect to the Star operation:

    -
      greatest lemma BisimilarityIsSymmetric<A(!new)>[nat](L1: Lang, L2: Lang)
    +
      greatest lemma BisimilarityIsSymmetric<A(!new)>[nat](L1: Lang, L2: Lang)
         ensures Bisimilar(L1, L2) ==> Bisimilar(L2, L1)
         ensures Bisimilar(L1, L2) <== Bisimilar(L2, L1)
       {}
     
       lemma StarCongruence<A(!new)>(L1: Lang, L2: Lang)
         requires Bisimilar(L1, L2)
    -    ensures Bisimilar(Star(L1), Star(L2))

    Conclusion

    -

    We have used Dafny’s built-in inductive and coinductive reasoning capabilities to define two language semantics for regular expressions: denotational and operational semantics. Through a number of dualities — construction and deconstruction, algebras and coalgebras, and congruence and bisimilarity — we have proven the semantics to be two sides of the same coin. The blogpost is inspired by research in the field of Coalgebra, which was pioneered by Rutten, Gumm, and others. The concept of well-behaved semantics goes back to Turi and Plotkin and was adapted by Jacobs to the case of regular expressions. We heavily used automata theoretic constructions from the 1960s, originally investigated by Brzozowski (a more modern presentation can be found e.g. here). Our presentation focused on the most important intuitive aspects of the proofs. To dive deep, please take a look at the full Dafny source code, which is available here, here, and here. + ensures Bisimilar(Star(L1), Star(L2))

    Conclusion

    +

    We have used Dafny’s built-in inductive and coinductive reasoning capabilities to define two language semantics for regular expressions: denotational and operational semantics. Through a number of dualities — construction and deconstruction, algebras and coalgebras, and congruence and bisimilarity — we have proven the semantics to be two sides of the same coin. The blogpost is inspired by research in the field of Coalgebra, which was pioneered by Rutten, Gumm, and others. The concept of well-behaved semantics goes back to Turi and Plotkin and was adapted by Jacobs to the case of regular expressions. We heavily used automata theoretic constructions from the 1960s, originally investigated by Brzozowski (a more modern presentation can be found e.g. here). Our presentation focused on the most important intuitive aspects of the proofs. To dive deep, please take a look at the full Dafny source code, which is available here, here, and here.

    diff --git a/assets/src/semantics-of-regular-expressions/Expressions.dfy b/assets/src/semantics-of-regular-expressions/Expressions.dfy index 1e12fc4..3bde096 100644 --- a/assets/src/semantics-of-regular-expressions/Expressions.dfy +++ b/assets/src/semantics-of-regular-expressions/Expressions.dfy @@ -25,4 +25,4 @@ module Expressions1 { case Comp(e1, e2) => Plus(Comp(Delta(e1)(a), e2), Comp(if Eps(e1) then One else Zero, Delta(e2)(a))) case Star(e1) => Comp(Delta(e1)(a), Star(e1)) } -} \ No newline at end of file +} From f6b6b90601f32bb0541a4019b0dee4692271f962 Mon Sep 17 00:00:00 2001 From: stefan-aws Date: Thu, 11 Jan 2024 14:34:05 +0000 Subject: [PATCH 61/62] change date --- ...own => 2024-01-11-semantics-of-regular-expressions.markdown} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename _posts/{2024-01-10-semantics-of-regular-expressions.markdown => 2024-01-11-semantics-of-regular-expressions.markdown} (85%) diff --git a/_posts/2024-01-10-semantics-of-regular-expressions.markdown b/_posts/2024-01-11-semantics-of-regular-expressions.markdown similarity index 85% rename from _posts/2024-01-10-semantics-of-regular-expressions.markdown rename to _posts/2024-01-11-semantics-of-regular-expressions.markdown index 5795659..23b3de1 100644 --- a/_posts/2024-01-10-semantics-of-regular-expressions.markdown +++ b/_posts/2024-01-11-semantics-of-regular-expressions.markdown @@ -2,7 +2,7 @@ layout: post title: "Well-Behaved (Co)algebraic Semantics of Regular Expressions in Dafny" author: Stefan Zetzsche and Wojciech Różowski -date: 2024-01-10 18:00:00 +0100 +date: 2024-01-11 18:00:00 +0100 --- {% include semantics-of-regular-expressions.html %} \ No newline at end of file From 616dd0f03156e0a7fa0ff9d056cb09e83d197b75 Mon Sep 17 00:00:00 2001 From: stefan-aws Date: Fri, 12 Jan 2024 15:39:25 +0000 Subject: [PATCH 62/62] change date --- ...own => 2024-01-12-semantics-of-regular-expressions.markdown} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename _posts/{2024-01-11-semantics-of-regular-expressions.markdown => 2024-01-12-semantics-of-regular-expressions.markdown} (85%) diff --git a/_posts/2024-01-11-semantics-of-regular-expressions.markdown b/_posts/2024-01-12-semantics-of-regular-expressions.markdown similarity index 85% rename from _posts/2024-01-11-semantics-of-regular-expressions.markdown rename to _posts/2024-01-12-semantics-of-regular-expressions.markdown index 23b3de1..119463f 100644 --- a/_posts/2024-01-11-semantics-of-regular-expressions.markdown +++ b/_posts/2024-01-12-semantics-of-regular-expressions.markdown @@ -2,7 +2,7 @@ layout: post title: "Well-Behaved (Co)algebraic Semantics of Regular Expressions in Dafny" author: Stefan Zetzsche and Wojciech Różowski -date: 2024-01-11 18:00:00 +0100 +date: 2024-01-12 18:00:00 +0100 --- {% include semantics-of-regular-expressions.html %} \ No newline at end of file