(** This file collects general purpose definitions and theorems on vectors
(lists of fixed length). It uses the definitions from the standard library, but
renames or changes their notations, so that it becomes more consistent with the
naming conventions in this development. *)
From stdpp Require Import countable.
From stdpp Require Export fin list.
From stdpp Require Import options.
Open Scope vector_scope.

(** The type [vec n] represents lists of consisting of exactly [n] elements.
Whereas the standard library declares exactly the same notations for vectors as
used for lists, we use slightly different notations so it becomes easier to use
lists and vectors together. *)
Notation vec := Vector.t.
Notation vnil := Vector.nil.
Arguments vnil {_}.
Notation vcons := Vector.cons.
Notation vapp := Vector.append.
Arguments vcons {_} _ {_} _.

Infix ":::" := vcons (at level 60, right associativity) : vector_scope.
Notation "(:::)" := vcons (only parsing) : vector_scope.
Notation "( x :::.)" := (vcons x) (only parsing) : vector_scope.
Notation "(.::: v )" := (λ x, vcons x v) (only parsing) : vector_scope.
Notation "[# ] " := vnil : vector_scope.
Notation "[# x ] " := (vcons x vnil) : vector_scope.
Notation "[# x ; .. ; y ] " := (vcons x .. (vcons y vnil) ..) : vector_scope.
Infix "+++" := vapp (at level 60, right associativity) : vector_scope.
Notation "(+++)" := vapp (only parsing) : vector_scope.
Notation "( v +++.)" := (vapp v) (only parsing) : vector_scope.
Notation "(.+++ w )" := (λ v, vapp v w) (only parsing) : vector_scope.

(** Similar to [fin], we provide an inversion principle that keeps the length
fixed. We define a tactic [inv_vec v] to perform case analysis on [v], using
this inversion principle. *)
Notation vec_0_inv := Vector.case0.
Definition vec_S_inv {A n} (P : vec A (S n) → Type)
  (Hcons : ∀ x v, P (x ::: v)) v : P v.
