Library IndProp_ko_utf8
Require Export Logic.
귀납적으로 정의한 명제
- 규칙 ev_0: 숫자 0은 짝수. 규칙 ev_SS: 만약 n이 짝수면
- S (S n)은 짝수.
(ev_0) ev 0
(ev_SS) ev (S (S n))
(ev_0) ev 0
(ev_SS) ev 2
(ev_SS) ev 4
Inductive ev : nat → Prop :=
| ev_0 : ev 0
| ev_SS : ∀ n : nat, ev n → ev (S (S n)).
이 정의는 이전에 Inductive를 사용한 사례들과 한 가지 중요한
점에서 다르다. 그 결과가 Type이 아니라 nat에서 Prop로 가는
함수라는 것이다. 즉, 숫자의 성질이다. list와 같이 Type → Type
타입을 갖는 함수를 만드는 귀납적 정의들을 이미 보아왔다. 여기서
새로운 것은 ev의 nat 인자가 콜론 _오른쪽_에 _이름 없이_
나타났기 때문에 다른 생성자들의 타입에 다른 값을 취하는 것을
허용하는 것이다. ev_0의 타입에는 0을, ev_SS의 타입에는 S
(S n)을 사용하고 있다.
이와 대조적으로 list 정의에서는 X 매개변수를 콜론 _왼쪽_에
_전역적으로_ 이름을 부여했고, nil과 cons의 결과가 (list X)로
동일하도록 만들었다. 만일 ev를 정의하면서 그 왼쪽에 nat을
도입하려고 한다면 다음의 에러가 발생할 것이다.
Fail Inductive wrong_ev (n : nat) : Prop :=
| wrong_ev_0 : wrong_ev 0
| wrong_ev_SS : ∀ n, wrong_ev n → wrong_ev (S (S n)).
(여기서 "매개변수"는 Inductive 정의에서 콜론 왼쪽에 나타난
인자를 가리키는 콕의 용어이다. 콜론 오른쪽에 나타난 인자를 지칭할
때 "인덱스"를 사용한다.)
ev 정의를 ev_0 : ev 0과 ev_SS : ∀ n, ev n → ev (S (S
n))이라는 두 가지 정리들과 함께 콕 성질 ev : nat → Prop를
정의하는 것으로 생각할 수 있다. 이러한 "생성자 정리들"은 증명한
정리들과 동일한 상태를 갖는다. 특히 특별한 숫자들에 대해 ev를
증명하기 위해 콕의 apply 전술에서 이 규칙 이름들을 지정할 수
있다.
Theorem ev_4 : ev 4.
Proof. apply ev_SS. apply ev_SS. apply ev_0. Qed.
... 또는 아래와 같이 함수 적용 구문을 사용할 수 있다.
Theorem ev_4' : ev 4.
Proof. apply (ev_SS 2 (ev_SS 0 ev_0)). Qed.
ev를 포함하는 가정들을 갖는 정리들을 증명할 수도 있다.
Theorem ev_plus4 : ∀ n, ev n → ev (4 + n).
Proof.
intros n. simpl. intros Hn.
apply ev_SS. apply ev_SS. apply Hn.
Qed.
Theorem ev_double : ∀ n,
ev (double n).
Proof.
Admitted.
ev (double n).
Proof.
Admitted.
☐
증명에서 증거를 사용하기
- E는 ev_0 (그리고 n은 O) 또는
- E는 ev_SS n' E' (그리고 n은 S (S n')이고 E'은 ev n'에 대한 증거이다)
증거에 대한 인버전
- 그 증거가 ev_0 형태라면 n = 0이라는 것을 안다.
- 만일 그렇지 않다면 그 증거는 ev_SS n' E' 형태이고 n = S (S n')이며 E'은 ev n'에 대한 증거이어야 한다.
Theorem ev_minus2 : ∀ n,
ev n → ev (pred (pred n)).
Proof.
intros n E.
inversion E as [| n' E'].
- simpl. apply ev_0.
- simpl. apply E'. Qed.
위 콕 증명에서 인버전 추론이 어떻게 동작하는지 풀어 설명하면
다음과 같다.
이 특별한 증명 사례는 inversion을 destruct로 바꾸어도 증명
가능하다.
- ev_0 형태 증거가 주어지면 n = 0이라는 것을 안다. 그러므로
ev (pred (pred 0))이 성립하는 것을 보이는 것으로 충분하다.
pred 정의에 따라 ev 0을 보이는 것과 동일하며 ev_0에 의해
바로 증명할 수 있다.
- 그렇지 않으면, 이 증거는 반드시 ev_SS n' E' 형태이고 n = S (S n')이며 E'은 ev n'에 대한 증거이어야 한다. 그다음에 ev (pred (pred (S (S n'))))이 성립함을 보여야 한다. 이를 단순하게 변환한 다음 E'으로 바로 증명할 수 있다.
Theorem ev_minus2' : ∀ n,
ev n → ev (pred (pred n)).
Proof.
intros n E.
destruct E as [| n' E'].
- simpl. apply ev_0.
- simpl. apply E'. Qed.
두 전술을 사용한 증명들의 차이는, 복잡한 식(변수 한개로 이루어진
식과 반대로)에 적용된 귀납적 성질을 나타내는 가정에 대해서는
inversion이 더 편리하다는 것이다. 여기 구체적인 예를 보자.
ev_minus2의 변형으로 다음을 증명하기를 원한다고 가정하자.
Theorem evSS_ev : ∀ n,
ev (S (S n)) → ev n.
직관적으로 가정에 대한 증거는 ev_0 생성자가 아니라는 것을
안다. 왜냐하면 O와 S는 nat 타입의 다른 생성자들이기
때문이다. 따라서 ev_SS가 적용할 유일한 경우이다. 불행히도
destruct는 이러한 상황을 고려하지 않기 때문에 두 개의 부분
목적들을 여전히 생성한다. 더 심각한 것은 이 전술을 사용해면 최종
목적이 변하지 않아서 이 증명을 완성하는데 어떠한 유용한 정보도
주지 않는 것이다.
Proof.
intros n E.
destruct E as [| n' E'].
-
Abort.
정확히 어떤 상황이 벌어진 것인가? destruct를 사용하면 각 성질의
인자가 나타난 곳을 모두 각 생성자에 대응하는 값들로 바꾸는 효과가
있다. ev_minus2'의 경우에는 이 인자 n이 최종 목적에 바로
사용되었으므로 이렇게 바꾸는 것으로 충분하다. 하지만 evSS_ev의
경우에 대체되는 식 (S (S n))이 어디에도 다시 나타나지 않기
때문에 도움이 되지 않는다.
반면에 inversion 전술로 (1) 첫번째 경우가 적용되지 않고, (2)
ev_SS에 나타난 n'이 n과 동일해야 한다는 것을 찾아낼 수 있다.
이 방법으로 증명을 완성한다.
Theorem evSS_ev : ∀ n,
ev (S (S n)) → ev n.
Proof.
intros n E.
inversion E as [| n' E'].
apply E'.
Qed.
inversion을 사용하여 귀납적 성질을 포함하는 "명백하게 모순인"
가정들에 폭발 원리를 적용할 수도 있다. 예를 들어:
Theorem one_not_even : ¬ ev 1.
Proof.
intros H. inversion H. Qed.
Theorem SSSSev__even : ∀ n,
ev (S (S (S (S n)))) → ev n.
Proof.
Admitted.
Theorem even5_nonsense :
ev 5 → 2 + 2 = 9.
Proof.
Admitted.
☐
여기서 inversion을 사용한 방법은 처음에는 다소 불가사의하게 보일
수 있다. 지금까지는 등식 형태의 명제에 대해서만 inversion을
사용하며 생성자의 단사 성질을 사용하거나 다른 생성자들을
구별하였다. 하지만 귀납적으로 정의된 명제들에 대한 증거를
분석하는 데에도 inversion을 적용할 수 있음을 여기서 확인한다.
inversion이 동작하는 일반적인 방법을 설명한다. 이름 I는 현재
문맥에 있는 가정 P를 참조하고 P는 Inductive 선언으로
정의되었다고 가정하자. 그렇다면 P의 각 생성자에 대해 inversion
I는 P를 증명하기 위해 사용했을 수 있는 이 생성자의 특정 조건
그대로를 I가 나타난 모든 곳에 대체하여 부분 목적을 생성한다. 이
부분 목적들 중 일부는 스스로 모순일 수 있다. inversion은 이러한
부분 목적들을 버린다. 남은 부분 목적들은 원래 목적이 성립하려면
증명되어야 할 경우들을 나타낸다. inversion 전술로 남은 부분
목적을 위해 P에 주어진 인자들에 대해 성립해야 할 증명 문맥으로
모든 등식들을 추가한다 (예를 들어 evSS_ev 증명에서 S (S n') =
n).
위의 ev_double 연습문제는 짝수에 대한 우리의 새로운 개념은 이전
두 연습문제들로 부터 유도할 수 있음을 보인다. (논리 장에서
even_bool_prop에 의해 서로 동일한 것을 이미 알고 있다. 세 개
모두가 일치하는 것을 보이려면 다음 보조 정리만 필요하다.
Lemma ev_even_firsttry : ∀ n,
ev n → ∃ k, n = double k.
Proof.
경우별 분석이나 n에 대한 귀납법으로 증명하는 것을 시도할 수도
있다. 하지만 ev는 전제에 나타나있기 때문에 이전에 논의한 바와
같이 이 전략은 분명히 막다른 골목에 다다를 것이다. ev의 증거에
대한 인버전을 먼저 시도하는 것이 나은 것 처럼 보인다. 정말로 첫
번째 경우는 사소하게 해결될 수 있다.
intros n E. inversion E as [| n' E'].
-
∃ 0. reflexivity.
- simpl.
불행히도 두 번째 경우는 더 어렵다. ∃ k, S (S n') = double
k를 보일 필요가 있지만, 오직 사용할 수 있는 가정은 ev n'이
성립함을 보이는 E'이다. 이 가정은 바로 사용되는 것은 아니기
때문에 막힌 것 같고 E에 대한 경우 별 분석을 시간 낭비였던 것으로
보인다.
두 번째 목적을 더 가까이 살펴보면 흥미로운 일이 발생했음을 알 수
있다. E에 대한 경우 별 분석을 수행하여 원래 결과를 ev: E'을
증명하는 _다른_ 증명을 포함하는 유사한 결과로 변환할 수 있었다. 더
형식적으로 다음을 증명하면 전체 증명을 마칠 수 있다.
exists k', n' = double k',
이 문장은 원래 문장과 동일하지만 n 대신 n'으로
바뀌었다. 정말로 콕에서 이 중간 결과로 충분함을 보이는 것은 어렵지
않다.
assert (I : (∃ k', n' = double k') →
(∃ k, S (S n') = double k)).
{ intros [k' Hk']. rewrite Hk'. ∃ (S k'). reflexivity. }
apply I.
Admitted.
증거에 대한 귀납법
Lemma ev_even : ∀ n,
ev n → ∃ k, n = double k.
Proof.
intros n E.
induction E as [|n' E' IH].
-
∃ 0. reflexivity.
-
destruct IH as [k' Hk'].
rewrite Hk'. ∃ (S k'). reflexivity.
Qed.
콕은 ev의 정의에서 단일 재귀 형태가 나타난 부분 E'에 대응하는
IH를 만든 것을 볼 수 있다. E'에 n'이 나타나기 때문에 귀납적
가정은 n이나 어떤 다른 숫자가 아닌 n'에 대한 것이다.
짝수에 대한 두 번째와 세 번째 정의가 동일함을 이제 증명한다.
Theorem ev_even_iff : ∀ n,
ev n ↔ ∃ k, n = double k.
Proof.
intros n. split.
- apply ev_even.
- intros [k Hk]. rewrite Hk. apply ev_double.
Qed.
나중에 보게 되듯이 증거에 대한 귀납법은 많은 분야를 거쳐서
반복해서 나타나는 기법이다. 특히 많은 성질들을 귀납적으로 정의하는
경우인 프로그래밍언어 의미를 형식화할 때 많이 사용한다.
이 방법에 익숙해지도록 이 방법을 사용하는 간단한 예를 보여준다.
연습문제: 별 두 개 (ev_sum)
Theorem ev_sum : ∀ n m, ev n → ev m → ev (n + m).
Proof.
Admitted.
Proof.
Admitted.
☐
연습문제: 별 네 개, 고급, 선택사항 (ev_alternate)
일반적으로 어떤 성질을 귀납적으로 정의하는 방법은 여러 가지이다. 다음은 ev 성질을 다소 복잡하게 정의한 예이다.Inductive ev' : nat → Prop :=
| ev'_0 : ev' 0
| ev'_2 : ev' 2
| ev'_sum : ∀ n m, ev' n → ev' m → ev' (n + m).
이 정의가 원래 정의와 논리적으로 동치임을 증명하시오. (귀납적 단계에
이르면 이전 정리를 보고 싶어할 수도 있다.)
Theorem ev'_ev : ∀ n, ev' n ↔ ev n.
Proof.
Admitted.
Theorem ev_ev__ev : ∀ n m,
ev (n+m) → ev n → ev m.
Proof.
Admitted.
☐
연습문제: 별 세 개, 선택사항 (ev_plus_plus)
이 연습문제는 이전에 증명해놓은 보조 정리를 적용하기만 하면 된다. 다시 작성하는 전술로 증명한 것 중에 어떤 것은 지루할 수 있지만 귀납법이나 경우 별 분석이 필요하지 않다.Theorem ev_plus_plus : ∀ n m p,
ev (n+m) → ev (n+p) → ev (m+p).
Proof.
Admitted.
☐
귀납적 관계
Module Playground.
숫자에 관한 "작거나 같다"라는 관계가 한가지 유용한 예이다.
다음 예는 꽤 직관적이다. 한 숫자가 다른 숫자보다 작거나 같음을
보이는 증거를 제시하는 두 가지 방법이 있다고 말한다. 두 숫자가
같거나 또는 첫 번째 숫자가 두 번째의 이전 숫자 보다 작거나 같다는
증거를 제시한다.
Inductive le : nat → nat → Prop :=
| le_n : ∀ n, le n n
| le_S : ∀ n m, (le n m) → (le n (S m)).
Notation "m <= n" := (le m n).
생성자 le_n과 le_S를 사용하여 ≤에 대한 사실들을 증명하는
것은 위의 ev처럼 성질에 대해 증명하는 것과 똑같은 패턴을
따른다. ≤ 목적 (예를 들어, 3<=3 또는 3<=6을 보이기 위해)을
증명하기 위해 이 생성자들을 apply할 수 있고, 문맥에 있는 ≤
가정 (예를 들어 (2 ≤ 1) → 2+2=5를 증명하기 위해서)에서 정보를
추출하기 위해서 inversion 같은 전술을 사용할 수 있다.
여기서 이 정의가 제대로 이루어졌는지 검사한다. (비록 처음 몇몇
강의에서 작성하였던 테스트 함수들에 대해 제공했던 것과 동일한
종류의 간단한 "유닛 테스트"이지만 그 증명을 명시적으로 만들어야
한다. simpl과 reflexivity 전술들은 검사하는데 더 이상 도움이
되지 않는다. 왜냐하면 그 증명들은 단지 계산을 단순화하는 것이
아니기 때문이다.)
Theorem test_le1 :
3 ≤ 3.
Proof.
apply le_n. Qed.
Theorem test_le2 :
3 ≤ 6.
Proof.
apply le_S. apply le_S. apply le_S. apply le_n. Qed.
Theorem test_le3 :
(2 ≤ 1) → 2 + 2 = 5.
Proof.
intros H. inversion H. inversion H2. Qed.
"엄격히 작은" 관계 n < m은 이제 le에 관하여 정의할 수 있다.
End Playground.
Definition lt (n m:nat) := le (S n) m.
Notation "m < n" := (lt m n).
여기 숫자에 대한 두 세가지 더 간단한 관계를 정의하였다.
Inductive square_of : nat → nat → Prop :=
| sq : ∀ n:nat, square_of n (n × n).
Inductive next_nat : nat → nat → Prop :=
| nn : ∀ n:nat, next_nat n (S n).
Inductive next_even : nat → nat → Prop :=
| ne_1 : ∀ n, ev (S n) → next_even n (S n)
| ne_2 : ∀ n, ev (S (S n)) → next_even n (S (S n)).
☐
연습문제: 별 세 개, 선택사항 (le_exercises)
≤과 < 관계에 대한 많은 사실들이 여기 있다. 이 강의에서 나중에 필요하다. 이 사실들에 대한 증명은 좋은 실용적인 연습문제가 된다.Lemma le_trans : ∀ m n o, m ≤ n → n ≤ o → m ≤ o.
Proof.
Admitted.
Theorem O_le_n : ∀ n,
0 ≤ n.
Proof.
Admitted.
Theorem n_le_m__Sn_le_Sm : ∀ n m,
n ≤ m → S n ≤ S m.
Proof.
Admitted.
Theorem Sn_le_Sm__n_le_m : ∀ n m,
S n ≤ S m → n ≤ m.
Proof.
Admitted.
Theorem le_plus_l : ∀ a b,
a ≤ a + b.
Proof.
Admitted.
Theorem plus_lt : ∀ n1 n2 m,
n1 + n2 < m →
n1 < m ∧ n2 < m.
Proof.
unfold lt.
Admitted.
Theorem lt_S : ∀ n m,
n < m →
n < S m.
Proof.
Admitted.
Theorem leb_complete : ∀ n m,
leb n m = true → n ≤ m.
Proof.
Admitted.
힌트: 다음 정리는 m에 대한 귀납법으로 가장 쉽게 증명될 수 있다.
Theorem leb_correct : ∀ n m,
n ≤ m →
leb n m = true.
Proof.
Admitted.
힌트: 이 정리는 induction을 사용하지 않고 쉽게 증명할 수 있다.
Theorem leb_true_trans : ∀ n m o,
leb n m = true → leb m o = true → leb n o = true.
Proof.
Admitted.
Theorem leb_iff : ∀ n m,
leb n m = true ↔ n ≤ m.
Proof.
Admitted.
leb n m = true ↔ n ≤ m.
Proof.
Admitted.
☐
Module R.
연습문제: 별 세 개, 추천 (R_provability)
삼 중 관계, 사 중 관계 등을 이진 관계와 동일한 방식으로 정의할 수 있다. 예를 들어, 숫자에 대한 다음과 같은 삼 중 관계를 보자.Inductive R : nat → nat → nat → Prop :=
| c1 : R 0 0 0
| c2 : ∀ m n o, R m n o → R (S m) n (S o)
| c3 : ∀ m n o, R m n o → R m (S n) (S o)
| c4 : ∀ m n o, R (S m) (S n) (S (S o)) → R m n o
| c5 : ∀ m n o, R m n o → R n m o.
- 다음 명제들 중 어느 것이 증명 가능한가?
- R 1 1 2
- R 2 2 6
- 정의 R에서 생성자 c5를 빼면 증명 가능한 명제 집합이 변할까?
(한 문장으로) 간단히 답을 하시오.
- 정의 R에서 생성자 c4를 빼면 증명 가능한 명제 집합이 변할까? (한 문장으로) 간단히 답을 하시오.
연습문제: 별 세 개, 선택사항 (R_fact)
위의 관계 R은 사실 익숙한 함수를 인코딩한다. 어떤 함수인지 맞추어보시오. 그런 다음 콕에서 다음 동치를 기술하고 증명하시오.Definition fR : nat → nat → nat
. Admitted.
Theorem R_equiv_fR : ∀ m n o, R m n o ↔ fR m n = o.
Proof.
Admitted.
☐
End R.
연습문제: 별 네 개, 고급 (subsequence)
첫번째 리스트의 모든 원소들이 동일한 순서로 두번째 리스트에 나타나면 첫번째 리스트는 두 번째 리스트의 _부분 순열_이다. 두 번째 리스트에는 추가적으로 어떤 원소들이 사이 사이에 포함될 수 있다. 예를 들어,- list nat에 관한 귀납적 명제 subseq를 정의해서 부분 순열이
의미하는 바를 표현해보시오. (힌트: 세 가지 경우가 필요할
것이다.)
- 부분 순열이 반사적임을 기술하는 subseq_refl을 증명하시오. 즉,
어떠한 리스트도 자신의 부분 순열이다.
- 어떤 리스트 l1, l2, l3에 대해 l1이 l2의 부분 순열이면
l1이 또한 l2 ++ l3의 부분 순열임을 기술하는 subseq_app를
증명하시오.
- (선택사항, 더 어려움) 부분 순열이 전이적임을 subseq_trans 증명하시오. 즉 l1이 l2의 부분 순열이고 l2가 l3의 부분 순열이면 l1은 l3의 부분 순열이다. 힌트: 주의깊게 귀납법을 선택하시오!
☐
Inductive R : nat -> list nat -> Prop :=
| c1 : R 0 ☐
| c2 : forall n l, R n l -> R (S n) (n :: l)
| c3 : forall n l, R (S n) l -> R n l.
다음 명제들 중 어느 것을 증명할 수 있는가?
☐
연습문제: 별 두 개, 선택사항 (R_provability2)
콕으로 아래와 같이 정의한다고 가정하자.- R 2 [1;0]
- R 1 [1;2;1;0]
- R 6 [3;2;1;0]
사례 연구: 정규식
Inductive reg_exp (T : Type) : Type :=
| EmptySet : reg_exp T
| EmptyStr : reg_exp T
| Char : T → reg_exp T
| App : reg_exp T → reg_exp T → reg_exp T
| Union : reg_exp T → reg_exp T → reg_exp T
| Star : reg_exp T → reg_exp T.
Arguments EmptySet {T}.
Arguments EmptyStr {T}.
Arguments Char {T} _.
Arguments App {T} _ _.
Arguments Union {T} _ _.
Arguments Star {T} _.
이 정의는 _다형성_을 지닌다는 점을 참고하시오. reg_exp T의
정규식은 T 타입(T 타입의 원소들로 이루어진 리스트들)의
문자들로 구성하는 문자열을 기술한다.
(보통 T 타입은 유한한 원소로 구성되어 있도록 가정하나 그러한
가정을 요구하지 않는다. 이러한 점은 일반적인 관행과 약간 다르다.)
다음 규칙에 따라 정규식과 문자열을 연결한다. 이 규칙은 정규식이
어떤 문자열에 _매치_하는가를 정의한다.
이렇게 서술한 정의를 Inductive 정의로 다음과 같이 쉽게 변환할 수 있다.
- 식 EmptySet은 어느 문자열에도 매치하지 않는다.
- 식 EmptyStr는 빈 문자열 []에 매치한다.
- 식 Char x는 한 문자로 된 문자열 [x]에 매치한다.
- re1이 s1에 매치하고 re2가 s2에 매치하면 App re1 re2는
s1 ++ s2에 매치한다.
- re1과 re2 중 적어도 어느 하나가 s에 매치하면 Union re1 re2는
s에 매치한다.
- 마지막으로 어떤 문자열 s를 s = s_1 ++ ... ++ s_k 문자열의 순열을
붙인 것이고 re는 s_i 문자열들 각각에 매치하면 Star re는 s에
매치한다.
Inductive exp_match {T} : list T → reg_exp T → Prop :=
| MEmpty : exp_match [] EmptyStr
| MChar : ∀ x, exp_match [x] (Char x)
| MApp : ∀ s1 re1 s2 re2,
exp_match s1 re1 →
exp_match s2 re2 →
exp_match (s1 ++ s2) (App re1 re2)
| MUnionL : ∀ s1 re1 re2,
exp_match s1 re1 →
exp_match s1 (Union re1 re2)
| MUnionR : ∀ re1 s2 re2,
exp_match s2 re2 →
exp_match s2 (Union re1 re2)
| MStar0 : ∀ re, exp_match [] (Star re)
| MStarApp : ∀ s1 s2 re,
exp_match s1 re →
exp_match s2 (Star re) →
exp_match (s1 ++ s2) (Star re).
다시 반복하면, 가독성을 높이기 위해 추론 규칙 표기법을 사용하여 이
정의를 표시할 수도 있다. 동시에 더 읽기 편한 중위 표기법을
도입하자.
Notation "s =~ re" := (exp_match s re) (at level 80).
(MEmpty) ☐ =~ EmptyStr
(MChar) x =~ Char x
(MApp) s1 ++ s2 =~ App re1 re2
(MUnionL) s1 =~ Union re1 re2
(MUnionR) s2 =~ Union re1 re2
(MStar0) ☐ =~ Star re
(MStarApp) s1 ++ s2 =~ Star re
Example reg_exp_ex1 : [1] =~ Char 1.
Proof.
apply MChar.
Qed.
Example reg_exp_ex2 : [1; 2] =~ App (Char 1) (Char 2).
Proof.
apply (MApp [1] _ [2]).
- apply MChar.
- apply MChar.
Qed.
(마지막 예제에서 MApp를 문자열 [1]과 [2]에 어떻게 바로
적용하는지 주목하시오. 증명 목적에 [1] ++ [2] 대신 [1; 2]이
나타나 있기 때문에 콕은 이 문자열을 어떻게 분리할지 스스로 알지
못할 것이다.)
inversion을 사용하여 어떤 문자열이 주어진 정규식에 매치되지
_않는지_도 보여줄 수 있다.
Example reg_exp_ex3 : ¬ ([1; 2] =~ Char 1).
Proof.
intros H. inversion H.
Qed.
도움 함수들을 정의해서 정규식을 작성하는 것을 도와줄 수
있다. reg_exp_of_list 함수는 인자로 받은 리스트를 정확히
매치하는 정규식을 만든다.
Fixpoint reg_exp_of_list {T} (l : list T) :=
match l with
| [] ⇒ EmptyStr
| x :: l' ⇒ App (Char x) (reg_exp_of_list l')
end.
Example reg_exp_ex4 : [1; 2; 3] =~ reg_exp_of_list [1; 2; 3].
Proof.
simpl. apply (MApp [1]).
{ apply MChar. }
apply (MApp [2]).
{ apply MChar. }
apply (MApp [3]).
{ apply MChar. }
apply MEmpty.
Qed.
exp_match에 대한 일반적인 사실들을 증명할 수도 있다. 예를 들어,
다음 보조 정리는 re에 매치하는 모든 문자열 s는 Star re에도
매치함을 보여준다.
Lemma MStar1 :
∀ T s (re : reg_exp T) ,
s =~ re →
s =~ Star re.
Proof.
intros T s re H.
rewrite <- (app_nil_r _ s).
apply (MStarApp s [] re).
- apply H.
- apply MStar0.
Qed.
(app_nil_r을 사용해서 이 정리의 목적을 MStarApp에서 기대하는
것과 정확히 동일한 형태로 변경하는 것에 주목하시오.)
연습문제: 별 세 개 (exp_match_ex1)
다음 보조 정리는 이 장의 처음 부분에서 서술한 매칭 규칙들은 형식적인 귀납적 정의로부터 얻을 수 있음을 보인다.Lemma empty_is_empty : ∀ T (s : list T),
¬ (s =~ EmptySet).
Proof.
Admitted.
Lemma MUnion' : ∀ T (s : list T) (re1 re2 : reg_exp T),
s =~ re1 ∨ s =~ re2 →
s =~ Union re1 re2.
Proof.
Admitted.
다음 보조 정리는 Poly 장의 fold 함수에 관하여 서술되어 있다.
만일 ss : list (list T)가 문자열들의 순열 s1, ..., sn을
표현한다면 fold app ss []는 그 문자열들 모두 함께 붙인 결과이다.
Lemma MStar' : ∀ T (ss : list (list T)) (re : reg_exp T),
(∀ s, In s ss → s =~ re) →
fold app ss [] =~ Star re.
Proof.
Admitted.
Lemma reg_exp_of_list_spec : ∀ T (s1 s2 : list T),
s1 =~ reg_exp_of_list s2 ↔ s1 = s2.
Proof.
Admitted.
☐
exp_match는 재귀적 구조이기 때문에 정규식 관련 증명들은 증명에
대한 귀납적 방법을 빈번하게 요구할 것으로 기대할 수 있다. 예를
들어 다음의 직관적인 결과를 증명하고 싶다고 가정하자. 정규식
re가 어떤 문자열 s에 매치하면 s의 모든 원소들은 re에
반드시 나타나야 한다. 이 정리를 기술하기 위해서 re_chars 함수를
먼저 정의한다. 이 함수는 정규식에 나타난 모든 문자들을 나열한다.
Fixpoint re_chars {T} (re : reg_exp T) : list T :=
match re with
| EmptySet ⇒ []
| EmptyStr ⇒ []
| Char x ⇒ [x]
| App re1 re2 ⇒ re_chars re1 ++ re_chars re2
| Union re1 re2 ⇒ re_chars re1 ++ re_chars re2
| Star re ⇒ re_chars re
end.
그런 다음 우리의 정리를 다음과 같이 다시 작성할 수 있다.
Theorem in_re_match : ∀ T (s : list T) (re : reg_exp T) (x : T),
s =~ re →
In x s →
In x (re_chars re).
Proof.
intros T s re x Hmatch Hin.
induction Hmatch
as [
|x'
|s1 re1 s2 re2 Hmatch1 IH1 Hmatch2 IH2
|s1 re1 re2 Hmatch IH|re1 s2 re2 Hmatch IH
|re|s1 s2 re Hmatch1 IH1 Hmatch2 IH2].
-
apply Hin.
-
apply Hin.
- simpl. rewrite in_app_iff in ×.
destruct Hin as [Hin | Hin].
+
left. apply (IH1 Hin).
+
right. apply (IH2 Hin).
-
simpl. rewrite in_app_iff.
left. apply (IH Hin).
-
simpl. rewrite in_app_iff.
right. apply (IH Hin).
-
destruct Hin.
MStarApp 경우가 흥미롭다. _두_ 귀납적 가정들을 얻는데, 하나는
x가 s1 (re에 매치하는)에 나타날 때 적용하고, 두 번째는 x가
s2 (Star re에 매치하는)에 나타날 때 적용한다. 이 것은 re에
반대로 exp_match의 증거에 대한 귀납법이 왜 필요한지 보여주는
좋은 사례이다. 왜냐하면 re는 re에 매치하는 문자열에 대한
귀납적 가정을 제공할 뿐인데 In x s2 경우에 대해 추론할 수 없기
때문이다.
-
simpl. rewrite in_app_iff in Hin.
destruct Hin as [Hin | Hin].
+
apply (IH1 Hin).
+
apply (IH2 Hin).
Qed.
연습문제: 별 네 개 (re_not_empty)
재귀 함수 re_not_empty를 작성하여 재귀식이 어떤 문자열에 매치하는지 테스트하시오. 이 함수가 올바르다는 사실을 증명하시오.Fixpoint re_not_empty {T : Type} (re : reg_exp T) : bool
. Admitted.
Lemma re_not_empty_correct : ∀ T (re : reg_exp T),
(∃ s, s =~ re) ↔ re_not_empty re = true.
Proof.
Admitted.
☐
remember 전술
Lemma star_app: ∀ T (s1 s2 : list T) (re : reg_exp T),
s1 =~ Star re →
s2 =~ Star re →
s1 ++ s2 =~ Star re.
Proof.
intros T s1 s2 re H1.
H1에 대해 inversion을 적용하면 재귀 경우들에서 그 다지
진전하지 못한다. 그래서 귀납법이 필요하다. 첫 번째로 평범하게
시도해본다.
induction H1
as [|x'|s1 re1 s2' re2 Hmatch1 IH1 Hmatch2 IH2
|s1 re1 re2 Hmatch IH|re1 s2' re2 Hmatch IH
|re''|s1 s2' re'' Hmatch1 IH1 Hmatch2 IH2].
하지만 이제 exp_match 정의에서 나온 일곱가지 경우들로 얻지만
H1의 가장 중요한 정보, s1은 Sar re 형태의 어떤 것에
매치되었다는 사실을 잃어버렸다. 이로 인해 일곱 개중 두
개(MStar0와 MStarApp)를 제외한 나머지 경우들은 모순이지만 이
정의의 _모든_ 일곱가지 생성자들에 대해 증명해야한다. MEmpty...와
같은 두세 가지 생성자들에 대한 증명을 여전히 진행할 수 있다.
-
simpl. intros H. apply H.
... 하지만 대부분의 경우 증명이 막힌다. MChar의 경우를 예로
살펴보면 다음을 보여여 한다.
s2 =~ Char x' -> x' :: s2 =~ Char x',
하지만 이를 증명하는 것은 명백히 불가능하다.
-
Abort.
문제는 Prop 가정에 대한 induction은 완전히 일반적인 가정들에
대해서는 적절히 동작한다. 즉, 모든 인자가 변수인 가정들에 대해서
적용하기 적절하지만 Star re와 같이 더 복잡한 식에 대해서는
그렇지 않다.
(이러한 점에서 증거에 대한 induction은 inversion 보다
destruct와 더 비슷하게 동작한다.)
문제를 일으키는 식들을 명시적인 등식을 가지고 일반화시켜서 이
문제를 해결할 수 있다.
Lemma star_app: ∀ T (s1 s2 : list T) (re re' : reg_exp T),
s1 =~ re' →
re' = Star re →
s2 =~ Star re →
s1 ++ s2 =~ Star re.
증거에 대한 귀납법을 이제 직접 수행할 수 있다. 왜냐하면 첫번째
가정의 인자가 충분히 일반적이므로 re' = Star re 등식을 이
문맥에서 뒤집음으로써 대부분의 경우들을 없앨 수 있기 때문이다.
이 증명 방법은 매우 흔해서 그러한 등식을 자동으로 생성해서 정리에
있는 문장을 수정하지 않아도 되는 편리한 전술을 콕에서 제공한다.
remember e as x 전술을 사용하면 콕 시스템은 (1) 식 e가 나타난
것을 모두 변수 x로 바꾸고, (2) 현재 문맥에 등식 x = e를
추가한다. 위 결과를 보이기 위해 이 전술을 사용하는 법이 여기
있다.
Abort.
Lemma star_app: ∀ T (s1 s2 : list T) (re : reg_exp T),
s1 =~ Star re →
s2 =~ Star re →
s1 ++ s2 =~ Star re.
Proof.
intros T s1 s2 re H1.
remember (Star re) as re'.
Lemma star_app: ∀ T (s1 s2 : list T) (re : reg_exp T),
s1 =~ Star re →
s2 =~ Star re →
s1 ++ s2 =~ Star re.
Proof.
intros T s1 s2 re H1.
remember (Star re) as re'.
이제 Heqre' : re' = Star re 이다.
generalize dependent s2.
induction H1
as [|x'|s1 re1 s2' re2 Hmatch1 IH1 Hmatch2 IH2
|s1 re1 re2 Hmatch IH|re1 s2' re2 Hmatch IH
|re''|s1 s2' re'' Hmatch1 IH1 Hmatch2 IH2].
Heqre'은 대부분의 경우에 모순이므로 즉시 결론을 내릴 수 있게
해준다.
- inversion Heqre'.
- inversion Heqre'.
- inversion Heqre'.
- inversion Heqre'.
- inversion Heqre'.
Star에 대응하는 경우들이 흥미롭다. MStarApp 경우에 대한 귀납적
가정 IH2는 Star re'' = Star re' 전제를 추가로 언급하는데
remember로 생성된 등식으로 부터 만들어진 것이다.
-
inversion Heqre'. intros s H. apply H.
-
inversion Heqre'. rewrite H0 in IH2, Hmatch1.
intros s2 H1. rewrite <- app_assoc.
apply MStarApp.
+ apply Hmatch1.
+ apply IH2.
× reflexivity.
× apply H1.
Qed.
연습문제: 별 네 개 (exp_match_ex2)
Lemma MStar'' : ∀ T (s : list T) (re : reg_exp T),
s =~ Star re →
∃ ss : list (list T),
s = fold app ss []
∧ ∀ s', In s' ss → s' =~ re.
Proof.
Admitted.
☐
우선 "충분히 긴"이라는 표현을 정의할 필요가 있다. 건설적 논리를
다루고 있으므로 각 정규식 re에 대해 "펌프 가능성"을 보장하는
문자열들 s의 최소 길이를 실제로 계산할 수 있어야 한다.
연습문제: 별 다섯개 , 고급 (pumping)
정규식 이론에서 첫번째 정말로 흥미로운 정리들 중 하나는 _펌핑 보조 정리_라 부르는 것이다. 이 보조 정리에 의하면, 어떤 충분히 긴 문자열 s가 정규식 re에 매치된다고 할때 이 문자열은 s의 어떤 중간 부분을 임의의 횟수로 반복해서 "펌프"하여 re에 매치되는 새로운 문자열을 만들 수 있다.Module Pumping.
Fixpoint pumping_constant {T} (re : reg_exp T) : nat :=
match re with
| EmptySet ⇒ 0
| EmptyStr ⇒ 1
| Char _ ⇒ 2
| App re1 re2 ⇒
pumping_constant re1 + pumping_constant re2
| Union re1 re2 ⇒
pumping_constant re1 + pumping_constant re2
| Star _ ⇒ 1
end.
다음으로 어떤 숫자 만큼 반복해서 문자열을 스스로에 붙이는 보조
함수를 정의하는 것이 유용하다.
Fixpoint napp {T} (n : nat) (l : list T) : list T :=
match n with
| 0 ⇒ []
| S n' ⇒ l ++ napp n' l
end.
Lemma napp_plus: ∀ T (n m : nat) (l : list T),
napp (n + m) l = napp n l ++ napp m l.
Proof.
intros T n m l.
induction n as [|n IHn].
- reflexivity.
- simpl. rewrite IHn, app_assoc. reflexivity.
Qed.
이제 펌핑 보조 정리는, 만일 s =~ re이고 s의 길이가 re의 최소
펌핑 상수이라면 s는 세 개의 부분 문자열들로 분리하여 s1 ++ s2
++ s3로 나타낼 수 있다. s2는 어떤 숫자만큼 반복할 수 있고 그
결과를 s1과 s3와 조합하면 re에 여전히 매치된다. s2는 빈
문자열이 아니라는 것도 보장되기 때문에 이 보조 정리는 re에
매치되는 원하는 길이의 문자열을 생성하는 (건설적!) 방법이 된다.
Lemma pumping : ∀ T (re : reg_exp T) s,
s =~ re →
pumping_constant re ≤ length s →
∃ s1 s2 s3,
s = s1 ++ s2 ++ s3 ∧
s2 ≠ [] ∧
∀ m, s1 ++ napp m s2 ++ s3 =~ re.
당신이 작성할 증명을 능률적으로 하기 위해 omega 전술을 사용하면
여러 곳에서 자연수에 대한 등식 또는 부등식 관련한 번거롭고
지나치게 상세한 주장을 자동으로 완성하는데 도움이 된다. omega
전술을 사용하려면 다음과 같은 Require 명령을 사용해야 한다.
나중에 omega 전술을 설명할 것이다. 하지만 당분간 원할 때마다
자유롭게 이 전술을 적용해도 된다. 귀납 증명의 첫 번째 경우는 이
전술을 사용하는 예이다.
Require Import Coq.omega.Omega.
Proof.
intros T re s Hmatch.
induction Hmatch
as [ | x | s1 re1 s2 re2 Hmatch1 IH1 Hmatch2 IH2
| s1 re1 re2 Hmatch IH | re1 s2 re2 Hmatch IH
| re | s1 s2 re Hmatch1 IH1 Hmatch2 IH2 ].
-
simpl. omega.
Admitted.
End Pumping.
☐
사례 연구: 반사를 개선하기
Theorem filter_not_empty_In : ∀ n l,
filter (beq_nat n) l ≠ [] →
In n l.
Proof.
intros n l. induction l as [|m l' IHl'].
-
simpl. intros H. apply H. reflexivity.
-
simpl. destruct (beq_nat n m) eqn:H.
+
intros _. rewrite beq_nat_true_iff in H. rewrite H.
left. reflexivity.
+
intros H'. right. apply IHl'. apply H'.
Qed.
destruct 다음의 첫번째 분기에서 beq_nat_true_iff 보조 정리를
beq_nat n m을 분해해서 만든 등식에 명시적으로 적용하여 beq_nat
n m = true 가정을 n = m 가정으로 변환한다. 그런 다음 이 가정을
사용하여 rewrite하여 이 경우를 증명했었다.
beq_nat n m에 대해 더 나은 경우 별 분석 원리를 가능하게 하는
귀납적 명제를 정의해서 위 과정을 간소화시킬 수 있다. 일반적으로
직접적으로 유용하지 않은 beq_nat n m = true와 같은 등식을
생성하는 대신 이 원리를 통해 우리가 정말로 필요로 하는 가정 n =
m을 곧바로 제공한다.
사실 조금 더 일반적인 것을 정의해서 단지 등식 뿐만 아니라 임의의
성질에 대해 사용할 수 있도록 한다.
Module FirstTry.
Inductive reflect : Prop → bool → Prop :=
| ReflectT : ∀ (P:Prop), P → reflect P true
| ReflectF : ∀ (P:Prop), ¬ P → reflect P false.
위 내용을 설명하기 전에 조금 재배치하자. ReflectT와 ReflectF
둘다 타입이 ∀ (P:Prop)로 시작하기 때문에 P를 전체 귀납적
선언의 매개변수로 두어 조금 더 읽기 쉽고 다루기 쉽게 만들 수 있다.
End FirstTry.
Inductive reflect (P : Prop) : bool → Prop :=
| ReflectT : P → reflect P true
| ReflectF : ¬ P → reflect P false.
reflect 성질은 명제 P와 부 울값 b 두 개의 인자를 받는다. 이
성질의 직관적인 의미는 성질 P가 부울 값 b에 _반사되어_ 있다.
즉, 성질 P는 부울 값 b와 동치라고 해석한다. P가 성립하는
필요 충분 조건이 b = true이다. 이 점을 확인하기 위해 정의를 보면
reflect P true가 성립하는 증거를 만드는 유일한 방법은 P가
참이라는 것을 보이고 ReflectT 생성자를 사용하는 점을 주목해야
한다. 이 문장을 뒤집어 보면, reflect P true 증명으로부터 P에
대한 증거를 추출하는 것이 가능해야 한다. 역으로 reflect P
false를 보이는 유일한 방법은 ¬ P에 대한 증거와 RefelctF
생성자를 조합하는 것이다.
이러한 직관적인 사실을 형식화하고 두 문장이 정말로 동치라는 것을
보이기 쉽다.
Theorem iff_reflect : ∀ P b, (P ↔ b = true) → reflect P b.
Proof.
intros P b H. destruct b.
- apply ReflectT. rewrite H. reflexivity.
- apply ReflectF. rewrite H. intros H'. inversion H'.
Qed.
Theorem reflect_iff : ∀ P b, reflect P b → (P ↔ b = true).
Proof.
Admitted.
Proof.
Admitted.
☐
"if and only if" 연결자를 사용하는 것과 비교해서 reflect의
장점은 reflect P b 형태의 가정이나 보조 정리를 분해하면서 b에
대한 경우 별 부석을 수행하고 동시에 양 갈래에 적절한 가정을 생성할
수 있다 (첫 번째 부분 목적에는 P를 두 번째 부분 목적에는 ¬ P).
Lemma beq_natP : ∀ n m, reflect (n = m) (beq_nat n m).
Proof.
intros n m.
apply iff_reflect. rewrite beq_nat_true_iff. reflexivity.
Qed.
이번에는 filter_not_empty_In을 다음과 같이 새로 증명한다.
destruct와 apply를 적용하는 것을 destruct 하나로 조합한 것을
살펴보시오.
(분명하게 이 점을 보기 위해서 콕으로 filter_not_empty_In을
증명한 두 가지 방법을 자세히 보고 destruct의 첫 번째 경우의 도입
부분의 증명 상태가 어떻게 다른지 관찰하시오.)
Theorem filter_not_empty_In' : ∀ n l,
filter (beq_nat n) l ≠ [] →
In n l.
Proof.
intros n l. induction l as [|m l' IHl'].
-
simpl. intros H. apply H. reflexivity.
-
simpl. destruct (beq_natP n m) as [H | H].
+
intros _. rewrite H. left. reflexivity.
+
intros H'. right. apply IHl'. apply H'.
Qed.
Fixpoint count n l :=
match l with
| [] ⇒ 0
| m :: l' ⇒ (if beq_nat n m then 1 else 0) + count n l'
end.
Theorem beq_natP_practice : ∀ n l,
count n l = 0 → ~(In n l).
Proof.
Admitted.
☐
이 기법은 여기서 본 증명의 경우 약간 편할 뿐이지만 일관되게
reflect를 사용하면 종종 커다란 증명일 수록 눈에 띄게 더 짧고
명확하게 증명을 작성하도록 만든다. 나중에 더 많은 예를 만나게 될
것이다.
reflect 성질을 사용하는 것은 콕 라이브러리 SSReflect로
유명해졌다. 이 라이브러리는 4색 정리와 Feit-Thompson 정리와 같은
수학에서 중요한 결과를 형식화하는데 사용되었다. 이름 SSReflect는
small-scale reflection를 나타내는데, 부울 계산으로 작은 증명
단계들을 간단하게 만드는 반사를 널리 사용하는 것을 의도한다.
추가 연습문제
연습문제: 별 세 개, 추천 (nostutter)
성질을 귀납적 정의로 공식화하는 것은 이 강의에서 필요한 중요한 능력이다. 어떠한 도움없이 이 연습문제를 해결해보라.Inductive nostutter {X:Type} : list X → Prop :=
.
아래의 테스트들을 모두 통과하도록 확인하되 주석 처리된 제안한
증명이 동작하지 않으면 자유롭게 변경하시오. 당신이 만든 정의가
저자가 만든 것과 다르지만 정확할 수 있다. 그러한 경우 이 예제들을
증명할 때 다른 증명이 필요할 수도 있다. (제안한 증명에 아직
설명하지 않은 많은 전술들을 상용하고 있음을 주목해야 할 것이다.
이 전술들을 사용하여 nostutter를 정의한 다른 가능한 방법들에
대해서도 적용될 수 있도록 더 강건한 증명을 만들 수 있다. 당연히
주석을 해제하고 제안한 증명을 그대로 이용할 수 있지만 더 기본적인
전술을 사용해서 증명할 수도 있다.)
Example test_nostutter_1: nostutter [3;1;4;1;5;6].
Admitted.
Example test_nostutter_2: nostutter (@nil nat).
Admitted.
Example test_nostutter_3: nostutter [5].
Admitted.
Example test_nostutter_4: not (nostutter [3;1;1;4]).
Admitted.
☐
리스트 l은 만일 리스트가 l1과 l2와 모두 동일한 원소들을
포함하고, l1과 l2의 순서를 유지하되, 서로 중간에 끼어들 수
있다면 이 리스트는 l1과 l2의 "순차 병합"이다. 예를 들어,
1;4;6;2;3
는 아래 두 리스트의 순차 병합이다.
1;6;2
과
4;3.
이제 집합 X, 함수 test: X→bool, list X 타입의 리스트 l을
가정하자. 그리고 l은 두 리스트 l1과 l2의 순차 병합이고 l1의
각 항목은 test를 만족하고 l2의 어느 항목도 이 테스트를 만족하지
않는다. 그렇다면, filter test l = l1이다.
이 명세를 콕의 정리로 작성하고 증명하시오. (리스트가 다른 두 리스트들의
병합이라는 것부터 정의해야 할 것이다. 귀납적 관계로 정의하되 Fixpoint를
사용하지 마시오.)
연습문제: 별 네 개, 고급 (filter_challenge)
Poly 장의 filter 정의가 추상적 명세에 일치하는 것을 증명하자. 여기 영어로 비형식적으로 작성한 명세가 있다:
☐
연습문제: 별 다섯개 , 고급, 선택사항 (filter_challenge_2)
filter의 동작 특성을 이렇게 다르게 정의할 수 있다. test가 true로 판정을 내리는 성질을 갖는 l의 모든 부분 순열들 중에 filter test l은 가장 긴 순열이다. 이 주장을 콕으로 작성해서 증명하시오.
☐
연습문제: 별 네 개, 선택사항 (palindromes)
팰린드롬은 뒤로 읽어도 앞으로 읽는 것과 동일한 순열이다.- list X에 대한 귀납적 명제 pal을 정의해서 팰린드롬이 의도하는
바를 정확히 기술하시오. (힌트: 세가지 경우가 필요하다. 리스트 구조에
관하여 정의해야 한다. 왜냐하면 다음과 같이 생성자 하나만 두면,
- 다음 명제 (pal_app_rev)을 증명하시오.
- 다음 명제 (pal_rev)을 증명하시오.
☐
forall l, l = rev l -> pal l.
연습문제: 별 다섯개 , 선택사항 (palindrome_converse)
다시 한번, 역 방향을 증명하기는 증거가 부족하기 때문에 훨씬 더 어렵다. 이전 연습문제의 pal 정의를 사용하여 다음을 증명하시오.
☐
연습문제: 별 네 개, 고급, 선택사항 (NoDup)
Logic 장에서 In 성질의 정의를 다시 기억해보자. 이 성질은 list l에서 적어도 한 번 x 값이 나타난다고 주장한다.
첫번째 작업은 In을 사용해서 disjoint X l1 l2 명제를 정의하는
것이다. 이 명제는 l1과 l2가 공통 원소가 없는 (타입 X의 원소를
갖는) 리스트일 때 바로 증명가능해야 한다.
다음 작업은 In을 사용하여 귀납적 명제 NoDup X l을 정의한다.
l의 모든 원소가 다른 원소와 다른 (타입 X의 원소를 갖는)
리스트이면 바로 증명 가능해야 한다. 예를 들어, NoDup nat
[1;2;3;4]와 NoDup bool []은 증명 가능하고 NoDup nat [1;2;1]과
NoDup bool [true;true]는 증명할 수 없어야 한다.
마지막으로 disjoint, NoDup, ++ (리스트 붙이기)를 연관짓는 한
가지 또는 그 이상 흥미로운 정리들을 기술하고 증명하시오.
☐
먼저 쉽고 유용한 보조 정리를 증명하시오.
연습문제: 별 네 개, 고급, 선택사항 (pigeonhole principle)
_비둘기 집 원리_는 세는 것에 대한 기본적인 사실을 기술한다. 만일 n개 이상의 항목들을 n개의 비둘기 집에 분배하면 어떤 비둘기 집은 반드시 적어도 둘 이상의 항목을 포함해야 한다. 종종 그러하듯이, 숫자에 관한 이 명백하고 당연한 사실을 증명하려면 사소하지 않은 절차가 필요하다. 이제 충분히 갖췄다...Lemma in_split : ∀ (X:Type) (x:X) (l:list X),
In x l →
∃ l1 l2, l = l1 ++ x :: l2.
Proof.
Admitted.
이제 repeats 성질을 정의하되, repeats X l은 l이 적어도 하나
반복되는 (X 타입의) 원소를 포함하는 주장이다.
Inductive repeats {X:Type} : list X → Prop :=
.
자, 비둘기 집 원리를 형식화하는 방법이 여기 있다. 리스트 l2는
비둘기 집 표시들의 리스트이고, 리스트 l1은 항목들 리스트를
할당한 라벨들을 표현한다. 만일 라벨보다 많은 항목이 있다면 적어도
두 항목은 동일한 라벨을 가져야 한다. 즉 list l1은 반복되는
원소를 포함한다.
excluded_middle 가정을 사용해서 In이 결정가능함을 보이면 (즉,
∀ x l, (In x l) ∨ ¬ (In x l)) 더 쉽게 증명할 수 있다.
하지만 In이 결저가능하다고 _가정하지 않고도_ 증명을 진행할 수도
있다. 이렇게 증명을 해낸다면 excluded_middle 가정을 필요로 하지
않을 것이다.
Theorem pigeonhole_principle: ∀ (X:Type) (l1 l2:list X),
excluded_middle →
(∀ x, In x l1 → In x l2) →
length l2 < length l1 →
repeats l1.
Proof.
intros X l1. induction l1 as [|x l1' IHl1'].
Admitted.
☐