Lógica em Coq
Published:
Nesta seção apresentaremos como demonstrar resultados conhecidos da lógica proposicional e de predicados utilizando Coq.
Implicação
Vamos iniciar demonstrando alguns fatos simples envolvendo a implicação. Para isso, vamos definir algumas variáveis proposicionais:
Variables A B C : Prop.
Em Coq, o comando Variables
permite a definição de variáveis de um determinado tipo. No código acima, declaramos A B C
como sendo do tipo Prop
, isso é, proposições. Dessa forma, temos que valores de tipo A B C
são, na verdade, provas de que estas proposições são verdadeiras.
A seguir, definimos um primeiro teorema:
Theorem first_theorem : (A -> B) -> A -> B.
Proof.
intro Hab.
intro Ha.
apply Hab.
assumption.
Qed.
Vamos analisar a estrutura deste primeiro exemplo. Inicialmente, definimos qual resultado desejamos provar usando a palavra chave Theorem
que deve ser seguida de um identificador, first_theorem
neste exemplo, para nomear este resultado. Após a definição do nome do teorema a ser demonstrado, definimos a fórmula lógica a ser provada, que em nosso exemplo consiste da tautologia (A -> B) -> A -> B
.
Ao processarmos a declaração deste teorema, o seu IDE irá iniciar o modo de construção interativa de provas, que exibe a seguinte configuração do estado inicial da prova.
1 subgoal, subgoal 1 (ID 1)
A, B, C : Prop
============================
(A -> B) -> A -> B
A interpretação do estado de prova é bastante simples: tudo acima da linha formada por ===
consiste das hipóteses que podem ser utilizadas para provar a conclusão, localizada abaixo destas linhas. Note que essa notação é similar à utilizada para representar provas utilizando dedução natural.
Provas em Coq são construídas utilizando comandos chamados de táticas, que alteram o estado atual da prova. Em nosso primeiro exemplo, a primeira tática que utilizamos é intro
, que permite adicionar o lado esquerdo de uma implicação ao conjunto de hipóteses, similar à conhecida regra de introdução da implicação. Ao processarmos a tática intro Hab
o estado de prova é alterado para:
1 subgoal, subgoal 1 (ID 2)
A, B, C : Prop
Hab : A -> B
============================
A -> B
Note que a tática intro
possui como parâmetro um nome a ser utilizado pela hipótese a ser adicionada. É importante ressaltar que este deve ser um novo nome, isto é, nenhuma outra hipótese deve possuir o mesmo nome utilizado como parâmetro para a tática intro
.
Em seguida introduzimos a hipótese Ha : A
utilizando a tática intro Ha
, resultando em:
1 subgoal, subgoal 1 (ID 3)
A, B, C : Prop
Hab : A -> B
Ha : A
============================
B
Para concluirmos a prova, precisamos deduzir B
a partir das hipóteses Hab : A -> B
e Ha : A
. Em dedução natural, esse passo é uma aplicação imediata da regra de eliminação da implicação (ou modus ponens). Em Coq, usamos a tática apply Hab
, que permite-nos deduzir B
se obtermos uma prova de A
. O estado de prova passa a ser:
1 subgoal, subgoal 1 (ID 3)
A, B, C : Prop
Hab : A -> B
Ha : A
============================
A
Note que o estado atual da prova possui como conclusão a proposição A
que esta presente nas hipóteses, Ha : A
. A tática assumption
permite concluir uma demonstração caso exista uma hipótese com o mesmo tipo da conclusão. A seguinte mensagem é apresentada por Coq para indicar que a demonstração foi concluída.
No more subgoals.
(dependent evars: (printing disabled) )
O comando Qed
é utilizado para terminar a definição de um teorema. Outro comando que termina uma prova é o Admitted
, que simplesmente assume uma demonstração como sendo verdadeira. Todos os exercícios propostos estão marcados com o comando Admitted
e sua tarefa é substituí-lo por um script de táticas para a construção da prova em questão.
Isso termina o nosso primeiro exemplo.
Exercício 1
Lemma ex1 : A -> B -> A.
Proof.
Admitted.
Exercício 2
Lemma ex2 : (A -> B) -> (B -> C) -> A -> C.
Proof.
Admitted.
Conjunção
Nesta seção abordaremos como construir provas utilizando conjunção. Para isso, utilizaremos duas novas táticas: destruct
, que permite decompor uma conjunção em suas partes e split
, que divide uma conclusão A /\ B
em duas conclusões. O exemplo a seguir mostra como usar essas táticas para provar que o conectivo de conjunção é comutativo.
Lemma and_comm : A /\ B -> B /\ A.
Proof.
intro Hab.
destruct Hab as [Ha Hb].
split.
+
assumption.
+
assumption.
Qed.
Após o processamento da tática `intro Hab, o estado da prova será:
1 subgoal, subgoal 1 (ID 5)
A, B, C : Prop
Hab : A /\ B
============================
B /\ A
Para concluir essa demonstração, devemos decompor a hipótese Hab : A /\ B
em duas hipóteses, Ha : A
e Hb : B
. A tática destruct Hab as [Ha Hb]
realiza essa decomposição dando o nome Ha
para o lado esquerdo e Hb
para o lado direito da conjunção A /\ B
. Com isso, o estado de prova passa a ser:
1 subgoal, subgoal 1 (ID 10)
A, B, C : Prop
Ha : A
Hb : B
============================
B /\ A
Na sequência, utilizamos a tática split
que divide uma conclusão formada por uma conjunção em duas provas, uma para concluir o lado esquerdo e outra para concluir o lado direito. Ao usarmos a tática split
, obteremos a seguinte configuração.
2 subgoals, subgoal 1 (ID 12)
A, B, C : Prop
Ha : A
Hb : B
============================
B
subgoal 2 (ID 13) is:
A
Note que foram produzidos duas provas distintas, uma cuja conclusão é B
e outra cuja conclusão é A
. Ambas demonstrações podem ser finalizadas utilizando a tática assumption
.
Note que o script de táticas utilizado utiliza bullets, uma adição recente a Coq que permite “marcar” o início de diferentes conclusões a serem demonstradas. Bullets podem ser representados pelos caracteres *, +, - .
A seguir, apresentamos mais um exemplo que utiliza táticas para conjunção e implicação lógica.
Lemma and_assoc : A /\ (B /\ C) -> (A /\ B) /\ C.
Proof.
intro Habc.
destruct Habc as [Ha [Hb Hc]].
split.
+
split.
*
assumption.
*
assumption.
+
assumption.
Qed.
O teorema and_assoc
mostra que a conjunção é uma operação associativa. Ele é provado utilizando táticas já apresentadas anteriormente. A peculiaridade deste exemplo é o uso de padrões aninhados para decompor a hipótese Habc: A /\ B /\ C
em seus três componentes, a saber: Ha : A
, Hb : B
e Hc : C
.
Exercício 3
Lemma ex3 : (A /\ B) /\ C -> A /\ (B /\ C).
Proof.
Admitted.
Exercício 4
Lemma ex4 : ((A /\ B) -> C) -> (A -> B -> C).
Proof.
Admitted.
Exercício 5
Lemma ex5 : (A -> B -> C) -> ((A /\ B) -> C).
Proof.
Admitted.
Exercício 6
Lemma ex6 : ((A -> B) /\ (A -> C)) -> A -> (B /\ C).
Proof.
Admitted.
Bi-condicional
Assim como na lógica proposicional, Coq define o conectivo bicondicional em termos da conjunção e da implicação lógica. A fórmula A <-> B
é equivalente a (A -> B) /\ (B -> A)
, conforme expresso pela seguinte definição Coq:
Definition iff (A B : Prop) : Prop := (A -> B) /\ (B -> A).
A seguir apresentamos um pequeno exemplo que usa o conectivo bi-condicional e a tática unfold
utilizada para substituir o bicondicional por sua definição.
Lemma and_comm_iff : (A /\ B) <-> (B /\ A).
Proof.
unfold iff.
split.
+
apply and_comm.
+
intro Hba.
destruct Hba as [Hb Ha].
split.
*
assumption.
*
assumption.
Qed.
O exemplo anterior usa a tática unfold iff
que modifica a conclusão de A /\ B <-> B /\ A
para (A /\ B -> B /\ A) /\ (B /\ A -> A /\ B)
Além do uso da tática unfold
, o exemplo usa um resultado previamente demonstrado, o teorema and_comm
. Para isso, usamos a tática apply
que permite usar um teorema cujo enunciado coincida com a conclusão da prova atual.
Negação
A negação, em Coq, é definida em termos da implicação lógica, isto é, a fórmula ~ A
é equivalente a A -> False
, em que False
é a proposição que não pode ser provada. Logo, as mesmas táticas utilizadas para implicação podem ser utilizadas sobre a negação.
A seguir apresentamos um exemplo que ilustra o uso da negação.
Lemma modus_tollens : ((A -> B) /\ ~ B) -> ~ A.
Proof.
intro H.
destruct H as [Hb Hnb].
unfold not.
unfold not in Hnb.
intro Ha.
apply Hnb.
apply Hb.
assumption.
Qed.
Neste exemplo, temos alguns pontos importantes: primeiramente ele utiliza a tática unfold
que subtitui um nome por sua definição. Em Coq, a negação é definida da seguinte maneira:
Definition not (A : Prop) : Prop := A -> False.
Após a execução da tática [destruct H as [Hb Hnb]], o estado da prova é
1 subgoal, subgoal 1 (ID 16)
A, B, C : Prop
Hb : A -> B
Hnb : ~ B
============================
~ A
Que possui tanto conclusão e hipótese que utiliza o conectivo de negação. Ao utilizarmos a tática unfold not
a fórmula ~ A
é substituída por sua definição, A -> False
.
Lemma contra : A -> ~ A -> B.
Proof.
intro Ha.
intro Hna.
contradiction.
Qed.
No exemplo contra
temos uma técnica de prova comum envolvendo o conectivo de negação: prova por contradição. Em lógica, sempre que temos um conjunto de hipóteses contraditório podemos concluir a demonstração apelando ao princípio ex falsum quod libet que permite concluir qualquer proposição a partir da dedução de False
.
Em Coq, podemos construir provas por contradição de diversas maneiras. A primeira que veremos neste curso é a tática contradiction
que a partir de fatos contraditórios (por exemplo A
e ~ A
) conclui a prova corrente.
Disjunção
Nesta seção apresentaremos táticas para construção de provas envolvendo disjunção. Para a utilização de hipóteses envolvendo a disjunção, utilizamos a tática destruct
, que funciona de maneira similar à regra de eliminação da disjunção da dedução natural. Para provar uma conclusão que utiliza a disjunção, devemos provar que seu lado esquerdo (ou direito) é verdadeiro. Para isso, utilizamos as táticas left
and right
, respectivamente. O próximo exemplo ilustra o uso destas táticas.
Lemma or_comm : (A \/ B) -> (B \/ A).
Proof.
intro Hab.
destruct Hab as [Ha | Hb].
+
right.
assumption.
+
left.
assumption.
Qed.
Note que a tática destruct
possui uma sintaxe diferente para dividir hipóteses envolvendo uma disjunção: cada componente da disjunção deve ser separado usando o caractere “|”.
Lemma or_assoc : A \/ (B \/ C) -> (A \/ B) \/ C.
Proof.
intro Habc.
destruct Habc as [Ha | [Hb | Hc]].
+
left.
left.
assumption.
+
left.
right.
assumption.
+
right.
assumption.
Qed.
Exercício 7
Lemma ex7 : ((A \/ B) /\ ~ A) -> B.
Proof.
Admitted.
Exercício 8
Lemma ex8 : (A \/ (B /\ C)) -> (A \/ B) /\ (A \/ C).
Proof.
Admitted.
Quantificadores universal e existencial
Para formalizarmos alguns fatos sobre a lógica de predicados devemos, primeiramente, definir um universo de discurso e predicados sobre seus elementos. A seguir definimos um tipo U
para representar um conjunto não vazio (especificamos esse fato supondo a existência de um valor u : U
e dois predicados definidos sobre este universo, P
e Q
.
Hypothesis U : Set.
Hypothesis u : U.
Hypothesis P : U -> Prop.
Hypothesis Q : U -> Prop.
Hypothesis R : U -> Prop.
Note que definimos o tipo U
pertencendo ao universo Set
. Visando eliminar paradoxos, como os presentes na teoria de conjuntos (por o exemplo, o paradoxo de Russell), a teoria de tipos classifica tipos em termos de universos. Coq possui dois universos básicos: Set
, o universo que classifica tipos com os quais podemos realizar computações e Prop
, o universo de proposições lógicas. Ambos Set
e Prop
pertencem ao universo Type 0
, que por sua vez pertence ao universo Type 1
formando uma cadeia infinita de universos de tipos que evitam inconsistências lógicas.
O quantificador universal é representado em Coq pela palavra reservada forall
e o quantificador existencial por exists
. Para o quantificador forall
utilizaremos as táticas intro
para introdução deste quantificador e apply
ou destruct
para eliminá-lo. O seguinte exemplo ilustra o uso destas táticas.
Lemma forall_and : (forall x : U, P x /\ Q x) -> ((forall x : U, P x) /\ (forall x : U, Q x)).
Proof.
intro H.
split.
+
intro y.
destruct (H y).
assumption.
+
intro y.
destruct (H y).
assumption.
Qed.
Para explicarmos o uso das táticas intro
e destruct
neste exemplo, vamos considerar o estado da prova após a execução da tática split
.
2 subgoals, subgoal 1 (ID 4)
U : Set
u : U
P, Q : U -> Prop
H : forall x : U, P x /\ Q x
============================
forall x : U, P x
subgoal 2 (ID 5) is:
forall x : U, Q x
Ao executarmos intro x
, obtemos a seguinte configuração para o primeiro componente desta prova.
1 focused subgoal
(unfocused: 1), subgoal 1 (ID 6)
U : Set
u : U
P, Q : U -> Prop
H : forall x : U, P x /\ Q x
y : U
============================
P y
A execução da tática intro y
incluiu no conjunto de hipóteses um elemento y : U
arbitrário, exatamente como a regra de introdução do quantificador universal presente na dedução natural. Para eliminarmos o quantificador universal de uma fórmula devemos obter um elemento do universo de discurso sobre o qual a fórmula que contém este quantificador está definida e substituir a variável quantificado por este elemento. Este passo de substituição da variável quantificada por um elemento do universo de discurso pode ser realizado tanto pela tática destruct
quanto pela tática apply
. No exemplo anterior, a tática apply
não é adequada devido ao fato da fórmula em questão não envolver implicações. Como ela utiliza o conectivo de conjunção o uso de destruct
é quase obrigatório. O uso de destruct
neste exemplo, destruct (H y)
especifica que desejamos usar esta tática sobre o resultado de substituir todas as ocorrências livres da variável x
em forall x : U, P x /\ Q x
por y
resultando em P y /\ Q y
, note que isso é exatamente o mecanismo de substituição que ocorre durante a aplicação de funções em linguagens de programação funcional! Além deste uso de destruct
e intro
, o restante desta demonstração é imediato.
O próximo exemplo ilustra o uso da tática apply
para eliminar o quantificador universal de uma fórmula.
Lemma forall_modus_ponens : ((forall x : U, P x -> Q x) /\
(forall y : U, Q y -> R y)) ->
(forall z : U, P z -> R z).
Proof.
intro Hpqr.
destruct Hpqr as [Hpq Hqr].
intro z.
intro Hpz.
apply Hqr.
apply Hpq.
assumption.
Qed.
Para compreendermos este uso de apply
, considere a configuração da prova imediatamente antes do uso de apply Hqr
.
1 subgoal, subgoal 1 (ID 10)
U : Set
u : U
P, Q, R : U -> Prop
Hpq : forall x : U, P x -> Q x
Hqr : forall y : U, Q y -> R y
z : U
Hpz : P z
============================
R z
Ao utilizarmos a tática apply Hqr
o estado da prova é alterado para:
U : Set
u : U
P, Q, R : U -> Prop
Hpq : forall x : U, P x -> Q x
Hqr : forall y : U, Q y -> R y
z : U
Hpz : P z
============================
Q z
Note que ao aplicarmos a hipótese Hqr : forall y : U, Q y -> R y
sobre a conclusão R z
, Coq automaticamente instancia a variável y
para z : U
e deixa como próximo objetivo a ser demonstrado Q z
, o lado esquerdo da implicação presente na hipótese Hqr
. Note que o uso de apply
realiza “dois” passos dedutivos: a eliminação do quantificador universal e a eliminação da implicação.
O quantificador existencial é representado por exists
e, usamos a tática destruct
para eliminar hipóteses envolvendo este quantificador e a tática exists
para provar uma conclusão que utiliza este quantificador. O próximo exemplo ilustra o uso destas táticas.
Lemma ex_or : (exists x : U, P x \/ Q x) -> (exists x : U, P x) \/ (exists y : U, Q y).
Proof.
intro Hpq.
destruct Hpq as [x [Hpx | Hqx]].
+
left.
exists x.
assumption.
+
right.
exists x.
assumption.
Qed.
A tática destruct Hpq as [x [Hpx | Hqx]]
ilustra como podemos combinar padrões para decompor uma fórmula que usa o quantificador existêncial no valor que a torna verdadeira, x
, e nas sub-fórmulas que o compõe. Por sua vez, a tática exists
permite especificar a evidência que torna uma fórmula envoveldo este quantificador como verdadeira.
Exercício 9
Lemma ex9 : forall x : U, P x -> exists y : U, P y.
Proof.
Admitted.
Exercício 10
Lemma ex10 : (forall x : U, P x -> ~ Q x) -> ~ exists y : U, P y /\ Q y.
Proof.
Admitted.
Exercício 11
Lemma ex11 : (forall x : U, P x -> Q x) -> (forall x : U, ~ Q x) -> (forall x : U, ~ P x).
Proof.
Admitted.
Exercício 12
Considere o seguinte argumento muito usado em livros texto sobre lógica formal:
"Todo homem é mortal. Sócrates é um homem. Logo, Sócrates é mortal".
Modele este argumento e prove sua veracidade (ou refute-o) usando um teorema Coq.