Proof.
  revert P Hcons.
  refine match v with [#] => tt | x ::: v => λ P Hcons, Hcons x v end.
Defined.

Instance vector_lookup_total A : ∀ m, LookupTotal (fin m) A (vec A m) :=
  fix go m i {struct i} := let _ : ∀ m, LookupTotal _ _ _ := @go in
  match i in fin m return vec A m → A with
  | 0%fin => vec_S_inv (λ _, A) (λ x _, x)
  | FS j => vec_S_inv (λ _, A) (λ _ v, v !!! j)
  end.

(** The tactic [vec_double_ind v1 v2] performs double induction on [v1] and [v2]
provided that they have the same length. *)
Notation vec_rect2 := Vector.rect2.
Ltac vec_double_ind v1 v2 :=
  match type of v1 with
  | vec _ ?n =>
    repeat match goal with
    | H' : context [ n ] |- _ => var_neq v1 H'; var_neq v2 H'; revert H'
    end;
    revert n v1 v2;
    match goal with |- ∀ n v1 v2, @?P n v1 v2 => apply (vec_rect2 P) end
  end.

Notation vcons_inj := VectorSpec.cons_inj.
Lemma vcons_inj_1 {A n} x y (v w : vec A n) : x ::: v = y ::: w → x = y.
Proof. apply vcons_inj. Qed.
Lemma vcons_inj_2 {A n} x y (v w : vec A n) : x ::: v = y ::: w → v = w.
Proof. apply vcons_inj. Qed.

Lemma vec_eq {A n} (v w : vec A n) : (∀ i, v !!! i = w !!! i) → v = w.
Proof.
  vec_double_ind v w; [done|]. intros n v w IH x y Hi. f_equal.
  - apply (Hi 0%fin).
  - apply IH. intros i. apply (Hi (FS i)).
Qed.

Instance vec_dec {A} {dec : EqDecision A} {n} : EqDecision (vec A n).
Proof.
 refine (vec_rect2
  (λ n (v w : vec A n), { v = w } + { v ≠ w })
  (left _)
  (λ _ _ _ H x y, cast_if_and (dec x y) H));
  f_equal; eauto using vcons_inj_1, vcons_inj_2.
Defined.

Ltac inv_vec v :=
  let T := type of v in
  match eval hnf in T with
  | vec _ ?n =>
    match eval hnf in n with
    | 0 => revert dependent v; match goal with |- ∀ v, @?P v => apply (vec_0_inv P) end
    | S ?n =>
      revert dependent v; match goal with |- ∀ v, @?P v => apply (vec_S_inv P) end;
      (* Try going on recursively. *)
      try (let x := fresh "x" in intros x v; inv_vec v; revert x)
    end
  end.

(** The following tactic performs case analysis on all hypotheses of the shape
[fin 0], [fin (S n)], [vec A 0] and [vec A (S n)] until no further case
analyses are possible. *)
Ltac inv_all_vec_fin := block_goal;
  repeat match goal with
  | v : vec _ _ |- _ => inv_vec v; intros
  | i : fin _ |- _ => inv_fin i; intros
  end; unblock_goal.

(** We define a coercion from [vec] to [list] and show that it preserves the
operations on vectors. We also define a function to go in the other way, but
do not define it as a coercion, as it would otherwise introduce ambiguity. *)
Fixpoint vec_to_list {A n} (v : vec A n) : list A :=
  match v with [#] => [] | x ::: v => x :: vec_to_list v end.
Coercion vec_to_list : vec >-> list.
Notation list_to_vec := Vector.of_list.

Lemma vec_to_list_cons {A n} x (v : vec A n) :
  vec_to_list (x ::: v) = x :: vec_to_list v.
Proof. done. Qed.
Lemma vec_to_list_app {A n m} (v : vec A n) (w : vec A m) :
  vec_to_list (v +++ w) = vec_to_list v ++ vec_to_list w.
Proof. by induction v; f_equal/=. Qed.
Lemma vec_to_list_to_vec {A} (l : list A): vec_to_list (list_to_vec l) = l.
Proof. by induction l; f_equal/=. Qed.
Lemma vec_to_list_length {A n} (v : vec A n) : length (vec_to_list v) = n.
Proof. induction v; simpl; by f_equal. Qed.
Lemma vec_to_list_same_length {A B n} (v : vec A n) (w : vec B n) :
  length v = length w.
Proof. by rewrite !vec_to_list_length. Qed.
Lemma vec_to_list_inj1 {A n m} (v : vec A n) (w : vec A m) :
  vec_to_list v = vec_to_list w → n = m.
Proof.
  revert m w. induction v; intros ? [|???] ?;
    simplify_eq/=; f_equal; eauto.
Qed.
Lemma vec_to_list_inj2 {A n} (v : vec A n) (w : vec A n) :
  vec_to_list v = vec_to_list w → v = w.
Proof.
  revert w. induction v; intros w; inv_vec w; intros;
    simplify_eq/=; f_equal; eauto.
Qed.
Lemma list_to_vec_to_list {A n} (v : vec A n) :
  list_to_vec (vec_to_list v) = eq_rect _ _ v _ (eq_sym (vec_to_list_length v)).
Proof.
  apply vec_to_list_inj2. rewrite vec_to_list_to_vec.
  by destruct (eq_sym (vec_to_list_length v)).
Qed.

Lemma vlookup_middle {A n m} (v : vec A n) (w : vec A m) x :
  ∃ i : fin (n + S m), x = (v +++ x ::: w) !!! i.
Proof.
  induction v as [|??? IHv]; simpl; [by eexists 0%fin|].
  destruct IHv as [i ?]. by exists (FS i).
Qed.
Lemma vec_to_list_lookup_middle {A n} (v : vec A n) (l k : list A) x :
  vec_to_list v = l ++ x :: k →
    ∃ i : fin n, l = take i v ∧ x = v !!! i ∧ k = drop (S i) v.
Proof.
  intros H.
  rewrite <-(vec_to_list_to_vec l), <-(vec_to_list_to_vec k) in H.
  rewrite <-vec_to_list_cons, <-vec_to_list_app in H.
  pose proof (vec_to_list_inj1 _ _ H); subst.
  apply vec_to_list_inj2 in H; subst. induction l as [|?? IHl]; simpl.
  - eexists 0%fin. simpl. by rewrite vec_to_list_to_vec.
  - destruct IHl as [i ?]. exists (FS i). simpl. intuition congruence.
Qed.
Lemma vec_to_list_drop_lookup {A n} (v : vec A n) (i : fin n) :
  drop i v = v !!! i :: drop (S i) v.
Proof. induction i as [|?? IHi]; inv_vec v; simpl; intros; [done | by rewrite IHi]. Qed.
Lemma vec_to_list_take_drop_lookup {A n} (v : vec A n) (i : fin n) :
  vec_to_list v = take i v ++ v !!! i :: drop (S i) v.
Proof. rewrite <-(take_drop i v) at 1. by rewrite vec_to_list_drop_lookup. Qed.

Lemma vlookup_lookup {A n} (v : vec A n) (i : fin n) x :
  v !!! i = x ↔ (v : list A) !! (i : nat) = Some x.
Proof.
  induction v as [|? ? v IH]; inv_fin i.
  - simpl; split; congruence.
  - done.
Qed.
Lemma vlookup_lookup' {A n} (v : vec A n) (i : nat) x :
  (∃ H : i < n, v !!! nat_to_fin H = x) ↔ (v : list A) !! i = Some x.
Proof.
  split.
  - intros [Hlt ?]. rewrite <-(fin_to_nat_to_fin i n Hlt). by apply vlookup_lookup.
  - intros Hvix. assert (Hlt:=lookup_lt_Some _ _ _ Hvix).
    rewrite vec_to_list_length in Hlt. exists Hlt.
    apply vlookup_lookup. by rewrite fin_to_nat_to_fin.
Qed.
Lemma elem_of_vlookup {A n} (v : vec A n) x :
  x ∈ vec_to_list v ↔ ∃ i, v !!! i = x.
Proof.
  rewrite elem_of_list_lookup. setoid_rewrite <-vlookup_lookup'.
  split; [by intros (?&?&?); eauto|]. intros [i Hx].
  exists i, (fin_to_nat_lt _). by rewrite nat_to_fin_to_nat.
Qed.

Lemma Forall_vlookup {A} (P : A → Prop) {n} (v : vec A n) :
  Forall P (vec_to_list v) ↔ ∀ i, P (v !!! i).
Proof. rewrite Forall_forall. setoid_rewrite elem_of_vlookup. naive_solver. Qed.
Lemma Forall_vlookup_1 {A} (P : A → Prop) {n} (v : vec A n) i :
  Forall P (vec_to_list v) → P (v !!! i).
Proof. by rewrite Forall_vlookup. Qed.
Lemma Forall_vlookup_2 {A} (P : A → Prop) {n} (v : vec A n) :
  (∀ i, P (v !!! i)) → Forall P (vec_to_list v).
Proof. by rewrite Forall_vlookup. Qed.
Lemma Exists_vlookup {A} (P : A → Prop) {n} (v : vec A n) :
  Exists P (vec_to_list v) ↔ ∃ i, P (v !!! i).
Proof. rewrite Exists_exists. setoid_rewrite elem_of_vlookup. naive_solver. Qed.
Lemma Forall2_vlookup {A B} (P : A → B → Prop) {n}
    (v1 : vec A n) (v2 : vec B n) :
  Forall2 P (vec_to_list v1) (vec_to_list v2) ↔ ∀ i, P (v1 !!! i) (v2 !!! i).
Proof.
  split.
  - vec_double_ind v1 v2; [intros _ i; inv_fin i |].
    intros n v1 v2 IH a b; simpl. inversion_clear 1.
    intros i. inv_fin i; simpl; auto.
  - vec_double_ind v1 v2; [constructor|].
    intros ??? IH ?? H. constructor.
    + apply (H 0%fin).
    + apply IH, (λ i, H (FS i)).
Qed.

(** Given a function [fin n → A], we can construct a vector. *)
Fixpoint fun_to_vec {A n} {struct n} : (fin n → A) → vec A n :=
  match n with
  | 0 => λ f, [#]
  | S n => λ f, f 0%fin ::: fun_to_vec (f ∘ FS)
  end.

Lemma lookup_fun_to_vec {A n} (f : fin n → A) i : fun_to_vec f !!! i = f i.
Proof.
  revert f. induction i as [|n i IH]; intros f; simpl; [done|]. by rewrite IH.
Qed.

(** The function [vmap f v] applies a function [f] element wise to [v]. *)
Notation vmap := Vector.map.

Lemma vlookup_map `(f : A → B) {n} (v : vec A n) i :
  vmap f v !!! i = f (v !!! i).
Proof. by induction v; inv_fin i; eauto. Qed.
Lemma vec_to_list_map `(f : A → B) {n} (v : vec A n) :
  vec_to_list (vmap f v) = f <$> vec_to_list v.
Proof. induction v as [|??? IHv]; simpl; [done|]. by rewrite IHv. Qed.

(** The function [vzip_with f v w] combines the vectors [v] and [w] element
wise using the function [f]. *)
Notation vzip_with := Vector.map2.

Lemma vlookup_zip_with `(f : A → B → C) {n} (v1 : vec A n) (v2 : vec B n) i :
  vzip_with f v1 v2 !!! i = f (v1 !!! i) (v2 !!! i).
Proof.
  vec_double_ind v1 v2.
  - intros i; inv_fin i.
  - intros n v1 v2 IH a b i.
    inv_fin i; eauto.
Qed.
Lemma vec_to_list_zip_with `(f : A → B → C) {n} (v1 : vec A n) (v2 : vec B n) :
  vec_to_list (vzip_with f v1 v2) =
    zip_with f (vec_to_list v1) (vec_to_list v2).
Proof.
  revert v2. induction v1 as [|??? IHv1]; intros v2; inv_vec v2; intros; simpl; [done|].
  by rewrite IHv1.
Qed.

(** Similar to vlookup, we cannot define [vinsert] as an instance of the
[Insert] type class, as it has a dependent type. *)
Fixpoint vinsert {A n} (i : fin n) (x : A) : vec A n → vec A n :=
  match i with
  | 0%fin => vec_S_inv _ (λ _ v, x ::: v)
  | FS i => vec_S_inv _ (λ y v, y ::: vinsert i x v)
  end.

Lemma vec_to_list_insert {A n} i x (v : vec A n) :
  vec_to_list (vinsert i x v) = insert (fin_to_nat i) x (vec_to_list v).
Proof. induction v as [|??? IHv]; inv_fin i; [done|]. simpl. intros. by rewrite IHv. Qed.

Lemma vlookup_insert {A n} i x (v : vec A n) : vinsert i x v !!! i = x.
Proof. by induction i; inv_vec v. Qed.
Lemma vlookup_insert_ne {A n} i j x (v : vec A n) :
  i ≠ j → vinsert i x v !!! j = v !!! j.
Proof.
  induction i as [|?? IHi]; inv_fin j; inv_vec v; simpl; try done.
  intros. apply IHi. congruence.
Qed.
Lemma vlookup_insert_self {A n} i (v : vec A n) : vinsert i (v !!! i) v = v.
Proof. by induction v; inv_fin i; intros; f_equal/=. Qed.

Lemma vmap_insert {A B} (f : A → B) (n : nat) i x (v : vec A n) :
  vmap f (vinsert i x v) = vinsert i (f x) (vmap f v).
Proof. induction v; inv_fin i; intros; f_equal/=; auto. Qed.

(** The functions [vtake i v] and [vdrop i v] take the first [i] elements of
a vector [v], respectively remove the first [i] elements of a vector [v]. *)
Fixpoint vtake {A n} (i : fin n) : vec A n → vec A i :=
  match i in fin n return vec A n → vec A i with
  | 0%fin => λ _, [#]
  | FS i => vec_S_inv _ (λ x v, x ::: vtake i v)
  end.
Fixpoint vdrop {A n} (i : fin n) : vec A n → vec A (n - i) :=
  match i in fin n return vec A n → vec A (n - i) with
  | 0%fin => id
  | FS i => vec_S_inv _ (λ _, vdrop i)
  end.

Lemma vec_to_list_take {A n} i (v : vec A n) :
  vec_to_list (vtake i v) = take (fin_to_nat i) (vec_to_list v).
Proof. induction i; inv_vec v; intros; f_equal/=; auto. Qed.
Lemma vec_to_list_drop {A n} i (v : vec A n) :
  vec_to_list (vdrop i v) = drop (fin_to_nat i) (vec_to_list v).
Proof. induction i; inv_vec v; intros; f_equal/=; auto. Qed.

(** The function [vreplicate n x] generates a vector with length [n] of elements
with value [x]. *)
Fixpoint vreplicate {A} (n : nat) (x : A) : vec A n :=
  match n with 0 => [#] | S n => x ::: vreplicate n x end.

Lemma vec_to_list_replicate {A} n (x : A) :
  vec_to_list (vreplicate n x) = replicate n x.
Proof. induction n; by f_equal/=. Qed.

Lemma vlookup_replicate {A} n (x : A) i : vreplicate n x !!! i = x.
Proof. induction i; f_equal/=; auto. Qed.

Lemma vmap_replicate {A B} (f : A → B) n (x : A) :
  vmap f (vreplicate n x) = vreplicate n (f x).
Proof. induction n; f_equal/=; auto. Qed.

(** Vectors are inhabited and countable *)
Global Instance vec_0_inhabited T : Inhabited (vec T 0) := populate [#].
Global Instance vec_inhabited `{Inhabited T} n : Inhabited (vec T n) :=
  populate (vreplicate n inhabitant).

Instance vec_countable `{Countable A} n : Countable (vec A n).
Proof.
  apply (inj_countable vec_to_list (λ l,
    guard (n = length l) as H; Some (eq_rect _ _ (list_to_vec l) _ (eq_sym H)))).
  intros v. case_option_guard as Hn.
  - rewrite list_to_vec_to_list.
    rewrite (proof_irrel (eq_sym _) Hn). by destruct Hn.
  - by rewrite vec_to_list_length in Hn.
Qed.


(** * Induction: Proof by Induction *)

(** The next line imports all of our definitions from the
    previous chapter. *)

Require Export Basics.

(** For it to work, you need to use [coqc] to compile [Basics.v]
    into [Basics.vo].  (This is like making a .class file from a .java
    file, or a .o file from a .c file.)

    Here are two ways to compile your code:

     - CoqIDE:

         Open [Basics.v].
         In the "Compile" menu, click on "Compile Buffer".

     - Command line:

         Run [coqc Basics.v]

    *)

(* ###################################################################### *)
(** * Naming Cases *)

(** The fact that there is no explicit command for moving from
    one branch of a case analysis to the next can make proof scripts
    rather hard to read.  In larger proofs, with nested case analyses,
    it can even become hard to stay oriented when you're sitting with
    Coq and stepping through the proof.  (Imagine trying to remember
    that the first five subgoals belong to the inner case analysis and
    the remaining seven cases are what remains of the outer one...)
    Disciplined use of indentation and comments can help, but a better
    way is to use the [Case] tactic. *)

(* [Case] is not built into Coq: we need to define it ourselves.
    There is no need to understand how it works -- you can just skip
    over the definition to the example that follows.  It uses some
    facilities of Coq that we have not discussed -- the string
    library (just for the concrete syntax of quoted strings) and the
    [Ltac] command, which allows us to declare custom tactics.  Kudos
    to Aaron Bohannon for this nice hack! *)

Require String. Open Scope string_scope.

Ltac move_to_top x :=
  match reverse goal with
  | H : _ |- _ => try move x after H
  end.

Tactic Notation "assert_eq" ident(x) constr(v) :=
  let H := fresh in
  assert (x = v) as H by reflexivity;
  clear H.

Tactic Notation "Case_aux" ident(x) constr(name) :=
  first [
    set (x := name); move_to_top x
  | assert_eq x name; move_to_top x
  | fail 1 "because we are working on a different case" ].

Tactic Notation "Case" constr(name) := Case_aux Case name.
Tactic Notation "SCase" constr(name) := Case_aux SCase name.
Tactic Notation "SSCase" constr(name) := Case_aux SSCase name.
Tactic Notation "SSSCase" constr(name) := Case_aux SSSCase name.
Tactic Notation "SSSSCase" constr(name) := Case_aux SSSSCase name.
Tactic Notation "SSSSSCase" constr(name) := Case_aux SSSSSCase name.
Tactic Notation "SSSSSSCase" constr(name) := Case_aux SSSSSSCase name.
Tactic Notation "SSSSSSSCase" constr(name) := Case_aux SSSSSSSCase name.
(** Here's an example of how [Case] is used.  Step through the
   following proof and observe how the context changes. *)

Theorem andb_true_elim1 : forall b c : bool,
  andb b c = true -> b = true.
Proof.
  intros b c H.
  destruct b.
  Case "b = true".  (* <----- here *)
    reflexivity.
  Case "b = false".  (* <---- and here *)
    rewrite <- H.
    reflexivity.
Qed.

(** [Case] does something very straightforward: It simply adds a
    string that we choose (tagged with the identifier "Case") to the
    context for the current goal.  When subgoals are generated, this
    string is carried over into their contexts.  When the last of
    these subgoals is finally proved and the next top-level goal
    becomes active, this string will no longer appear in the context
    and we will be able to see that the case where we introduced it is
    complete.  Also, as a sanity check, if we try to execute a new
    [Case] tactic while the string left by the previous one is still
    in the context, we get a nice clear error message.

    For nested case analyses (e.g., when we want to use a [destruct]
    to solve a goal that has itself been generated by a [destruct]),
    there is an [SCase] ("subcase") tactic. *)

(** **** Exercise: 2 stars (andb_true_elim2) *)
(** Prove [andb_true_elim2], marking cases (and subcases) when
    you use [destruct]. *)

Theorem andb_true_elim2 : forall b c : bool,
  andb b c = true -> c = true.
Proof.
  intros b c H. destruct c.
  Case "c = true".
    reflexivity.
  Case "c = false".
    rewrite <- H. destruct b.
    SCase "b = true".
      reflexivity.
    SCase "b = false".
      reflexivity.  Qed.
(** [] *)

(** There are no hard and fast rules for how proofs should be
    formatted in Coq -- in particular, where lines should be broken
    and how sections of the proof should be indented to indicate their
    nested structure.  However, if the places where multiple subgoals
    are generated are marked with explicit [Case] tactics placed at
    the beginning of lines, then the proof will be readable almost no
    matter what choices are made about other aspects of layout.

    This is a good place to mention one other piece of (possibly
    obvious) advice about line lengths.  Beginning Coq users sometimes
    tend to the extremes, either writing each tactic on its own line
    or entire proofs on one line.  Good style lies somewhere in the
    middle.  In particular, one reasonable convention is to limit
    yourself to 80-character lines.  Lines longer than this are hard
    to read and can be inconvenient to display and print.  Many
    editors have features that help enforce this. *)

(* ###################################################################### *)
(** * Proof by Induction *)

(** We proved in the last chapter that [0] is a neutral element
    for [+] on the left using a simple argument.  The fact that it is
    also a neutral element on the _right_... *)

Theorem plus_0_r_firsttry : forall n:nat,
  n + 0 = n.

(** ... cannot be proved in the same simple way.  Just applying
  [reflexivity] doesn't work: the [n] in [n + 0] is an arbitrary
  unknown number, so the [match] in the definition of [+] can't be
  simplified.  *)

Proof.
  intros n.
  simpl. (* Does nothing! *)
Abort.

(** And reasoning by cases using [destruct n] doesn't get us much
   further: the branch of the case analysis where we assume [n = 0]
   goes through, but in the branch where [n = S n'] for some [n'] we
   get stuck in exactly the same way.  We could use [destruct n'] to
   get one step further, but since [n] can be arbitrarily large, if we
   try to keep on like this we'll never be done. *)

Theorem plus_0_r_secondtry : forall n:nat,
  n + 0 = n.
Proof.
  intros n. destruct n as [| n'].
  Case "n = 0".
    reflexivity. (* so far so good... *)
  Case "n = S n'".
    simpl.       (* ...but here we are stuck again *)
Abort.

(** To prove such facts -- indeed, to prove most interesting
    facts about numbers, lists, and other inductively defined sets --
    we need a more powerful reasoning principle: _induction_.

    Recall (from high school) the principle of induction over natural
    numbers: If [P(n)] is some proposition involving a natural number
    [n] and we want to show that P holds for _all_ numbers [n], we can
    reason like this:
         - show that [P(O)] holds;
         - show that, for any [n'], if [P(n')] holds, then so does
           [P(S n')];
         - conclude that [P(n)] holds for all [n].

    In Coq, the steps are the same but the order is backwards: we
    begin with the goal of proving [P(n)] for all [n] and break it
    down (by applying the [induction] tactic) into two separate
    subgoals: first showing [P(O)] and then showing [P(n') -> P(S
    n')].  Here's how this works for the theorem we are trying to
    prove at the moment: *)

Theorem plus_0_r : forall n:nat, n + 0 = n.
Proof.
  intros n. induction n as [| n'].
  Case "n = 0".     reflexivity.
  Case "n = S n'".  simpl. rewrite -> IHn'. reflexivity.  Qed.

(** Like [destruct], the [induction] tactic takes an [as...]
    clause that specifies the names of the variables to be introduced
    in the subgoals.  In the first branch, [n] is replaced by [0] and
    the goal becomes [0 + 0 = 0], which follows by simplification.  In
    the second, [n] is replaced by [S n'] and the assumption [n' + 0 =
    n'] is added to the context (with the name [IHn'], i.e., the
    Induction Hypothesis for [n']).  The goal in this case becomes [(S
    n') + 0 = S n'], which simplifies to [S (n' + 0) = S n'], which in
    turn follows from the induction hypothesis. *)

Theorem minus_diag : forall n,
  minus n n = 0.
Proof.
  (* WORKED IN CLASS *)
  intros n. induction n as [| n'].
  Case "n = 0".
    simpl. reflexivity.
  Case "n = S n'".
    simpl. rewrite -> IHn'. reflexivity.  Qed.

(** **** Exercise: 2 stars (basic_induction) *)

(** Prove the following lemmas using induction. You might need
    previously proven results. *)

Theorem mult_0_r : forall n:nat,
  n * 0 = 0.
Proof.
  intros n. induction n as [| n'].
  Case "n = 0".
    reflexivity.
  Case "n = S n'".
    simpl. rewrite -> IHn'. reflexivity.  Qed.

Theorem plus_n_Sm : forall n m : nat,
  S (n + m) = n + (S m).
Proof.
  intros n m. induction n as [| n'].
  Case "n = 0".
    reflexivity.
  Case "n = S n'".
    simpl. rewrite -> IHn'. reflexivity.  Qed.

Theorem plus_comm : forall n m : nat,
  n + m = m + n.
Proof.
  intros n m. induction n as [| n'].
  Case "n = 0".
    rewrite -> plus_0_r. reflexivity.
  Case "n = S n'".
    simpl. rewrite -> IHn'. rewrite -> plus_n_Sm. reflexivity.  Qed.

Theorem plus_assoc : forall n m p : nat,
  n + (m + p) = (n + m) + p.
Proof.
  intros n m p. induction n as [| n'].
  Case "n = 0".
    reflexivity.
  Case "n = S n'".
    simpl. rewrite -> IHn'. reflexivity.  Qed.
(** [] *)

(** **** Exercise: 2 stars (double_plus) *)

(** Consider the following function, which doubles its argument: *)

Fixpoint double (n:nat) :=
  match n with
  | O => O
  | S n' => S (S (double n'))
  end.

(** Use induction to prove this simple fact about [double]: *)

Lemma double_plus : forall n, double n = n + n .
Proof.
  intros n. induction n as [| n'].
  Case "n = 0".
    reflexivity.
  Case "n = S n'".
    simpl. rewrite -> IHn'. rewrite -> plus_n_Sm. reflexivity.  Qed.
(** [] *)

(** **** Exercise: 1 star (destruct_induction) *)
(** Briefly explain the difference between the tactics
    [destruct] and [induction].

[induction] adds inductive hypothesis.

*)
(** [] *)

(* ###################################################################### *)
(** * Proofs Within Proofs *)

(** In Coq, as in informal mathematics, large proofs are very
    often broken into a sequence of theorems, with later proofs
    referring to earlier theorems.  Occasionally, however, a proof
    will need some miscellaneous fact that is too trivial (and of too
    little general interest) to bother giving it its own top-level
    name.  In such cases, it is convenient to be able to simply state
    and prove the needed "sub-theorem" right at the point where it is
    used.  The [assert] tactic allows us to do this.  For example, our
    earlier proof of the [mult_0_plus] theorem referred to a previous
    theorem named [plus_O_n].  We can also use [assert] to state and
    prove [plus_O_n] in-line: *)

Theorem mult_0_plus' : forall n m : nat,
  (0 + n) * m = n * m.
Proof.
  intros n m.
  assert (H: 0 + n = n).
    Case "Proof of assertion". reflexivity.
  rewrite -> H.
  reflexivity.  Qed.

(** The [assert] tactic introduces two sub-goals.  The first is
    the assertion itself; by prefixing it with [H:] we name the
    assertion [H].  (Note that we could also name the assertion with
    [as] just as we did above with [destruct] and [induction], i.e.,
    [assert (0 + n = n) as H].  Also note that we mark the proof of
    this assertion with a [Case], both for readability and so that,
    when using Coq interactively, we can see when we're finished
    proving the assertion by observing when the ["Proof of assertion"]
    string disappears from the context.)  The second goal is the same
    as the one at the point where we invoke [assert], except that, in
    the context, we have the assumption [H] that [0 + n = n].  That
    is, [assert] generates one subgoal where we must prove the
    asserted fact and a second subgoal where we can use the asserted
    fact to make progress on whatever we were trying to prove in the
    first place. *)

(** Actually, [assert] will turn out to be handy in many sorts of
    situations.  For example, suppose we want to prove that [(n + m)
    + (p + q) = (m + n) + (p + q)]. The only difference between the
    two sides of the [=] is that the arguments [m] and [n] to the
    first inner [+] are swapped, so it seems we should be able to
    use the commutativity of addition ([plus_comm]) to rewrite one
    into the other.  However, the [rewrite] tactic is a little stupid
    about _where_ it applies the rewrite.  There are three uses of
    [+] here, and it turns out that doing [rewrite -> plus_comm]
    will affect only the _outer_ one. *)

Theorem plus_rearrange_firsttry : forall n m p q : nat,
  (n + m) + (p + q) = (m + n) + (p + q).
Proof.
  intros n m p q.
  (* We just need to swap (n + m) for (m + n)...
     it seems like plus_comm should do the trick! *)
  rewrite -> plus_comm.
  (* Doesn't work...Coq rewrote the wrong plus! *)
Abort.

(** To get [plus_comm] to apply at the point where we want it, we can
    introduce a local lemma stating that [n + m = m + n] (for
    the particular [m] and [n] that we are talking about here), prove
    this lemma using [plus_comm], and then use this lemma to do the
    desired rewrite. *)

Theorem plus_rearrange : forall n m p q : nat,
  (n + m) + (p + q) = (m + n) + (p + q).
Proof.
  intros n m p q.
  assert (H: n + m = m + n).
    Case "Proof of assertion".
    rewrite -> plus_comm. reflexivity.
  rewrite -> H. reflexivity.  Qed.

(** **** Exercise: 4 stars (mult_comm) *)
(** Use [assert] to help prove this theorem.  You shouldn't need to
    use induction. *)

Theorem plus_swap : forall n m p : nat,
  n + (m + p) = m + (n + p).
Proof.
  intros n m p.
  rewrite -> plus_assoc.
  rewrite -> plus_assoc.
  assert (H: n + m = m + n).
    Case "Proof of assertion".
    rewrite -> plus_comm. reflexivity.
  rewrite -> H. reflexivity. Qed.

(** Now prove commutativity of multiplication.  (You will probably
    need to define and prove a separate subsidiary theorem to be used
    in the proof of this one.)  You may find that [plus_swap] comes in
    handy. *)

Theorem mult_n_Sm : forall n m : nat,
  n * S m = n + n * m.
Proof.
  intros n m. induction n as [| n'].
  Case "n = 0".
    reflexivity.
  Case "n = S n'".
    simpl. rewrite -> IHn'. rewrite -> plus_swap. reflexivity.  Qed.

Theorem mult_comm : forall m n : nat,
  m * n = n * m.
Proof.
  intros m n. induction m as [| m'].
  Case "m = 0".
    rewrite -> mult_0_r. reflexivity.
  Case "m = S m'".
    rewrite -> mult_n_Sm. rewrite <- IHm'. reflexivity.  Qed.
(** [] *)

(** **** Exercise: 2 stars, optional (evenb_n__oddb_Sn) *)

(** Prove the following simple fact: *)

Theorem evenb_n__oddb_Sn : forall n : nat,
  evenb n = negb (evenb (S n)).
Proof.
  intros n. induction n as [| n'].
  Case "n = 0".
    reflexivity.
  Case "n = S n'".
    assert (H: evenb (S (S n')) = evenb n').
    SCase "Proof of assertion".
      reflexivity.
    rewrite -> H. rewrite -> IHn'.
    rewrite -> negb_involutive. reflexivity.  Qed.
(** [] *)

(* ###################################################################### *)
(** * More Exercises *)

(** **** Exercise: 3 stars, optional (more_exercises) *)
(** Take a piece of paper.  For each of the following theorems, first
    _think_ about whether (a) it can be proved using only
    simplification and rewriting, (b) it also requires case
    analysis ([destruct]), or (c) it also requires induction.  Write
    down your prediction.  Then fill in the proof.  (There is no need
    to turn in your piece of paper; this is just to encourage you to
    reflect before hacking!) *)

Theorem ble_nat_refl : forall n:nat,
  true = ble_nat n n.
  intros n. induction n as [| n'].
  Case "n = 0".
    reflexivity.
  Case "n = S n'".
    rewrite -> IHn'. reflexivity.  Qed.

Theorem zero_nbeq_S : forall n:nat,
  beq_nat 0 (S n) = false.
Proof.
  reflexivity.  Qed.

Theorem andb_false_r : forall b : bool,
  andb b false = false.
Proof.
  intros b. destruct b.
  Case "b = true".
    reflexivity.
  Case "b = false".
    reflexivity.  Qed.

Theorem plus_ble_compat_l : forall n m p : nat,
  ble_nat n m = true -> ble_nat (p + n) (p + m) = true.
Proof.
  intros n m p H. induction p as [| p'].
  Case "p = 0".
    simpl. rewrite -> H. reflexivity.
  Case "p = S p'".
    simpl. rewrite -> IHp'. reflexivity.  Qed.

Theorem S_nbeq_0 : forall n:nat,
  beq_nat (S n) 0 = false.
Proof.
  reflexivity.  Qed.

Theorem mult_1_l : forall n:nat, 1 * n = n.
Proof.
  intros n. destruct n as [| n'].
  Case "n = 0".
    rewrite -> mult_0_r. reflexivity.
  Case "n = S n'".
    simpl. rewrite -> plus_0_r. reflexivity.  Qed.

Theorem all3_spec : forall b c : bool,
    orb
      (andb b c)
      (orb (negb b)
               (negb c))
  = true.
Proof.
  intros b c. destruct b.
  Case "b = true".
    destruct c.
    SCase "c = true".
      reflexivity.
    SCase "c = false".
      reflexivity.
  Case "b = false".
    destruct c.
    SCase "c = true".
      reflexivity.
    SCase "c = false".
      reflexivity.  Qed.

Theorem mult_plus_distr_r : forall n m p : nat,
  (n + m) * p = (n * p) + (m * p).
Proof.
  intros n m p. induction p as [| p'].
  Case "p = 0".
    rewrite -> mult_0_r. rewrite -> mult_0_r. rewrite -> mult_0_r.
    reflexivity.
  Case "p = S p'".
    assert (H1: forall k, k * S p' = S p' * k).
      intros k. rewrite -> mult_comm. reflexivity.
    rewrite -> H1. rewrite -> H1. rewrite -> H1. simpl.
    assert (H2: forall k : nat, p' * k = k * p').
      intros k. rewrite -> mult_comm. reflexivity.
    rewrite -> H2. rewrite -> H2. rewrite -> H2. rewrite -> IHp'.
    rewrite -> plus_assoc. rewrite -> plus_assoc.
    assert (H3: n + n * p' + m = m + n + n * p').
      rewrite -> plus_comm. rewrite -> plus_assoc. reflexivity.
    rewrite -> H3.
    assert (H4: n + m = m + n).
      rewrite -> plus_comm. reflexivity.
    rewrite -> H4. reflexivity.  Qed.

Theorem mult_assoc : forall n m p : nat,
  n * (m * p) = (n * m) * p.
Proof.
  intros n m p. induction n as [| n'].
  Case "n = 0".
    reflexivity.
  Case "n = S n'".
    simpl. rewrite -> IHn'.
    rewrite -> mult_plus_distr_r. reflexivity.  Qed.
(** [] *)

(** **** Exercise: 2 stars, optional (beq_nat_refl) *)
(** Prove the following theorem.  Putting [true] on the left-hand side
of the equality may seem odd, but this is how the theorem is stated in
the standard library, so we follow suit.  Since rewriting
works equally well in either direction, we will have no
problem using the theorem no matter which way we state it. *)

Theorem beq_nat_refl : forall n : nat,
  true = beq_nat n n.
Proof.
  intros n. induction n as [| n'].
  Case "n = 0".
    reflexivity.
  Case "n = S n'".
    rewrite -> IHn'. reflexivity.  Qed.
(** [] *)

(** **** Exercise: 2 stars, optional (plus_swap') *)
(** The [replace] tactic allows you to specify a particular subterm to
   rewrite and what you want it rewritten to.  More precisely,
   [replace (t) with (u)] replaces (all copies of) expression [t] in
   the goal by expression [u], and generates [t = u] as an additional
   subgoal. This is often useful when a plain [rewrite] acts on the wrong
   part of the goal.

   Use the [replace] tactic to do a proof of [plus_swap'], just like
   [plus_swap] but without needing [assert (n + m = m + n)].
*)

Theorem plus_swap' : forall n m p : nat,
  n + (m + p) = m + (n + p).
Proof.
  intros n m p.
  rewrite -> plus_assoc. rewrite -> plus_assoc.
  replace (n + m) with (m + n). reflexivity.
  Case "Proof of replace".
    rewrite -> plus_comm. reflexivity.  Qed.
(** [] *)

(** **** Exercise: 3 stars (binary_commute) *)
(** Recall the [increment] and [binary-to-unary] functions that you
    wrote for the [binary] exercise in the [Basics] chapter.  Prove
    that these functions commute -- that is, incrementing a binary
    number and then converting it to unary yields the same result as
    first converting it to unary and then incrementing.

    (Before you start working on this exercise, please copy the
    definitions from your solution to the [binary] exercise here so
    that this file can be graded on its own.  If you find yourself
    wanting to change your original definitions to make the property
    easier to prove, feel free to do so.) *)

Inductive bin : Type :=
  | Z : bin
  | T : bin -> bin
  | M : bin -> bin.

Fixpoint inc (b : bin) : bin :=
  match b with
  | Z    => M Z         (* 0      -> 1        *)
  | T b' => M b'        (* 2n     -> 2n + 1   *)
  | M b' => T (inc b')  (* 2n + 1 -> 2(n + 1) *)
  end.

Fixpoint bin2nat (b : bin) : nat :=
  match b with
  | Z    => O
  | T b' => (bin2nat b') * 2
  | M b' => S ((bin2nat b') * 2)
  end.

Theorem inc_bin2nat_comm: forall b,
  bin2nat (inc b) = S (bin2nat b).
Proof.
  intros b. induction b as [| b'| b'].
  Case "b = Z".
    reflexivity.
  Case "b = T b'".
    reflexivity.
  Case "b = M b'".
    simpl. rewrite -> IHb'. reflexivity.  Qed.
(** [] *)

(** **** Exercise: 5 stars, advanced (binary_inverse) *)
(** This exercise is a continuation of the previous exercise about
    binary numbers.  You will need your definitions and theorems from
    the previous exercise to complete this one.

    (a) First, write a function to convert natural numbers to binary
        numbers.  Then prove that starting with any natural number,
        converting to binary, then converting back yields the same
        natural number you started with.
*)

Fixpoint nat2bin (n : nat) : bin :=
  match n with
  | O    => Z
  | S n' => inc (nat2bin n')
  end.

Theorem nat2bin_inverse: forall (n : nat),
  bin2nat (nat2bin n) = n.
Proof.
  intros. induction n as [| n'].
  Case "n = 0".
    reflexivity.
  Case "n = S n'".
    simpl. rewrite -> inc_bin2nat_comm.
    rewrite -> IHn'. reflexivity.  Qed.

(**
    (b) You might naturally think that we should also prove the
        opposite direction: that starting with a binary number,
        converting to a natural, and then back to binary yields the
        same number we started with.  However, it is not true!
        Explain what the problem is.

    (c) Define a function [normalize] from binary numbers to binary
        numbers such that for any binary number b, converting to a
        natural and then back to binary yields [(normalize b)].  Prove
        it.

    Again, feel free to change your earlier definitions if this helps
    here.
*)

Definition twice (b : bin) : bin :=
  match b with
  | Z => Z
  | c => T c
  end.

Fixpoint normalize (b : bin) : bin :=
  match b with
  | Z => Z
  | T b' => twice (normalize b')
  | M b' => inc (twice (normalize b'))
  end.

Theorem inc_twice: forall b,
  inc (inc (twice b)) = twice (inc b).
Proof.
  intros b. destruct b as [|b' |b' ].
  SCase "b = Z".    reflexivity.
  SCase "b = T b'". reflexivity.
  SCase "b = M b'". reflexivity.
Qed.

Theorem double_nat2bin_comm: forall n,
  nat2bin (n * 2) = twice (nat2bin n).
Proof.
  intros n. induction n as [| n'].
  Case "n = 0".
    reflexivity.
  Case "n = S n'".
    simpl. rewrite -> IHn'. rewrite -> inc_twice. reflexivity.  Qed.

Theorem bin2nat_inverse : forall b,
  nat2bin (bin2nat b) = normalize b.
Proof.
  intros b. induction b as [| b' | b'].
  Case "b = Z".
    reflexivity.
  Case "b = T b'".
    simpl. rewrite -> double_nat2bin_comm.
    rewrite -> IHb'. reflexivity.
  Case "b = M b'".
    simpl. rewrite -> double_nat2bin_comm.
    rewrite -> IHb'. reflexivity.  Qed.
(** [] *)

(* ###################################################################### *)
(** * Advanced Material *)

(** ** Formal vs. Informal Proof *)

(** "Informal proofs are algorithms; formal proofs are code." *)

(** The question of what, exactly, constitutes a "proof" of a
    mathematical claim has challenged philosophers for millenia.  A
    rough and ready definition, though, could be this: a proof of a
    mathematical proposition [P] is a written (or spoken) text that
    instills in the reader or hearer the certainty that [P] is true.
    That is, a proof is an act of communication.

    Now, acts of communication may involve different sorts of readers.
    On one hand, the "reader" can be a program like Coq, in which case
    the "belief" that is instilled is a simple mechanical check that
    [P] can be derived from a certain set of formal logical rules, and
    the proof is a recipe that guides the program in performing this
    check.  Such recipes are _formal_ proofs.

    Alternatively, the reader can be a human being, in which case the
    proof will be written in English or some other natural language,
    thus necessarily _informal_.  Here, the criteria for success are
    less clearly specified.  A "good" proof is one that makes the
    reader believe [P].  But the same proof may be read by many
    different readers, some of whom may be convinced by a particular
    way of phrasing the argument, while others may not be.  One reader
    may be particularly pedantic, inexperienced, or just plain
    thick-headed; the only way to convince them will be to make the
    argument in painstaking detail.  But another reader, more familiar
    in the area, may find all this detail so overwhelming that they
    lose the overall thread.  All they want is to be told the main
    ideas, because it is easier to fill in the details for themselves.
    Ultimately, there is no universal standard, because there is no
    single way of writing an informal proof that is guaranteed to
    convince every conceivable reader.  In practice, however,
    mathematicians have developed a rich set of conventions and idioms
    for writing about complex mathematical objects that, within a
    certain community, make communication fairly reliable.  The
    conventions of this stylized form of communication give a fairly
    clear standard for judging proofs good or bad.

    Because we are using Coq in this course, we will be working
    heavily with formal proofs.  But this doesn't mean we can ignore
    the informal ones!  Formal proofs are useful in many ways, but
    they are _not_ very efficient ways of communicating ideas between
    human beings. *)

(** For example, here is a proof that addition is associative: *)

Theorem plus_assoc' : forall n m p : nat,
  n + (m + p) = (n + m) + p.
Proof. intros n m p. induction n as [| n']. reflexivity.
  simpl. rewrite -> IHn'. reflexivity.  Qed.

(** Coq is perfectly happy with this as a proof.  For a human,
    however, it is difficult to make much sense of it.  If you're used
    to Coq you can probably step through the tactics one after the
    other in your mind and imagine the state of the context and goal
    stack at each point, but if the proof were even a little bit more
    complicated this would be next to impossible.  Instead, a
    mathematician might write it something like this: *)
(** - _Theorem_: For any [n], [m] and [p],
      n + (m + p) = (n + m) + p.
    _Proof_: By induction on [n].

    - First, suppose [n = 0].  We must show
        0 + (m + p) = (0 + m) + p.
      This follows directly from the definition of [+].

    - Next, suppose [n = S n'], where
        n' + (m + p) = (n' + m) + p.
      We must show
        (S n') + (m + p) = ((S n') + m) + p.
      By the definition of [+], this follows from
        S (n' + (m + p)) = S ((n' + m) + p),
      which is immediate from the induction hypothesis. [] *)

(** The overall form of the proof is basically similar.  This is
    no accident: Coq has been designed so that its [induction] tactic
    generates the same sub-goals, in the same order, as the bullet
    points that a mathematician would write.  But there are
    significant differences of detail: the formal proof is much more
    explicit in some ways (e.g., the use of [reflexivity]) but much
    less explicit in others (in particular, the "proof state" at any
    given point in the Coq proof is completely implicit, whereas the
    informal proof reminds the reader several times where things
    stand). *)

(** Here is a formal proof that shows the structure more
    clearly: *)

Theorem plus_assoc'' : forall n m p : nat,
  n + (m + p) = (n + m) + p.
Proof.
  intros n m p. induction n as [| n'].
  Case "n = 0".
    reflexivity.
  Case "n = S n'".
    simpl. rewrite -> IHn'. reflexivity.   Qed.

(** **** Exercise: 2 stars, advanced (plus_comm_informal) *)
(** Translate your solution for [plus_comm] into an informal proof. *)

(** Theorem: Addition is commutative. *)
(** - _Theorem_: For any [n] and [m],
      n + m = m + n.
    _Proof_: By induction on [n].

    - First, suppose [n = 0].  We must show
        0 + m = m + 0.
      This follows from the fact that 0 is a "neutral element" for [+]
      on the right and on the left.

    - Next, suppose [n = S n'], where
        n' + m = m + n'.
      We must show
        (S n') + m = m + (S n').
      By the definition of [+], this follows from
        S (n' + m) = m + (S n'),
      and from induction hypothesis
        S (m + n') = m + (S n'),
      which immediate from the plus_n_Sm. [] *)

(** **** Exercise: 2 stars, optional (beq_nat_refl_informal) *)
(** Write an informal proof of the following theorem, using the
    informal proof of [plus_assoc] as a model.  Don't just
    paraphrase the Coq tactics into English!

    Theorem: [true = beq_nat n n] for any [n].

    Proof: By induction on [n].

    - First, suppose [n = 0]. We must show
        true = beq_nat 0 0.
      This follows directly from the definition of [beq_nat].

    - Next, suppose [n = S n'], where
        true = beq_nat n' n'.
      We must show
        true = beq_nat (S n') (S n').
      By the induction hypothesis, this follows from
        beq_nat n' n' = beq_nat (S n') (S n')
      which is immediate from [beq_nat] definition. [] *)

(* $Date: 2013-07-17 16:19:11 -0400 (Wed, 17 Jul 2013) $ *)


(** * C2: Teoretyczne podstawy teorii typów [TODO] *)

(* begin hide *)
(*
TODO-1: Wkleić tutaj opis podstaw teorii typów z mojej pracy magisterskiej.
TODO 0: Zdeduplikować dwie pierwsze sekcje, bo obie są o termach.
TODO 1: Osąd `x : A` możemy czytać jako "x jest typu A", zaś konkretnie
TODO 1: `x : nat` jako "x jest liczbą naturalną".
TODO 2: Zrobić więcej ściąg/zadań z czytania różnych rzeczy.
TODO 3: powiązanie reguł wprowadzania/eliminacji/obliczania/unikalności
TODO 3: z równoważnościami, czyli w sumie Harmonia.
TODO 3: A równoważności to nic innego jak konstrukcje uniwersalne.
TODO 4: Podkreślić gdzieś mocniej, że reguła indukcji mówi,
TODO 4: że nie ma nic poza tym, co można zrobić konstruktorami.
TODO 5: Intuicja dla reguł unikalności: dzida składa się z przeddzidzia
TODO 5: dzidy, śróddzidzia dzidy i zadzidzia dzidy.
TODO 6: Wprowadzić pojęcie "motyw eliminacji" i częściej używać.
TODO 7: Podkreślić związki teorii typów z rzeczywistością przez
TODO 7: podkreślenie kwestii silnej normalizacji.
TODO 8: Opisać parametryczność tuż po opisaniu funkcji i [Type].
*)
(* end hide *)

(** Uwaga: ten rozdział jest póki co posklejany z fragmentów innych
    rozdziałów. Czytając go, weź na to poprawkę. W szczególności zawiera on
    zadania, których nie będziesz w stanie zrobić, bo niezbędny do tego
    materiał jest póki co w kolejnym rozdziale. Możesz więc przeczytać
    część teoretyczną, a zadania pominąć (albo w ogóle pominąć cały ten
    rozdział). *)

(** * Typy i termy *)

(** Czym są termy? Są to twory o naturze syntaktycznej (składniowej),
    reprezentujące funkcje, typy, zdania logiczne, predykaty, relacje
    etc. Polskim słowem o najbliższym znaczeniu jest słowo "wyrażenie".
    Zamiast prób definiowania termów, co byłoby problematyczne,
    zobaczmy przykłady:
    - [2] — stałe są termami
    - [P] — zmienne są termami
    - [Prop] — typy są termami
    - [fun x : nat => x + 2] — λ-abstrakcje (funkcje) są termami
    - [f x] — aplikacje funkcji do argumentu są termami
    - [if true then 5 else 2] — konstrukcja if-then-else jest termem *)

(** Nie są to wszystkie występujące w Coqu rodzaje termów — jest
    ich nieco więcej.

    Kolejnym fundamentalnym pojęciem jest pojęcie typu. W Coqu
    każdy term ma dokładnie jeden, niezmienny typ. Czym są typy?
    Intuicyjnie można powiedzieć, że typ to rodzaj metki, która
    dostarcza nam informacji dotyczących danego termu.

    Dla przykładu,
    stwierdzenie [x : nat] informuje nas, że [x] jest liczbą
    naturalną, dzięki czemu wiemy, że możemy użyć go jako argumentu
    dodawania: term [x + 1] jest poprawnie typowany (ang. well-typed),
    tzn. [x + 1 : nat], a więc możemy skonkludować, że [x + 1] również
    jest liczbą naturalną.

    Innym przykładem niech będzie stwierdzenie [f : nat -> nat],
    które mówi nam, że [f] jest funkcją, która bierze liczbę
    naturalną i zwraca liczbę naturalną. Dzięki temu wiemy, że term
    [f 2] jest poprawnie typowany i jest liczbą naturalną,
    tzn. [f 2 : nat], zaś term [f f] nie jest poprawnie typowany,
    a więc próba jego użycia, a nawet napisania byłaby błędem.

    Typy są tworami absolutnie kluczowymi. Informują nas, z jakimi
    obiektami mamy do czynienia i co możemy z nimi zrobić, a Coq
    pilnuje ścisłego przestrzegania tych reguł. Dzięki temu
    wykluczona zostaje możliwość popełnienia całej gamy różnych
    błędów, które występują w językach nietypowanych, takich jak
    dodanie liczby do ciągu znaków.

    Co więcej, system typów Coqa jest jednym z najsilniejszych,
    jakie dotychczas wymyślono, dzięki czemu umożliwia nam wiele
    rzeczy, których prawie żaden inny język programowania nie potrafi,
    jak np. reprezentowanie skomplikowanych obiektów matematycznych
    i dowodzenie twierdzeń. *)

(** * Typy i termy, kanoniczność i uzasadnienie reguł eliminacji *)

(** Co to są termy? Po polsku: wyrażenia. Są to napisy zbudowane według
    pewnych reguł (które będziemy chcieli poznać), które mogą oznaczać
    przeróżne rzeczy: zdania logiczne i ich dowody, programy i ich
    specyfikacje, obiekty matematyczne takie jak liczby czy funkcje,
    struktury danych takie jak napisy czy listy.

    Najważniejszym, co wiemy o każdym termie, jest jego typ. Co to jest typ?
    To taki klasyfikator, który mówi, czego możemy się po termie spodziewać -
    można liczyć za pomocą liczb, ale nie za pomocą wartości logicznych.
    Można dowodzić zdań, ale nie napisów. Można skleić ze sobą dwa napisy,
    ale nie napis i funkcję etc.

    Każdy term ma tylko jeden typ, więc każdy typ możemy sobie wyobrazić jako
    wielki worek z termami. Dla przykładu, typ [nat], czyli typ liczb
    naturalnych, to worek, w którym są takie wyrażenia, jak:
    - [42]
    - [2 + 2]
    - [10 * 10]
    - jeżeli słowo "dupa" zawiera "i", to [123], a w przeciwnym wypadku [765]
    - długość listy [[a, b, c, d, e]]

    Najważniejsze termy są nazywane elementami. Dla [nat] są to [0], [1],
    [2], [3], [4], [5] i tak dalej. Elementy wyróżnia to, że są w postaci
    normalnej (zwanej też postacią kanoniczną). Znaczy to intuicyjnie, że
    są one ostatecznymi wynikami obliczeń, np.:
    - obliczenie [42] daje [42]
    - obliczenie [2 + 2] daje [4]
    - obliczenie [10 * 10] daje [100]
    - obliczenie ... daje [765]
    - obliczenie długości listy daje [5]

    Czym dokładnie są obliczenia, dowiemy się później. Na razie wystarczy
    nam wiedzieć, że każdy term zamknięty, czyli taki, o którym wiadomo
    wystarczająco dużo, oblicza się do postaci normalnej, np. 5 + 1 oblicza
    się do 6. Jeżeli jednak czegoś nie wiadomo, to term się nie oblicza, np.
    n + 1 nie wiadomo ile wynosi, jeżeli nie wiadomo, co oznacza n.

    Podsumowując, każdy element jest termem, a każdy term oblicza się do
    postaci normalnej, czyli do elementu. *)

(** * Typy a zbiory *)

(** Z filozoficznego punktu widzenia należy stanowczo odróżnić
    typy od zbiorów, znanych chociażby z teorii zbiorów ZF,
    która jest najpowszechniej używaną podstawą współczesnej
    matematyki:
    - zbiory są materialne, podczas gdy typy są strukturalne.
      Dla przykładu, zbiory {1, 2} oraz {2, 3} mają przecięcie
      równe {2}, które to przecięcie jest podzbiorem każdego
      z nich. W przypadku typów jest inaczej — dwa różne typy
      są zawsze rozłączne i żaden typ nie jest podtypem innego
    - relacja "x ∈ A" jest semantyczna, tzn. jest zdaniem
      logicznym i wymaga dowodu. Relacja "x : A" jest syntaktyczna,
      a więc nie jest zdaniem logicznym i nie wymaga dowodu —
      Coq jest w stanie sprawdzić automatycznie (bez pomocy
      użytkownika), czy dany term jest danego typu, a często
      także wywnioskować z kontekstu, jakiego typu jest dany
      term
    - zbiór to kolekcja obiektów, do której można włożyć cokolwiek.
      Nowe zbiory mogą być formowane ze starych w sposób niemal
      dowolny (aksjomaty są dość liberalne). Typ to kolekcja obiektów
      o takiej samej wewnętrznej naturze. Zasady formowania nowych
      typów ze starych są bardzo ścisłe
    - teoria zbiorów mówi, jakie obiekty istnieją (np. aksjomat
      zbioru potęgowego mówi, że dla każdego zbioru istnieje zbiór
      wszystkich jego podzbiorów). Teoria typów mówi, w jaki sposób
      obiekty mogą być konstruowane — różnica być może ciężko
      dostrzegalna dla niewprawionego oka, ale znaczna *)

(** * Uniwersa *)

(** Jeżeli przeczytałeś uważnie sekcję "Typy i termy" z rozdziału o logice,
    zauważyłeś zapewne stwierdzenie, że typy są termami. W połączeniu ze
    stwierdzeniem, że każdy term ma swój typ, zrodzić musi się pytanie:
    jakiego typu są typy? Zacznijmy od tego, że żeby uniknąć używania mało
    poetyckiego określenia "typy typów", typy typów nazywamy uniwersami.
    Czasami używa się też nazwy "sort", bo określenie "jest sortu" jest
    znacznie krótsze, niż "należy do uniwersum" albo "żyje w uniwersum". *)

(** [Prop], jak już wiesz, jest uniwersum zdań logicznych. Jeżeli
    [x : A] oraz [A : Prop] (tzn. [A] jest sortu [Prop]), to typ
    [A] możemy interpretować jako zdanie logiczne, a term [x]
    jako jego dowód. Na przykład [I] jest dowodem zdania [True],
    tzn. [I : True], zaś term [42] nie jest dowodem [True], gdyż
    [42 : nat]. *)

Check True.
(* ===> True : Prop *)

Check I.
(* ===> I : True *)

Check 42.
(* ===> 42 : nat *)

(** O ile jednak każde zdanie logiczne jest typem, nie każdy typ jest
    zdaniem — przykładem niech będą liczby naturalne [nat]. Sortem [nat]
    jest [Set]. Niech nie zmyli cię ta nazwa: [Set] nie ma nic wspólnego
    ze zbiorami znanymi choćby z teorii zbiorów ZF.

    [Set] jest uniwersum, w którym żyją specyfikacje. Jeżeli [x : A] oraz
    [A : Set] (tzn. sortem [A] jest [Set]), to [A] możemy interpretować
    jako specyfikację pewnej klasy programów, a term [x] jako program,
    który tę specyfikację spełnia (implementuje). Na przykład [2 + 2]
    jest programem, ktory spełnia specyfikację [nat], tzn. [2 + 2 : nat],
    zaś [fun n : nat => n] nie spełnia specyfikacji [nat], gdyż
    [fun n : nat => n : nat -> nat]. *)

Check nat.
(* ===> nat : Set *)

Check 2 + 2.
(* ===> 2 + 2 : nat *)

Check fun n : nat => n.
(* fun n : nat => n : nat -> nat *)

(** Oczywiście w przypadku typu [nat] mówiene o specyfikacji jest trochę
    na wyrost, gdyż określenie "specyfikacja" kojarzy nam się z czymś,
    co określa właściwości, jakie powinien mieć spełniający ją program.
    O takich specyfikacjach dowiemy się więcej w kolejnych rozdziałach.
    Choć każda specyfikacja jest typem, to rzecz jasna nie każdy typ jest
    specyfikacją — niektóre typy są przecież zdaniami.

    Jeżeli czytasz uważnie, to pewnie wciąż czujesz niedosyt — wszakże
    uniwersa, jako typy, także są termami. Jakiego zatem typu są uniwersa?
    Przekonajmy się. *)

Check Prop.
(* ===> Prop : Type *)

Check Set.
(* ===> Set : Type *)

(** [Prop] oraz [Set] są sortu [Type]. To stwierdzenie wciąż jednak pewnie
    nie zaspakaja twojej ciekawości. Pójdźmy więc po nitce do kłębka. *)

Check Type.
(* ===> Type : Type *)

(** Zdaje się, że osiągnęliśmy kłębek i że [Type] jest typu [Type].
    Rzeczywistość jest jednak o wiele ciekawsza. Gdyby rzeczywiście
    zachodziło [Type : Type], doszłoby do paradoksu znanego jako
    paradoks Girarda (którego omówienie jednak pominiemy). Prawda
    jest inna. *)

(* Set Printing Universes. *)

(** Uwaga: powyższa komenda zadziała jedynie w konsoli (program coqtop).
    Aby osiągnąć ten sam efekt w CoqIDE, zaznacz opcję
    [View > Display universe levels]. *)

Check Type.
(* ===> Type (* Top.7 *) : Type (* (Top.7)+1 *) *)

(** Co oznacza ten dziwny napis? Otóż w Coqu mamy do czynienia nie z
    jednym, ale z wieloma (a nawet nieskończenie wieloma) uniwersami.
    Uniwersa te są numerowane liczbami naturalnymi: najniższe uniwersum
    ma numer 0, a każde kolejne o jeden większy. Wobec tego hierarchia
    uniwersów wygląda tak (użyta notacja nie jest tą, której używa Coq;
    została wymyślona ad hoc):
    - [Set] żyje w uniwersum [Type(0)]
    - [Type(0)] żyje w uniwersum [Type(1)]
    - w ogólności, [Type(i)] żyje w uniwersum [Type(i + 1)] *)

(** Aby uniknąć paradoksu, definicje odnoszące się do typów żyjących
    na różnych poziomach hierarchii muszą same bytować w uniwersum
    na poziomie wyższym niż każdy z tych, do których się odwołują.
    Aby to zapewnić, Coq musi pamiętać, na którym poziomie znajduje
    każde użycie [Type] i odpowiednio dopasowywać poziom hierarchii,
    do którego wrzucone zostaną nowe definicje.

    Co więcej, w poprzednim rozdziale dopuściłem się drobnego kłamstewka
    twierdząc, że każdy term ma dokładnie jeden typ. W pewnym sensie nie
    jest tak, gdyż powyższa hierarcha jest _kumulatywna_ — znaczy to, że
    jeśli [A : Type(i)], to także [A : Type(j)] dla i < j. Tak więc każdy
    typ, którego sortem jest [Type], nie tylko nie ma unikalnego typu/sortu,
    ale ma ich nieskończenie wiele.

    Brawo! Czytając tę sekcję, dotarłeś do króliczej nory i posiadłeś
    wiedzę tajemną, której prawie na pewno nigdy ani nigdzie nie użyjesz.
    Możemy zatem przejść do meritum. *)

(** * Pięć rodzajów reguł *)

(** Być może jeszcze tego nie zauważyłeś, ale większość logiki konstruktywnej,
    programowania funkcyjnego, a przede wszystkim teorii typów kręci się wokół
    pięciu rodzajów reguł.
    Są to reguły:
    - formowania (ang. formation rules)
    - wprowadzania (ang. introduction rules)
    - eliminacji (ang. elimination rules)
    - obliczania (ang. computation rules)
    - unikalności (ang. uniqueness principles) *)

(** W tym podrozdziale przyjrzymy się wszystkim pięciu typom reguł. Zobaczymy
    jak wyglądają, skąd się biorą i do czego służą. Podrozdział będzie miał
    charakter mocno teoretyczny. *)

(** ** Reguły formowania *)

(** Reguły formowania mówią nam, jak tworzyć typy (termy sortów [Set] i [Type])
    oraz zdania (termy sortu [Prop]). Większość z nich pochodzi z nagłówków
    definicji induktywnych. Reguła dla typu [bool] wygląda tak: *)

(*
    ----------
    bool : Set
*)

(** Ten mistyczny zapis pochodzi z publikacji dotyczących teorii typów.
    Nad kreską znajdują się przesłanki reguły, a pod kreską znajduje się
    konkluzja reguły.

    Regułę tę możemy odczytać: [bool] jest typem sortu [Set]. Postać tej
    reguły wynika wprost z definicji typu [bool]. *)

Print bool.
(* ===> Inductive bool : Set :=  true : bool | false : bool *)

(** Powyższej regule formowania odpowiada tutaj fragment [Inductive bool : Set],
    który stwierdza po prostu, że [bool] jest typem sortu [Set].

    Nie zawsze jednak reguły formowania są aż tak proste. Reguła dla produktu
    wygląda tak: *)

(*
    A : Type, B : Type
    ------------------
    prod A B : Type
*)

(** Reguła formowania dla [prod] głosi: jeżeli [A] jest typem sortu [Type]
    oraz [B] jest typem sortu [Type], to [prod A B] jest typem sortu
    [Type]. Jest ona rzecz jasna konsekwencją definicji produktu. *)

Print prod.
(* ===> Inductive prod (A B : Type) : Type :=
          pair : A -> B -> A * B *)

(** Regule odpowiada fragment [Inductive prod (A B : Type) : Type]. To,
    co w regule jest nad kreską ([A : Type] i [B : Type]), tutaj występuje
    przed dwukropkiem, po prostu jako argumentu typu [prod]. Jak widać,
    nagłówek typu induktywnego jest po prostu skompresowaną formą reguły
    formowania.

    Należy zauważyć, że nie wszystkie reguły formowania pochodzą z definicji
    induktywnych. Tak wygląda reguła formowania dla funkcji (między typami
    sortu [Type]): *)

(*
    A : Type, B : Type
    ------------------
    A -> B : Type
*)

(** Reguła nie pochodzi z definicji induktywnej, gdyż typ funkcji [A -> B]
    jest typem wbudowanym i nie jest zdefiniowany indukcyjnie. *)

(** **** Ćwiczenie *)

(** Napisz, bez podglądania, jak wyglądają reguły formowania dla [option],
    [nat] oraz [list]. Następnie zweryfikuj swoje odpowiedzi za pomocą
    komendy [Print]. *)

(* begin hide *)

(*  [option]

    A : Type
    ---------------
    option A : Type
*)

(*  [nat]

    ---------
    nat : Set
*)

(*  [list]

    A : Type
    -------------
    list A : Type
*)

(* end hide *)

(** ** Reguły wprowadzania *)

(** Reguły wprowadzania mówią nam, w jaki sposób formować termy danego
    typu. Większość z nich pochodzi od konstruktorów typów induktywnych.
    Dla typu bool reguły wprowadzania wyglądają tak: *)

(*
    -----------
    true : bool

    ------------
    false : bool
*)

(** Reguły te stwierdzają po prostu, że [true] jest termem typu [bool]
    oraz że [false] jest termem typu [bool]. Wynikają one wprost z
    definicji typu [bool] — każda z nich odpowiada jednemu konstruktorowi.

    Wobec powyższego nie powinna zaskoczyć cię reguła wprowadzania dla
    produktu: *)

(*
    A : Type, B : Type, a : A, b : B
    --------------------------------
    pair A B a b : prod A B
*)

(** Jeżeli jednak zaskoczyła cię obecność w regule [A : Type] i [B : Type],
    przyjrzyj się dokładnie typowi konstruktora [pair]: *)

Check @pair.
(* ===> pair : forall A B : Type, A -> B -> A * B *)

(** Widać tutaj jak na dłoni, że [pair] jest funkcją zależną biorącą
    cztery argumenty i zwracają wynik, którego typ jest produktem jej
    dwóch pierwszych argumentów.

    Podobnie jak w przypadku reguł formowania, nie wszystkie reguły
    wprowadzania pochodzą od konstruktorów typów induktywnych. W
    przypadku funkcji reguła wygląda mniej więcej tak: *)

(*
    Γ |- A -> B : Type, Γ; x : A |- y : B
    -------------------------------------
    Γ |- fun x => y : A -> B
*)

(** Pojawiło się tu kilka nowych rzeczy: litera Γ oznacza kontekst,
    zaś zapis Γ |- j, że osąd j zachodzi w kontekście Γ. Zapis Γ; j
    oznacza rozszerzenie kontekstu Γ poprzez dodanie do niego osądu j.

    Regułę możemy odczytać tak: jeżeli [A -> B] jest typem sortu [Type]
    w kontekście Γ i [y] jest termem typu [B] w kontekście Γ rozszerzonym
    o osąd [x : T], to [fun x => y] jest termem typu [A -> B] w kontekście
    Γ.

    Powyższa reguła nazywana jest "lambda abstrakcją" (gdyż zazwyczaj jest
    zapisywana przy użyciu symbolu λ zamiast słowa kluczowego [fun], jak
    w Coqu). Nie przejmuj się, jeżeli jej. Znajomość reguł wprowadzania nie
    jest nam potrzebna, by skutecznie posługiwać się Coqiem.

    Należy też dodać, że reguła ta jest nieco uproszczona. Pełniejszy
    opis teoretyczny induktywnego rachunku konstrukcji można znaleźć
    w #manualu#. *)

(** **** Ćwiczenie *)

(** Napisz (bez podglądania) jak wyglądają reguły wprowadzania dla
    [option], [nat] oraz [list]. Następnie zweryfikuj swoje odpowiedzi
    za pomocą komendy [Print]. *)

(* begin hide *)

(*  [option]

    A : Type
    -----------------
    None A : option A

    A : Type, x : A
    -----------------
    Some x : option A
*)

(*  [nat]

    -------
    0 : nat

    n : nat
    ---------
    S n : nat
*)

(*  [list]

    A : Type
    ------------
    nil A : Type

    A : Type, h : A, t : list A
    ---------------------------
    h :: t : list A
*)

(* end hide *)

(** ** Reguły eliminacji *)

(** Reguły eliminacji są w pewien sposób dualne do reguł wprowadzania.
    Tak jak reguły wprowadzania dla typu [T] służą do konstruowania
    termów typu [T] z innych termów, tak reguły eliminacji dla typu [T]
    mówią nam, jak z termów typu [T] skonstruować termy innych typów.

    Zobaczmy, jak wygląda jedna z reguł eliminacji dla typu [bool]. *)

(*
    A : Type, x : A, y : A, b : bool
    --------------------------------
    if b then x else y : A
*)

(** Reguła ta mówi nam, że jeżeli mamy typ [A] oraz dwie wartości
    [x] i [y] typu [A], a także term [b] typu [bool], to możemy
    skonstruować inną wartość typu [A], mianowicie [if b then x
    else y].

    Reguła ta jest dość prosta. W szczególności nie jest ona zależna,
    tzn. obie gałęzie [if]a muszą być tego samego typu. Przyjrzyjmy
    się nieco bardziej ogólnej regule. *)

(*
    P : bool -> Type, x : P true, y : P false, b : bool
    ----------------------------------------------------
    bool_rect P x y b : P b
*)

(** Reguła ta mówi nam, że jeżeli mamy rodzinę typów [P : bool -> Type]
    oraz termy [x] typu [P true] i [y] typu [P false], a także term [b]
    typu [bool], to możemy skonstruować term [bool_rect P x y b] typu
    [P b].

    Spójrzmy na tę regułę z nieco innej strony: *)

(*
    P : bool -> Type, x : P true, y : P false
    ----------------------------------------------------
    bool_rect P x y : forall b : bool, P b
*)

(** Widzimy, że reguły eliminacji dla typu induktywnego [T] służą do
    konstruowania funkcji, których dziedziną jest [T], a więc mówią
    nam, jak "wyeliminować" term typu [T], aby uzyskać term innego typu.

    Reguły eliminacji występują w wielu wariantach:
    - zależnym i niezależnym — w zależności od tego, czy służą do definiowania
      funkcji zależnych, czy nie.
    - rekurencyjnym i nierekurencyjnym — te druge służą jedynie do
      przeprowadzania rozumowań przez przypadki oraz definiowania funkcji
      przez dopasowanie do wzorca, ale bez rekurencji. Niektóre typy nie
      mają rekurencyjnych reguł eliminacji.
    - pierwotne i wtórne — dla typu induktywnego [T] Coq generuje regułę
      [T_rect], którą będziemy zwać regułą pierwotną. Jej postać wynika
      wprost z definicji typu [T]. Reguły dla typów nieinduktywnych (np.
      funkcji) również będziemy uważać za pierwotne. Jednak nie wszystkie
      reguły są pierwotne — przekonamy się o tym w przyszłości, tworząc
      własne reguły indukcyjne.
*)

(** Zgodnie z zaprezentowaną klasyfikacją, pierwsza z naszych reguł jest:
    - niezależna, gdyż obie gałęzie [if]a są tego samego typu. Innymi słowy,
      definiujemy term typu [A], który nie jest zależny
    - nierekurencyjna, gdyż typ [bool] nie jest rekurencyjny i wobec tego
      może posiadać jedynie reguły nierekurencyjne
    - wtórna — regułą pierwotną dla [bool] jest [bool_rect] *)

(** Druga z naszych reguł jest:
    - zależna, gdyż definiujemy term typu zależnego [P b]
    - nierekurencyjna z tych samych powodów, co reguła pierwsza
    - pierwotna — Coq wygenerował ją dla nas automatycznie *)

(** W zależności od kombinacji powyższych cech reguły eliminacji mogą
    występować pod różnymi nazwami:
    - reguły indukcyjne są zależne i rekurencyjne. Służą do definiowania
      funkcji, których przeciwdziedzina jest sortu [Prop], a więc do
      dowodzenia zdań przez indukcję
    - rekursory to rekurencyjne reguły eliminacji, które służą do definiowania
      funkcji, których przeciwdziedzina jest sortu [Set] lub [Type] *)

(** Nie przejmuj się natłokiem nazw ani rozróżnień. Powyższą klasyfikację
    wymyśliłem na poczekaniu i nie ma ona w praktyce żadnego znaczenia.

    Zauważmy, że podobnie jak nie wszystkie reguły formowania i wprowadzania
    pochodzą od typów induktywnych, tak i nie wszystkie reguły eliminacji
    od nich pochodzą. Kontrprzykładem niech będzie reguła eliminacji dla
    funkcji (niezależnych): *)

(*
    A : Type, B : Type, f : A -> B, x : A
    -------------------------------------
    f x : B
*)

(** Reguła ta mówi nam, że jeżeli mamy funkcję [f] typu [A -> B] oraz
    argument [x] typu [A], to aplikacja funkcji [f] do argumentu [x]
    jest typu [B].

    Zauważmy też, że mimo iż reguły wprowadzania i eliminacji są w pewien
    sposób dualne, to istnieją między nimi różnice.

    Przede wszystkim, poza regułami wbudowanymi, obowiązuje prosta zasada:
    jeden konstruktor typu induktywnego — jedna reguła wprowadzania. Innymi
    słowy, reguły wprowadzania dla typów induktywnych pochodzą bezpośrednio
    od konstruktorów i nie możemy w żaden sposób dodać nowych. Są one w
    pewien sposób pierwotne i nie mamy nad nimi (bezpośredniej) kontroli.

    Jeżeli chodzi o reguły eliminacji, to są one, poza niewielką ilością
    reguł pierwotnych, w pewnym sensie wtórne —
    możemy budować je z dopasowania do wzorca i rekursji strukturalnej i
    to właśnie te dwie ostatnie idee są w Coqu ideami pierwotnymi. Jeżeli
    chodzi o kontrolę, to możemy swobodnie dodawać nowe reguły eliminacji
    za pomocą twierdzeń lub definiując je bezpośrednio.

    Działanie takie jest, w przypadku nieco bardziej zaawansowanych
    twierdzeń niż dotychczas widzieliśmy, bardzo częste. Ba! Częste
    jest także tworzenie reguł eliminacji dla każdej funkcji z osobna,
    perfekcyjnie dopasowanych do kształtu jej rekursji. Jest to nawet
    bardzo wygodne, gdyż Coq potrafi automatycznie wygenerować dla nas
    takie reguły.

    Przykładem niestandardowej reguły może być reguła eliminacji dla
    list działająca "od tyłu": *)

(*
    A : Type, P : list A -> Prop,
    H : P [[]],
    H' : forall (h : A) (t : list A), P t -> P (t ++ [[h]])
    -------------------------------------------------------------
    forall l : list A, P l
*)

(** Póki co wydaje mi się, że udowodnienie słuszności tej reguły będzie dla
    nas za trudne. W przyszłości na pewno napiszę coś więcej na temat reguł
    eliminacji, gdyż ze względu na swój "otwarty" charakter są one z punktu
    widzenia praktyki najważniejsze.

    Tymczasem na otarcie łez zajmijmy się inną, niestandardową regułą dla
    list. *)

(** **** Ćwiczenie *)

(** Udowodnij, że reguła dla list "co dwa" jest słuszna. Zauważ, że komenda
    [Fixpoint] może służyć do podawania definicji rekurencyjnych nie tylko
    "ręcznie", ale także za pomocą taktyk.

    Wskazówka: użycie hipotezy indukcyjnej [list_ind_2] zbyt wcześnie
    ma podobne skutki co wywołanie rekurencyjne na argumencie, który
    nie jest strukturalnie mniejszy. *)

Require Import List.
Import ListNotations.

Module EliminationRules.

Fixpoint list_ind_2
  (A : Type) (P : list A -> Prop)
  (H0 : P []) (H1 : forall x : A, P [x])
  (H2 : forall (x y : A) (l : list A), P l -> P (x :: y :: l))
    (l : list A) : P l.
(* begin hide *)
Proof.
  destruct l as [| x [| y t]]; auto.
  apply H2. apply list_ind_2; auto.
Qed.
(* end hide *)

(** **** Ćwiczenie *)

(** Napisz funkcję [apply], odpowiadającą regule eliminacji dla funkcji
    (niezależnych). Udowodnij jej specyfikację.

    Uwaga: notacja "$$" na oznaczenie aplikacji funkcji pochodzi z języka
    Haskell i jest tam bardzo często stosowana, gdyż pozwala zaoszczędzić
    stawiania zbędnych nawiasów. *)

(* begin hide *)
Definition apply {A B : Type} (f : A -> B) (x : A) : B := f x.
(* end hide *)

Notation "f $ x" := (apply f x) (at level 5).

Lemma apply_spec :
  forall (A B : Type) (f : A -> B) (x : A), f $ x = f x.
(* begin hide *)
Proof. trivial. Qed.
(* end hide *)

End EliminationRules.

(** ** Reguły obliczania *)

(** Poznawszy reguły wprowadzania i eliminacji możemy zadać sobie pytanie:
    jakie są między nimi związki? Jedną z odpowiedzi na to pytanie dają
    reguły obliczania, które określają, w jaki sposób reguły eliminacji
    działają na obiekty stworzone za pomocą reguł wprowadzania. Zobaczmy
    o co chodzi na przykładzie. *)

(*
    A : Type, B : Type, x : A |- e : B, t : A
    -----------------------------------------
    (fun x : A => e) t ≡ e{x/t}
*)

(** Powyższa reguła nazywa się "redukcja beta". Mówi ona, jaki efekt ma
    aplikacja funkcji zrobionej za pomocą lambda abstrakcji do argumentu,
    przy czym aplikacja jest regułą eliminacji dla funkcji, a lambda
    abstrakcja — regułą wprowadzania.

    Możemy odczytać ją tak: jeżeli [A] i [B] są typami, zaś [e] termem
    typu [B], w którym występuje zmienna wolna [x] typu [A], to wyrażenie
    [(fun x : A => e) t] redukuje się (symbol ≡) do [e], w którym w miejsce
    zmiennej [x] podstawiono term [t].

    Zauważ, że zarówno symbol ≡ jak i notacja [e{x/t}] są tylko nieformalnymi
    zapisami i nie mają żadnego znaczenia w Coqu.

    Nie jest tak, że dla każdego typu jest tylko jedna reguła obliczania.
    Jako, że reguły obliczania pokazują związek między regułami eliminacji
    i wprowadzania, ich ilość można przybliżyć prostym wzorem:

    ## reguł obliczania = ## reguł eliminacji * ## reguł wprowadzania,

    gdzie ## to nieformalny symbol oznaczający "ilość". W Coqowej praktyce
    zazwyczaj oznacza to, że reguł obliczania jest nieskończenie wiele,
    gdyż możemy wymyślić sobie nieskończenie wiele reguł eliminacji.
    Przykładem typu, który ma więcej niż jedną regułę obliczania dla danej
    reguły eliminacji, jest [bool]: *)

(*
    P : bool -> Type, x : P true, y : P false
    -----------------------------------------
    bool_rect P x y true ≡ x

    P : bool -> Type, x : P true, y : P false
    -----------------------------------------
    bool_rect P x y false ≡ y
*)

(** Typ [bool] ma dwie reguły wprowadzania pochodzące od dwóch konstruktorów,
    a zatem ich związki z regułą eliminacji [bool_rect] będą opisywać dwie
    reguły obliczania. Pierwsza z nich mówi, że [bool_rect P x y true]
    redukuje się do [x], a druga, że [bool_rect P x y false] redukuje się do
    [y].

    Gdyby zastąpić w nich regułe [bool_rect] przez nieco prostszą regułę, w
    której nie występują typy zależne, to można by powyższe reguły zapisać
    tak: *)

(*
    A : Type, x : A, y : A
    -----------------------------------------
    if true then x else y ≡ x

    A : Type, x : A, y : A
    -----------------------------------------
    if false then x else y ≡ y
*)

(** Wygląda dużo bardziej znajomo, prawda?

    Na zakończenie wypadałoby napisać, skąd biorą się reguły obliczania. W
    nieco mniej formalnych pracach teoretycznych na temat teorii typów są
    one zazwyczaj uznawane za byty podstawowe, z których następnie wywodzi
    się reguły obliczania takich konstrukcji, jak np. [match].

    W Coqu jest na odwrót. Tak jak reguły eliminacji pochodzą od dopasowania
    do wzorca i rekursji, tak reguły obliczania pochdzą od opisanych już
    wcześniej reguł redukcji (beta, delta, jota i zeta), a także konwersji
    alfa. *)

(** **** Ćwiczenie *)

(** Napisz reguły obliczania dla liczb naturalnych oraz list (dla reguł
    eliminacji [nat_ind] oraz [list_ind]). *)

(* begin hide *)

(*  Liczbt naturalne.

    P : nat -> Prop, H0 : P 0, HS : forall n : nat, P n -> P (S n)
    --------------------------------------------------------------
    nat_ind P H0 HS 0 ≡ H0

    P : nat -> Prop, H0 : P 0, HS : forall n : nat, P n -> P (S n), n : nat
    -----------------------------------------------------------------------
    nat_ind P H0 HS (S n) ≡ HS n (nat_ind P H0 HS n)

    Listy.

    A : Type, P : list A -> Prop, Hnil : P [],
    Hcons : forall (h : A) (t : list A), P t -> P (h :: t)
    ------------------------------------------------------
    list_ind A P Hnil Hcons [] ≡ Hnil

    A : Type, P : list A -> Prop, Hnil : P [],
    Hcons : forall (h : A) (t : list A), P t -> P (h :: t),
    h : A, t : list A
    -------------------------------------------------------
    list_ind A P Hnil Hcons (h :: t) ≡
    Hcons h t (list_ind A P Hnil Hcons t) *)

(* end hide *)

(** ** Reguły unikalności *)

(** Kolejną odpowiedzią na pytanie o związki między regułami wprowadzania
    i eliminacji są reguły unikalności. Są one dualne do reguł obliczania
    i określają, w jaki sposób reguły wprowadzania działają na obiekty
    pochodzące od reguł eliminacji. Przyjrzyjmy się przykładowi. *)

(*
    A : Type, B : Type, f : A -> B
    ------------------------------
    (fun x : A => f x) ≡ f
*)

(** Powyższa reguła unikalności dla funkcji jest nazywana "redukcją eta".
    Stwierdza ona, że funkcja stworzona za pomocą abstrakcji [fun x : A],
    której ciałem jest aplikacja [f x] jest definicyjnie równa funkcji [f].
    Regułą wprowadzania dla funkcji jest oczywiście abstrakcja, a regułą
    eliminacji — aplikacja.

    Reguły unikalności różnią się jednak dość mocno od reguł obliczania,
    gdyż zamiast równości definicyjnej ≡ mogą czasem używać standardowej,
    zdaniowej równości Coqa, czyli [=]. Nie do końca pasuje też do nich
    stwierdzenie, że określają działanie reguł wprowadzania na reguły
    eliminacji, gdyż zamiast reguł eliminacji mogą w nich występować
    inne byty, zdefiniowane jednak za pomocą reguł eliminacji. Zobaczmy
    o co chodzi na przykładzie. *)

(*
    A : Type, B : Type, p : A * B
    --------------------------------
    (fst p, snd p) = p
*)

(** Powyższa reguła głosi, że para, której pierwszym elementem jest pierwszy
    element pary [p], a drugim elementem — drugi element pary [p], jest w
    istocie równa parze [p]. W Coqu możemy ją wyrazić (i udowodnić) tak: *)

Lemma prod_uniq :
  forall (A B : Type) (p : A * B),
    (fst p, snd p) = p.
Proof.
  destruct p. cbn. trivial.
Qed.

(** Podsumowując, reguły unikalności występują w dwóch rodzajach:
    - dane nam z góry, niemożliwe do wyrażenia bezpośrednio w Coqu i
      używające równości definicyjnej, jak w przypadku redukcji eta
      dla funkcji
    - możliwe do wyrażenia i udowodnienia w Coqu, używające zwykłej
      równości, jak dla produktów i w ogólności dla typów induktywnych *)

(** **** Ćwiczenie *)

(** Sformułuj reguły unikalności dla funkcji zależnych ([forall]), sum
    zależnych ([sigT]) i [unit] (zapisz je w notacji z poziomą kreską).
    Zdecyduj, gdzie w powyższej klasyfikacji mieszczą się te reguły.
    Jeżeli to możliwe, wyraź je i udowodnij w Coqu. *)

(* begin hide *)

(*
    A : Type, P : A -> Type, f : forall x : A, P x
    ----------------------------------------------
    (fun x : A => f x) ≡ f
*)

(*
    A : Type, P : A -> Type, p : {x : A & P x}
    ------------------------------------------
    (projT1 p, projT2 p) = p
*)

(*
    u : unit
    --------
    u = tt
*)

(** Reguła dla funkcji jest pierwszego typu, zaś reguły dla sum zależnych i
    [unit] są drugiego typu. *)

Lemma sigT_uniq :
  forall (A : Type) (P : A -> Type) (p : {x : A & P x}),
    existT P (projT1 p) (projT2 p) = p.
Proof.
  intros. destruct p. cbn. f_equal.
Qed.

Lemma unit_uniq :
  forall u : unit, u = tt.
Proof.
  destruct u. trivial.
Qed.

(* end hide *)

(** * Metateoria *)

(* begin hide *)
(** TODO: Podrozdział "Metateoria" zakłada, że omówiono wcześniej pojęcie
    TODO: postaci normalnej i silnej normalizowalności. *)
(* end hide *)

(** Zbliżamy się powoli do końca rozdziału. Z jednej strony sporo się
    nauczyliśmy, ale z drugiej strony fakt ten może budzić dość spory
    niepkój. No bo niby skąd mamy wiedzieć, że cała ta logika (i teoria
    typów też) to nie są zupełne bzdury?

    Pisząc ściślej: skąd np. mamy pewność, że logika konstruktywna nie
    jest sprzeczna, tzn. nie można w niej udowodnić [False]? A jakim
    sposobem ustalić, czy przypadkiem nie zrobiłem cię w konia pisząc,
    że nie da się udowodnić prawa wyłączonego środka?

    W niniejszym podrozdziale spróbujemy udzielić krótkiej i zwięzłej
    (a co za tym idzie, bardzo zgrubnej i średnio precyzyjnej) odpowiedzi
    na te pytania. Zacznijmy od paru kluczowych uwag. *)

(** ** Preliminaria *)

(** Najpierw będziemy chcieli udowodnić, że logika konstruktywna jest
    niesprzeczna. Co w tym przypadku oznacza słowo "udowodnić"? Aż do
    teraz dowodziliśmy twierdzeń _w Coqu/w logice konstruktywnej_, ale
    teraz będziemy chcieli coś udowodnić _o Coqu/o logice konstruktywnej_.

    Ta różnica jest bardzo istotna: jeżeli chcemy udowodnić coś o Coqu,
    to nie możemy zrobić tego w Coqu. Wynika to z jednego z twierdzeń
    Gödla, które w uproszczeniu mówi, że jeżeli dany system logiczny
    potrafi wyrazić arytmetykę liczb naturalnych (no wiesz, dodawanie,
    mnożenie i takie tam), to system ten nie może udowodnić swojej
    własnej niesprzeczności.

    Jeżeli przeraża cię powyższy akapit, to... taś taś ptaszku, będzie
    dobrze. Parafrazując: żeby udowodnić, że system logiczny nie jest
    sprzeczny, musimy to zrobić w systemie logicznym, który jest od
    niego silniejszy.

    Oczywiście wnikliwy umysł wnet dostrzeże tutaj pewien problem.
    Gdy już udowodnimy w silniejszym systemie, że słabszy system
    jest niesprzeczny, to jak mamy się upewnić, czy nasze twierdzenie
    nie jest przypadkiem gówno warte, np. dlatego, że silniejszy system
    jest sprzeczny?

    W tym celu wypadałoby udowodnić również niesprzeczność silniejszego
    systemu. Zgodnie z powyższym rozumowaniem trzeba w tym celu mieć
    jeszcze silniejszy system i on również powinien być niesprzeczny, bo
    inaczej z absolutnej matematycznej pewności nici.

    Myślę, że widzisz już, dokąd to wszystko zmierza. Tego typu problem
    w filozofii nazywa się _regressus ad infinitum_, co po naszemu znaczy
    po prostu "cofanie się w nieskończoność". Niestety w naszym logicznym
    kontekście nie ma on żadnego rozwiązania.

    Trochę terminologii: słabszy system (ten, którego niesprzeczności
    chcemy dowieść), bywa zazwyczaj nazywany "teorią" lub "językiem", a
    silniejszy (ten, w którym dowodzimy) to "metateoria" lub "metajęzyk".
    Dla zmylenia przeciwnika określeniem "metateoria" określa się także
    zbiór właściwości tego słabszego systemu (a zatem np. niesprzeczność
    logiki konstruktywnej jest jej właściwością metateoretyczną).

    Uwaga: nie bój się terminologii i żargonu, one nie gryzą.

    W praktyce kiedy poważni matematycy (a raczej informatycy i logicy,
    bo matematycy sensu stricto to straszne miernoty w kwestii logiki)
    chcą udowodnić niesprzeczność jakiegoś systemu formalnego, to po
    prostu nie przejmują się niesprzecznością metateorii, w której
    dowodzą. Zazwyczaj taki dowód i tak nie jest sformalizowany, więc
    zwykłe błędy w rozumowaniu są większym problemem, niż sprzeczność
    metateorii. Praktycznym uzasadnieniem na sensowność takiego
    postępowania może być to, że w ulubionej metateorii dowodzącego od
    dawna nie znaleziono sprzeczności (np. w teorii zbiorów ZFC, której
    używa się w takich przypadkach najcześciej, nie znaleziono jej przez
    100 lat, więc wydaje się być dość bezpieczna).

    Teoretycznie, co z tego wychodzi, to względny dowód niesprzeczności,
    czyli twierdzenie postaci "jeżeli metateoria jest niesprzeczna, to
    teoria jest niesprzeczna", które w praktyce traktuje się jako
    absolutny dowód niesprzeczności.

    Dobra, wystarczy już tego ględzenia. W naszym przypadku po prostu
    zignorujemy problemy filozoficzne i przyjrzymy się nieformalnemu
    dowodowi na to, że logika konstruktywna jest niesprzeczna. *)

(** ** Niesprzeczność *)

(** Dowód jest banalnie prosty. Załóżmy, że istnieje jakiś dowód fałszu.

    Przypomnijmy sobie, że Coq jest językiem silnie normalizowalnym.
    Znaczy to, że wszystkie termy obliczają się do postaci normalnej,
    czyli termu, który jest ostatecznym wynikiem obliczeń i nie może
    zostać "jeszcze bardziej obliczony".

    Ponieważ zdania są typami, to ich certyfikaty również podlegają
    prawom rządzącym obliczeniami. Jak wyglądają certyfikaty na [False]?
    Cóż, teoretycznie mogą być postaci np. [f x], gdzie [f : P -> False],
    zaś [x : P]. Prawdziwe pytanie brzmi jednak: jak wyglądają postacie
    normalne certyfikatów na [False]?

    Przypomnijmy, że dla większości typów (a zatem także dla zdań)
    termy w postaci normalnej to te, które pojawiają się w regułach
    wprowadzania i nie inaczej jest dla [False], a ponieważ [False]
    nie ma reguły wprowadzania, to nie ma żadnego certyfikatu na [False],
    który byłby w postaci normalnej.

    Ale zaraz! Zgodnie z początkowym założeniem mamy jakiś ceryfikat na
    [False], a zatem na mocy silnej normalizowalności oblicza się on do
    certyfikatu na [False] w postaci normalnej, a to oznacza sprzeczność.
    Wobec tego początkowe założenie było błędne i nie może istnieć żaden
    certyfikat na [False].

    Słowem: nie da się udowodnić fałszu, a zatem logika konstruktywna
    jest niesprzeczna. *)

(** ** Niedowodliwość prawa wyłączonego środka *)

(** Podobnie przebiega dowód na niedowodliwość prawa wyłączonego środka.
    Zacznijmy od założenia _a contrario_, że mamy certyfikat na prawo
    wyłączonego środka, czyli [LEM : forall P : Prop, P \/ ~ P].

    Ponieważ Coq jest silnie normalizowalny, to nasz certyfikat oblicza
    się do certyfikatu w postaci normalnej. Jak wygląda postać normalna
    dla naszego certyfikatu? Postacie normalne certyfikatów na kwantyfikację
    uniwersalną [forall x : P, Q] (a zatem także na implikację [P -> Q])
    są postaci [fun p : P => q], a certyfikatów na dysjunkcję [P \/ Q] są
    postaci [or_introl p] lub [or_intror q].

    Czy powyższe fakty wystarczą, by określić, jak wygląda postać normalna
    certyfikatu na prawo wyłączonego środka? Jeszcze nie. Przecież mamy
    do dyspozycji zdanie [P], więc być może da się wycisnąć z niego trochę
    użytecznych informacji, które pozwolą nam zadecydować, czy pójść w lewo,
    czy w prawo, czyż nie?

    Otóż nie - Coqowa teoria typów zabrania wykonywania na typach i zdaniach
    dopasowań do wzorca i wszelkich innych operacji, które pozwalałyby podejmować
    decyzje jedynie na podstawie samego typu. Więcej na ten temat dowiemy się
    pod koniec tego rozdziału, gdy będziemy zajmować się parametrycznością.

    Teraz możemy już wywnioskować, że nasz certyfikat na prawo wyłączonego środka
    może mieć jedną z dwóch postaci: [fun P : Prop => or_introl p], gdzie [p : P]
    to certyfikat na [p], lub [fun P : Prop => or_intror np], gdzie [np : ~ P] to
    certyfikat na [~ P]. Rozważmy dwa przypadki.

    Jeżeli [LEM] ma pierwszą z tych dwóch postaci, to oznacza to w
    zasadzie, że wszystkie zdania są prawdziwe! No bo patrz: jeżeli
    przyjrzymy się [LEM False], to widzimy, że przyjmuje on postać
    [or_introl p], gdzie [p] jest certyfikatem na [False], ale wiemy
    już, że [False] nie da się udowodnić.

    Podobnie gdy [LEM] ma drugą z tych postaci. Wtedy [LEM True] jest
    postaci [or_intror p], gdzie [p] to certyfikat na [True -> False],
    a zatem [p I] to certyfikat na [False], ale znów - fałszu nie da
    się udowodnić!

    Ponieważ w obu przypadkach uzyskaliśmy sprzeczność, a [LEM] nie
    może być żadnej innej postaci, konkluzja jest oczywista: początkowe
    założenie było błędne i certyfikat na prawo wyłączonego środka nie
    istnieje. *)

(** ** Konkluzja *)

(** Silna normalizowalność jest jedną z kluczowych metateoretycznych
    właściwości logik i języków programowania. Wynikają z niej nie
    tylko inne metateoretyczne właściwości tradycyjnie uznawane za
    ważniejsze, jak niesprzeczność, ale także bardziej ciekawostkowe,
    jak niedowodliwość prawa wyłączonego środka.

    Nie przejmuj się, jeżeli nie do końca rozumiesz (albo wcale nie
    rozumiesz) powyższe wywody (szczególnie preliminaria) lub dowody.
    Ich rozumienie nie jest niezbędne do skutecznego dowodzenia ani
    programowania. Ba! Wydaje mi się, że jest całkiem na odwrót: żeby
    zrozumieć je na intuicyjnym poziomie, potrzeba sporo praktycznego
    doświadczenia w programowaniu i dowodzeniu. Jeżeli go nabędziesz,
    powyższe wywody i dowody nagle staną się łatwe, miłe i przyjemne
    (i puszyste i mięciutkie!). Wróć do nich za jakiś, żeby się o tym
    przekonać. *)

(** * Parametryczność *)

(* begin hide *)

(** TODO: Odkłamać kwestię i wyjaśnić polimorfizmu.
    Najlepiej zrobić to za pomocą podziału na funkcje parametryczne
    (czyli takie, które na wszystkich typach działają tak samo) oraz
    ezoteryczną homoklasyczną magię, która wszystko to psuje. *)

(** Niech [A B : Type]. Zadajmy sobie następujące pytanie: ile jest funkcji
    typu [A -> B]? Żeby ułatwić sobie zadanie, ograniczmy się jedynie do
    typów, które mają skończoną ilość elementów.

    Nietrudno przekonać się, że ich ilość to |B|^|A|, gdzie ^ oznacza
    potęgowanie, zaś |T| to ilość elementów typu [T] (ta notacja nie ma
    nic wspólnego z Coqiem — zaadaptowałem ją z teorii zbiorów jedynie
    na potrzeby tego podrozdziału).

    Udowodnić ten fakt możesz (choć póki co nie w Coqu) posługując się
    indukcją po ilości elementów typu [A]. Jeżeli [A] jest pusty, to
    jest tylko jedna taka funkcja, o czym przekonałeś się już podczas
    ćwiczeń w podrozdziale o typie [Empty_set]. *)

(** **** Ćwiczenie *)

(** Udowodnij (nieformalnie, na papierze), że w powyższym akapicie nie
    okłamałem cię. *)

(** **** Ćwiczenie *)

(** Zdefiniuj wszystkie możliwe funkcje typu [unit -> unit], [unit -> bool]
    i [bool -> bool]. *)

(** Postawmy sobie teraz trudniejsze pytanie: ile jest funkcji typu
    [forall A : Type, A -> A]? W udzieleniu odpowiedzi pomoże nam
    parametryczność — jedna z właściwości Coqowego polimorfizmu.

    Stwierdzenie, że polimorfizm w Coqu jest parametryczny, oznacza, że
    funkcja biorąca typ jako jeden z argumentów działa w taki sam sposób
    niezależnie od tego, jaki typ przekażemy jej jako argument.

    Konsekwencją tego jest, że funkcje polimorficzne nie wiedzą (i nie
    mogą wiedzieć), na wartościach jakiego typu operują. Wobec tego
    elementem typu [forall A : Type, A -> A] nie może być funkcja, która
    np. dla typu [nat] stale zwraca [42], a dla innych typów po prostu
    zwraca przekazany jej argument.

    Stąd konkludujemy, że typ [forall A : Type, A -> A] ma tylko jeden
    element, a mianowicie polimorficzną funkcję identycznościową. *)

Definition id' : forall A : Type, A -> A :=
  fun (A : Type) (x : A) => x.

(** **** Ćwiczenie *)

(** Zdefiniuj wszystkie elementy następujących typów lub udowodnij, że
    istnienie choć jednego elementu prowadzi do sprzeczności:
    - [forall A : Type, A -> A -> A]
    - [forall A : Type, A -> A -> A -> A]
    - [forall A B : Type, A -> B]
    - [forall A B : Type, A -> B -> A]
    - [forall A B : Type, A -> B -> B]
    - [forall A B : Type, A -> B -> A * B]
    - [forall A B : Type, A -> B -> sum A B]
    - [forall A B C : Type, A -> B -> C]
    - [forall A : Type, option A -> A]
    - [forall A : Type, list A -> A] *)

Lemma no_such_fun :
  (forall A B : Type, A -> B) -> False.
Proof.
  intros. exact (X nat False 42).
Qed.

Lemma no_such_fun_2 :
  (forall A B C : Type, A -> B -> C) -> False.
Proof.
  intro H. apply (H True True); trivial.
Qed.

Lemma no_such_fun_3 :
  (forall A : Type, option A -> A) -> False.
Proof.
  intro H. apply H. exact None.
Qed.

Lemma no_such_fun_4 :
  (forall A : Type, list A -> A) -> False.
Proof.
  intro H. apply H. exact nil.
Qed.
(* end hide *)