Raciocínio sobre listas
Assim como os números, fatos simples sobre funções de processamento de lista podem às vezes ser provado inteiramente por simplificação. Por exemplo, a simplificação realizada por
Equal.refl
é suficiente para este teorema...
`List.nil`_app (xs: List Nat) : Equal (Concat (List.nil Nat) xs) xs
`List.nil`_app xs = Equal.refl
... isso porque o Kind "vê" o List.nil
e já reduz automaticamente a igualdade da mesma forma que ocorre com os números naturais, com o Nat.zero
Além disso, como acontece com os números, às vezes é útil realizar uma análise de caso no possíveis formas (vazias ou não vazias) de uma lista desconhecida
Tl_length_pred (xs: List Nat) : Equal Nat (Pred (Length xs)) (Length (Tail xs))
Tl_length_pred List.nil = Equal.refl
Tl_length_pred (List.cons head tail) = Equal.refl
Caso o usuário não abra os casos e use direto o Equal.refl
, o Kind retorna um erro de tipo:
- ERROR Type mismatch
• Got : Equal Nat (Nat.pred (Length xs)) (Nat.pred (Length xs))
• Expected : Equal Nat (Nat.pred (Length xs)) (Length (Tail xs))
• Context:
• xs : (List Nat)
Tl_length_pred xs = Equal.refl
┬─────────
└Here!
Da mesma forma, alguns teoremas precisam de indução para suas provas.
- Micro-Sermão. Simplesmente ler scripts de prova de exemplo não o levará muito longe! É importante trabalhar os detalhes de cada um, usando Kind e pensando no que cada passo alcança. Caso contrário, é mais ou menos garantido que os exercícios não farão sentido quando você chegar a eles. ( ಠ ʖ̯ ಠ)
Indução em Listas
Provas por indução sobre tipos de dados como List
são um pouco menos familiares do que a indução de números naturais padrão, mas a ideia é igualmente simples. Cada declaração de dados define um conjunto de valores de dados que podem ser construídos usando os construtores declarados: um booleano pode ser True ou False; um número pode ser Zero ou Succ aplicado a outro número; uma lista de naturais pode ser List.nil
ou List.cons
aplicado a um número e uma lista.
Além disso, as aplicações dos construtores declarados entre si são as únicas
possíveis formas que os elementos de um conjunto definido indutivamente podem ter, e este fato
diretamente dá origem a uma maneira de raciocinar sobre conjuntos indutivamente definidos: um número
é Zero ou então é Succ aplicado a um número menor; uma lista é List.nil
ou então
é um List.cons
aplicado a algum número e a alguma lista menor; etc. Então, se tivermos e mente
alguma proposição p
que menciona uma lista l
e queremos argumentar que p
vale
para todas as listas, podemos raciocinar da seguinte forma:
- Primeiro, mostre que
p
é verdadeiro paral
quandol
éList.nil
. - Então mostre que
p
é verdadeiro paral
quandol
éList.cons n l
para algum númeron
e alguma lista menorl
, assumindo quep
é verdadeiro paral
.
Como listas maiores só podem ser construídas a partir de listas menores, eventualmente chegando a List.nil
,
esses dois argumentos juntos estabelecem a verdade de p
para todas as listas l
. Aqui está um
exemplo concreto:
Concat_assoc (xs: List Nat) (ys: List Nat) (zs: List Nat) : Equal (Concat (Concat xs ys) zs) (Concat xs (Concat ys zs))
Concat_assoc List.nil ys zs = Equal.refl
Concat_assoc (List.cons Nat xs.head xs.tail) ys zs =
let ind = Concat_assoc xs.tail ys zs
let app = Equal.apply (x => (List.cons xs.head x)) ind
app
Nós recebemos três listas xs
, ys
e zs
e verificamos se a concatenação da de xs
e ys
com zs
é igual a de xs
com a de ys
com zs
Para isso nós verificamos para o caso de xs
ser uma lista vazia, então recebemos uma reflexão da concatenação é entre ys
e zs
e basta dar um *Equal*.**refl**
Em seguida nós "abrimos" o xs
para obter o xs.tail
para a nossa indução, e recebemos como objetivo:
• Expected: Equal (List Nat) (List.cons Nat xs.head (Concat (Concat xs.tail ys) zs)) (List.cons Nat xs.head (Concat xs.tail (Concat ys zs)))
e a nossa variável ind
é:
• ind: Equal (List Nat) (Concat (Concat xs.tail ys) zs) (Concat xs.tail (Concat ys zs))
bastando apenas aplicar um List.cons xs.head
em ambos os lados da igualdade para ter o objetivo final e é isso o que fazemos no app
:
• app : Equal (List Nat) (List.cons Nat xs.head (Concat (Concat xs.tail ys) zs)) (List.cons Nat xs.head (Concat xs.tail (Concat ys zs)))
OBSERVAÇÃO
O Type Check nos retorna tipos t2
, t3
e outros gerados no mesmo estilo e podemos ignorar e até mesmo apagar na hora de comparar o retorno das variáveis como vemos no seguinte caso:
• Expected: Equal (List Nat) (List.cons Nat xs.head (Concat (Concat xs.tail ys) zs)) (List.cons Nat xs.head (Concat xs.tail (Concat ys zs)))
• app : Equal (List Nat) (List.cons Nat xs.head (Concat (Concat xs.tail ys) zs)) (List.cons Nat xs.head (Concat xs.tail (Concat ys zs)))
Dessa forma fica mais fácil perceber que o app
e o Expected
são idênticos, então não é necessário se assustar ao ver esses tipos gerados
Invertendo uma lista
Para um exemplo um pouco mais complicado de prova indutiva sobre listas, suponha que usamos Concat
para definir uma função de reversão de lista Rev
:
Rev (xs: List Nat) : List Nat
Rev List.nil = List.nil Nat
Rev (List.cons head tail) = Concat (Rev tail) [head]
Test_rev1 : Equal (List Nat) (Rev [1n,2n,3n]) [3n,2n,1n]
Test_rev1 = Equal.refl
Test_rev2 : Equal (Rev List.nil) List.nil
Test_rev2 = Equal.refl
Propriedades da Rev
Agora vamos provar alguns teoremas sobre o rev que acabamos de definir. Para algo um pouco mais desafiador do que vimos, vamos provar que inverter uma lista não altera seu comprimento. Nossa primeira tentativa fica presa o caso sucessor...
Rev_length_firsttry (xs: List Nat) : Equal Nat (Length (Rev xs)) (Length xs)
Rev_length_firsttry List.nil = Equal.refl
Rev_length_firsttry (List.cons xs.head xs.tail) =
let ind = Rev_length_firsttry xs.tail
?
O Type Check nos retorna o seguinte objetivo e contexto:
+ INFO Inspection.
• Expected: Equal Nat (Length (Concat (Rev tail) (List.cons Nat head (List.nil Nat)))) (Nat.succ (Length tail))
• Context:
• head : Nat
• tail : (List Nat)
• ind : Equal Nat (Length (Rev tail)) (Length tail)
• ind = (Rev_length_firsttry tail)
let ind = Rev_length_firsttry tail
?
┬
└Here!
Agora nós temos que provar que o tamanho da concatenação do reverso do tail da lista e a head dela é igual ao sucessor do tamanho da tail, então precisaremos usar algumas outras provas, uma dela é que o tamanho da concatenação de duas listas é o mesmo da soma do tamanho das de cada uma delas:
Concat_length (xs: List Nat) (ys: List Nat) : Equal Nat (Length (Concat xs ys)) (Plus (Length xs) (Length ys))
Concat_length List.nil ys = Equal.refl
Concat_length (List.cons xs.head xs.tail) ys =
let ind = Concat_length xs.tail ys
let app = Equal.apply (x => (Nat.succ x)) ind
app
Além dessa prova, usaremos outras já provadas nos capítulos anteriores:
Plus_n_z (n: Nat) : Equal Nat n (Plus n Nat.zero)
Plus_n_sn (n: Nat) (m: Nat) : Equal Nat (Nat.succ (Plus n m)) (Plus n (Nat.succ m))
Plus_comm (n: Nat) (m: Nat) : Equal Nat (Plus n m) (Plus m n)
E agora é possível provar o nosso teorema:
Rev_length (xs: List Nat) : Equal Nat (Length (Rev xs)) (Length xs)
Rev_length List.nil = Equal.refl
Rev_length (List.cons Nat head tail) =
let ind = Rev_length tail
?
+ INFO Inspection.
• Expected: Equal Nat (Length (Concat (Rev tail) (List.cons Nat head (List.nil Nat)))) (Nat.succ (Length tail))
• Context:
• head : Nat
• tail : (List Nat)
• ind : Equal Nat (Length (Rev tail)) (Length tail)
• ind = (Rev_length tail)
let ind = Rev_length tail
?
┬
└Here!
Nós criamos uma variável com nossa auxiliar Concat_length
:
Rev_length (xs: List Nat) : Equal Nat (Length (Rev xs)) (Length xs)
Rev_length List.nil = Equal.refl
Rev_length (List.cons Nat head tail) =
let ind = Rev_length tail
let aux1 = Concat_length (Rev xs.tail) [xs.head]
?
Recebemos um novo contexto para nos auxiliar, o
• aux1: Equal Nat (Length (Concat (Rev tail) (List.cons Nat head (List.nil Nat)))) (Plus (Length (Rev tail)) 1n)
A aux1
é igual ao lado esquerdo do nosso Expected
, então metade do trabalho já foi resolvido, basta o outro lado da igualdade e para isso nós criamos uma nova variável, a aux2
:
let aux2 = Plus_comm (Length (Rev xs.tail)) (1n)
Agora nosso contexto está ainda melhor:
• aux2: Equal Nat (Plus (Length (Rev tail)) 1n) (Nat.succ (Length (Rev tail)))
Como estamos progredindo nas provas formais, é possível perceber que o lado esquerdo da aux2
é igual ao direito da aux1
e podemos encadear um no outro com o Equal.chain
:
let chn = Equal.chain aux1 aux2
Ao dar o Type Check, vemos nosso novo contexto:
• chn : Equal Nat (Length (Concat (Rev tail) (List.cons Nat head (List.nil Nat)))) (Nat.succ (Length (Rev tail)))
Nossa variável chn
é praticamente idêntica ao nosso Expected
só diferindo na parte final, pois Expected
espera um Nat.succ (Length xs.tail)
e o chn
nos dá Nat.succ (Length (Rev xs.tail))
, mas nós temos a variável ind
que nos retorna essa igualdade. Vamos relembrar:
• ind: Equal Nat (Length (Rev tail)) (Length tail)
Incrível, não é? Ela nos retorna exatamente o que precisamos, que o tamanho do reverso da tail
é igual ao tamanho da tail
, então basta reescrever a variável ind
na nossa chn
:
let rrt = Equal.rewrite ind (x => Equal Nat (Length (Concat (Rev tail) (List.cons head (List.nil)))) (Nat.succ x )) chn
Vamos ver nosso novo contexto, apenas ocultando os tipos para uma leitura mais fácil:
+ INFO Inspection.
• Expected: Equal Nat (Length (Concat (Rev tail) (List.cons _ head (List.nil _)))) (Nat.succ (Length tail))
• Context:
• head : Nat
• tail : (List Nat)
• ind : Equal Nat (Length (Rev tail)) (Length tail)
• ind = (Rev_length tail)
• aux1 : Equal Nat (Length (Concat (Rev tail) (List.cons Nat head (List.nil Nat)))) (Plus (Length (Rev tail)) 1n)
• aux1 = (Concat_length (Rev tail) (List.cons Nat head (List.nil Nat)))
• aux2 : Equal Nat (Plus (Length (Rev tail)) 1n) (Nat.succ (Length (Rev tail)))
• aux2 = (Plus_comm (Length (Rev tail)) 1n)
• chn : Equal Nat (Length (Concat (Rev tail) (List.cons Nat head (List.nil Nat)))) (Nat.succ (Length (Rev tail)))
• chn = Equal.chain Nat (Length (Concat (Rev tail) (List.cons Nat head (List.nil Nat)))) (Plus (Length (Rev tail)) 1n) (Nat.succ (Length (Rev tail))) aux1 aux2
• rrt : Equal Nat (Length (Concat (Rev tail) (List.cons Nat head (List.nil Nat)))) (Nat.succ (Length tail))
• rrt = Equal.rewrite Nat (Length (Rev tail)) (Length tail) ind (x => Equal Nat (Length (Concat (Rev tail) (List.cons Nat head (List.nil Nat)))) (Nat.succ x))) chn
Agora é muito mais fácil perceber que nosso rrt
é exatamente o nosso Expected
, então nossa prova fica assim:
Rev_length (xs: List Nat) : Equal Nat (Length (Rev xs)) (Length xs)
Rev_length List.nil = Equal.refl
Rev_length (List.cons Nat head tail) =
let ind = Rev_length tail
let aux1 = Concat_length (Rev tail) [head]
let aux2 = Plus_comm (Length (Rev tail)) (1n)
let chn = Equal.chain aux1 aux2
let rrt = Equal.rewrite ind (x => Equal Nat (Length (Concat (Rev tail) [head])) (Nat.succ x)) chn
rrt
Exercícios de Listas, parte 1
List_exercises
Vamos praticar um pouco mais com as listas:
Concat_nil_r (xs: List Nat) : Equal (Concat xs List.nil) xs
Concat_nil_r xs = ?
Concat_assoc (xs: List Nat) (ys: List Nat) (zs: List Nat) : Equal (Concat (Concat xs ys) zs) (Concat xs (Concat ys zs))
Concat_assoc xs ys zs = ?
Rev_app_distr (xs: List Nat) (ys: List Nat) : Equal (Rev (Concat xs ys)) (Concat (Rev ys) (Rev xs))
Rev_app_distr xs ys = ?
Rev_involutive (xs: List Nat) : Equal (Rev (Rev xs)) xs
Rev_involutive xs = ?
Há uma solução curta para a próxima. Se você estiver achando muito difícil ou começar a ficar longo demais, recue e tente procurar uma maneira mais simples.
Concat_assoc4 (l1: List Nat) (l2: List Nat) (l3: List Nat) (l4: List Nat) : Equal (List Nat) (Concat l1 (Concat l2 (Concat l3 l4))) (Concat (Concat (Concat l1 l2) l3) l4)
Concat_assoc4 l1 l2 l3 l4 = ?
Um exercício sobre sua implementação de Nonzeros:
Nonzeros_app (xs: List Nat) (ys: List Nat) : Equal (List Nat) (Nonzeros (Concat xs ys)) (Concat (Nonzeros xs) (Nonzeros ys))
Nonzeros_app xs ys = ?
Beq_NatList
Preencha a definição de beq_NatList, que compara listas de números para igualdade. Prove que beq_NatList xs ys produz Bool.true para cada lista.
Beq_NatList (xs: List Nat) (ys: List Nat) : Bool
Beq_NatList xs ys = ?
Test_beq_natlist1 : Equal Bool (Beq_list List.nil List.nil) Bool.true
Test_beq_natlist1 = ?
Test_beq_natlist2 : Equal Bool (Beq_list [1n,2n,3n] [1n,2n,3n]) Bool.true
Test_beq_natlist2 = ?
Test_beq_natlist3 : Equal Bool (Beq_list [1n,2n,3n] [1n,2n,4n]) Bool.false
Test_beq_natlist3 = ?
Beq_natlist_refl (xs: List Nat) : Equal Bool Bool.true (Beq_list xs xs)
Beq_natlist_refl xs = ?
Exercícios de Listas, parte 2
Proofs
Prove o seguinte teorema, ele ajudará você na prova seguinte
Ble_n_succ_n (n: Nat) : Equal Bool (Lte n (Nat.succ n)) Bool.true
Ble_n_succ_n n = ?
Aqui estão mais alguns pequenos teoremas para provar sobre suas definições sobre listas.
Count_member_nonzero (xs: List Nat) : Equal Bool (Lte 1n (Count 1n (List.cons 1n xs))) Bool.true
Count_member_nonzero xs = ?
Rev_injective
Prove que a função Rev é injetiva - isto é
Rev_injective (xs: List Nat) (ys: List Nat) (e: Equal (List Nat) (Rev xs) (Rev ys)) :tail Equal (List Nat) xs ys
Rev_injective xs ys e = ?
Opcional: Count_sum
Escreva um teorema interessante sobre Listas envolvendo as funções count e sum, e prova-o. (Você pode encontrar que a dificuldade da prova depende de como você definiu o count!)
Count_sum : ?