Encoding Intuitionistic Logic proofs
Table of Contents
1. Review
We've now begun encoding propositional logic proofs in Haskell. Doing this involved a few steps, which we rehash here in turn.
1.1. Formulae
We defined formulae using the standard way of defining a set of syntactic objects in Haskell, i.e., in terms of an algebraic data type. Given a set of atomic formulae
data Atom = P | Q | R deriving (Show)
we can define the set of all formulae as a data type Form:
data Form = At Atom -- atomic formulae | Form :/\ Form -- conjunctions | Form :\/ Form -- disjunctions | Form :-> Form -- implications | Taut -- tautology | Contr -- contradiction deriving (Show)
For example, we represent the formula \(p ∧ q\) as the expression At P :/\ At
Q, the formula \(p → q\) as At P :-> At Q, the formula \(p ∨ (q → r)\) as At P
:\/ (At Q :-> At R), and so on, all of these expressions being of type Form.
1.2. Being in a context
Ultimately, we want to have a way of encoding judgments of the form \(Γ ⊢ φ\). But before we get there, we need to go about encoding contexts \(Γ\), as well as talk about what it means for some assumption \(x : φ\) to be in a context \(Γ\).
As for contexts, we can encode them as an algebraic data type analogous to lists:
data Context = Empty | Cons Form Context
With contexts at hand, we can begin talking, in Haskell, about what it means for some assumption to be in a context. In particular, we use the following encoding of ways of being in a context as a generalized algebraic data type (GADT):
data In (φ :: Form) (γ :: Context) where First :: In φ (Cons φ γ) Next :: In φ γ -> In φ (Cons ψ γ)
We have done a couple of novel things here, which require particular Haskell
language extensions. First, we've encoded the data type In φ γ as a GADT,
necessitating the following language extension, which should go at the top of
your Haskell file:
{-# LANGUAGE GADTs #-}
Second, we have begun using data types themselves, i.e., Form and Context as
the types of parameters of a new type constructor In.1
Note that this is very different from how data types are usually
defined—even data types like List a:
data List a = Empty | Cons a (List a)
Here, the a in List a is a type (specifically, it can be any type). But the φ
of In φ γ isn't a type; it's a Form! Similarly, the γ of In φ γ isn't a type
either; it's a Context! In order to allow not only types but data to serve as
the parameters of data types, we need to turn on two other language
extensions:
{-# LANGUAGE DataKinds #-} {-# LANGUAGE KindSignatures #-}
The first language extension, DataKinds, allows data to be promoted to the
same level as types; i.e., so that it can serve as the parameter of a type
constructor, for example, in the way that a formula φ can be the parameter of
the type constructor In. The second, KindSignatures, allows new data types to
be declared by annotating their parameters with what kind of data they should
be (e.g., (φ :: Form) and (γ :: Context) above).
Thus what the data type In φ γ encodes is a way of an index into some context
γ which correspond to an assumption of φ. For example, if you were to request
the type of First in your ghci repl, you would get
>>> :t First First :: In φ (Cons φ γ)
meaning that First just indexes the first element of the context Cons φ γ. If
you were to request the type of, e.g., Next First, you would get
>>> :t Next First Next First :: In φ (Cons ψ (Cons φ γ))
meaning that Next First indexes the second element of the context Cons ψ
(Cons φ γ). And so on. In other words, the data type In φ γ just gives us all
the possible ways that some formula φ could be in a context γ; i.e., by being
the first element, the second element, etc.
1.3. Building proofs
We can continue by declaring a data type to encode proofs. In general, the constructors of this data type act as functions: they either take a way of being in a context onto an axiom that crucially uses that way of being in a context, or they take old proofs onto new proofs. In the former case, we are simply encoding the axiom rule: \[\frac{φ ∈ Γ}{Γ ⊢ φ}\mathtt{Ax}\] or, translated into Haskell,
data Proof (γ :: Context) (φ :: Form) where Ax :: In φ γ -> Proof γ φ
In the latter case, we are encoding the rules of Intuitionistic Logic, e.g.,
\(∧\mathtt{I}\), \(∨\mathtt{E}\), etc. As we saw in class, we can continue the
declaration of Proof γ φ by encoding the rules \(∨\mathtt{I}_L\),
\(∨\mathtt{I}_R\), and \(∨\mathtt{E}\):
data Proof (γ :: Context) (φ :: Form) where Ax :: In φ γ -> Proof γ φ OrIntroL :: Proof γ φ -> Proof γ (φ :\/ ψ) OrIntroR :: Proof γ ψ -> Proof γ (φ :\/ ψ) OrElim :: Proof γ (φ :\/ ψ) -> Proof (Cons φ γ) χ -> Proof (Cons ψ γ) χ -> Proof γ χ
2. Exercises
Finish declaring Proof γ φ. That is, fill in the following types:
data Proof (γ :: Context) (φ :: Form) where Ax :: In φ γ -> Proof γ φ -- Ax OrIntroL :: Proof γ φ -> Proof γ (φ :\/ ψ) -- ∨I_L OrIntroR :: Proof γ ψ -> Proof γ (φ :\/ ψ) -- ∨I_R OrElim :: Proof γ (φ :\/ ψ) -> Proof (Cons φ γ) χ -> Proof (Cons ψ γ) χ -> Proof γ χ -- ∨E AndIntro :: _ -- ∧I AndElimL :: _ -- ∧E_L AndElimR :: _ -- ∧E_R ArrIntro :: _ -- →I ArrElim :: _ -- →E TautIntro :: _ -- ⊤I ContrElim :: _ -- ⊥E
Refer to the rules!