1

I am studying Curry–Howard correspondence.

Given propositional logic statement: (¬p -> q) -> ((¬p -> ¬q) -> p).

I need to define a type (as proposition) and a function (as a proof) in OCaml.

I came up with the next code and stuck:

type empty = | ;; 
let ex58: (('p->empty) -> 'q) -> (('p->empty) -> ('q->empty)) -> 'p = fun f g -> g(f)

Error:

This expression has type ('p -> empty) -> 'q but an expression was expected of type 'p -> empty.
Chris
  • 26,361
  • 5
  • 21
  • 42
Oleg Dats
  • 3,933
  • 9
  • 38
  • 61
  • 1
    It's an exercise number 58 from some book? What book is it? I'm pretty sure that it's not intuitionistically valid. – Andrey Tyukin Dec 21 '21 at 19:22
  • You are right. The proof involves: Double negation elimination of p. I did not know it before posting the question. Ex 5.8 http://intrologic.stanford.edu/chapters/chapter_05.html – Oleg Dats Dec 22 '21 at 12:43

2 Answers2

3

When working on this exercise, it will probably easier to start with introducing a type constructor for not:

type empty = |
type 'a not = 'a -> empty

then use an explicit universal quantification to rewrite the exercise:

let proof_by_contradiction: type p q. (p not -> q) -> (p not -> q not) -> p =
   ...

which should improve slightly error messages

Error: This expression has type p not -> q but an expression was expected of type p not = p -> empty

Before diving into this exercise, it might be useful to try your hand at

let proof_by_negation:  type p q. (p -> q) -> (p -> q not) -> p not =
  ...

first.

octachron
  • 17,178
  • 2
  • 16
  • 23
  • your suggestions are great; but I'm not sure the initial assignment mentioned by the OP is provable, constructively… without using `fun _ -> assert false` ;-) – ErikMD Dec 18 '21 at 23:22
  • 1
    The initial assignment is certainly a trick assignment, I hope that it comes after an explanation of the absence of the double negation elimination in constructive logic. – octachron Dec 20 '21 at 10:53
  • @octachron I have implemented: `let proof_by_negation: type p q. (p -> q) -> (p -> q not) -> p not = fun f g x -> g(x)(f(x))`. The initial assignment expects Double negation elimination of `p` and as a result it is not provable constructively? Can you please suggest source to read more about universal and existential quantification in OCaml? How to define and prove first order theorems. – Oleg Dats Dec 22 '21 at 13:06
  • 1
    For universal quantification, https://ocaml.org/manual/polymorphism.html#s%3Apolymorphic-recursion . For existential quantification, the GADTs chapter will be the closest: https://ocaml.org/manual/gadts-tutorial.html . – octachron Dec 22 '21 at 16:10
  • Thanks, I will read provided links. May I ask one question ahed. I read about Lambda cube. They mention: system λP (so called dependent types) as a whole corresponds to first-order logic with implication as only connective. Does OCaml supports dependant types? – Oleg Dats Dec 22 '21 at 16:23
  • 1
    No, OCaml does not support dependent types at the value level. One of the reason is that inference for dependent types is not decidable at all. There is a design compromise between how much a type system is able to express, and how much it is possible to infer types automatically. – octachron Dec 22 '21 at 16:27
3

I'm pretty sure that it's not constructively provable.

First, note that

¬¬p -> (¬p -> a)

holds for completely arbitrary p and a (from ¬¬p and ¬p you first obtain proof of falsehood, then by ex falso quodlibet you obtain any a).

In particular, for any q,

    ¬¬p -> ((¬p -> q) /\ (¬p -> ¬q))             // ("lemma")

holds (apply previous statement to a = q and a = ¬q).

Now, if your original statement ((¬p -> q) /\ (¬p -> ¬q)) -> p were true, then you could precompose ¬¬p -> ((¬p -> q) /\ (¬p -> ¬q)), hence obtaining ¬¬p -> p. But this is double negation elimination, which is known to not be provable constructively.

Here is the full construction in Scala 3 (somewhat close-ish-ly related to OCaml; The subset of the language used here should be easily translatable to OCaml):

type ¬[A] = A => Nothing                               // negation
type /\[A, B] = (A, B)                                 // conjunction / product
type Claim[P, Q] = (¬[P] => Q) => (¬[P] => ¬[Q]) => P  // your claim
type DoubleNegationElimination[P] = ¬[¬[P]] => P

/** Ex falso quodlibet. */
def efq[X]: Nothing => X = f => f

/** Lemma, as explained above. */
def lemma[P, Q](a: ¬[¬[P]]): (¬[P] => Q) /\ (¬[P] => ¬[Q]) =
  val left: ¬[P] => Q = notP => efq(a(notP))
  val right: ¬[P] => ¬[Q] = notP => efq(a(notP))
  (left, right)

/** This shows that if you could prove your claim for any `P`, `Q`,
  * then you would also be able to prove double negation elimination
  * for `P`.
  */
def claimImpliesDoubleNegationElimination[P, Q](
  c: Claim[P, Q]
): DoubleNegationElimination[P] =
  notNotP => {
    val (left, right) = lemma[P, Q](notNotP)
    c(left)(right)
  }

/** This is an (incomplete, because impossible) proof of the double
  * negation elimination for any `P`. It is incomplete, because it
  * relies on the validity of your original claim.
  */
def doubleNegationElimination[P]: DoubleNegationElimination[P] =
  claimImpliesDoubleNegationElimination(claim[P, Unit])

/** There cannot be a constructive proof of this, because otherwise
  * we would obtain a constructive proof of `doubleNegationElimination`.
  */
def claim[P, Q]: Claim[P, Q] = ???

Andrey Tyukin
  • 43,673
  • 4
  • 57
  • 93