Números
Os tipos que definimos até agora são exemplos de tipos enumerados: suas definições enumeram explicitamente um conjunto finito de elemento. Um jeito mais interessante de definir um tipo é estabelecer uma coleção de regras indutivas descrevendo seus elementos. Por exemplo, nós podemos definir os números naturais da seguinte maneira:
type Nat {
zero
succ (pred: Nat)
}
Essa definição pode ser lida como:
Nat.zeroé um número natural;Nat.succé um construtor que recebe um número natural, construindo outro número natural;- Ou seja, se
né um número natural, então(Nat.succ n)também será.
- Ou seja, se
Todo tipo definido indutivamente (Como Nat, Bool ou Dia) é um conjunto de expressões.
A definição de Nat diz como expressões do tipo Nat podem ser construídas:
- A expressão
Nat.zeropertence ao conjunto dosNat; - Se
né uma expressão do conjunto dosNat, então(Nat.succ n)também é uma expressão do conjunto dosNat; e - Expressões formadas dessas duas formas são as únicas que pertencem à
Nat.
As mesmas regras se aplicam para nossas definições de Dia e Bool.
As anotações que usamos para eles são análogas à do construtor
Nat.zero, indicando que não recebem nenhum argumento.
Essas três condições demonstram o poder das declarações indutivas. Elas implicam
que a expressão Nat.zero, a expressão (Nat.succ Nat.zero), a expressão
(Nat.succ (Nat.succ Nat.zero)) e assim por diante, são do conjunto Nat,
enquanto outras expressões como Bool.true, (Andb Bool.true Bool.false),
e (Nat.succ (Nat.succ Bool.false)) não são.
Nós podemos escrever funções simples usando pattern matching em números naturais da mesma forma que fizemos acima - por exemplo, a função predecessor:
Pred (n: Nat) : Nat
// Como números naturais são estritamente não-negativos,
// usamos como convenção que qualquer coisa que seria
// menor do que 0 retorna 0
Pred Nat.zero = Nat.zero
Pred (Nat.succ k) = k
O segundo pattern pode ser lido como: "se n tem a forma (Nat.succ k)
para algum k, retorne k."
MinusTwo (n: Nat) : Nat
MinusTwo Nat.zero = Nat.zero
MinusTwo (Nat.succ Nat.zero) = Nat.zero
MinusTwo (Nat.succ (Nat.succ k)) = k
Para evitar ter que escrever uma sequência de Nat.succ toda vez que você quiser
um Nat, é possível usar sufixo n ao final de número qualquer, exemplo o5n, que recebe um número escrito no tipo primitivo U60 mais o sufixo n e retorna o Nat correspondente.
{...}
Test : Equal Nat 6n (Nat.succ (Nat.succ (Nat.succ (Nat.succ (Nat.succ (Nat.succ Nat.zero))))))
Test = Equal.refl
O construtor Nat.succ tem tipo Nat -> Nat, assim como as funções MinusTwo
e Pred. Todos eles são coisas que, ao serem aplicadas a um Nat, retornam
um Nat. A diferença essencial entre o Nat.succ e os outros dois, no entanto,
é que funções vem com regras de redução - por exemplo, Pred (Nat.succ Nat.zero)
é reduzível para Nat.zero - enquanto que o Nat.succ não. Apesar de ele ser
uma função aplicável a um argumento, ela não computa nada.
Para a maioria das definições de funções de números, só pattern matching não
é suficiente: nós precisaremos também de recursão. Por exemplo, para checar
que um número n é par, nós podemos checar recursivamente se n-2 é par.
Evenb (n: Nat) : Bool
Evenb Nat.zero = Bool.true
Evenb (Nat.succ Nat.zero) = Bool.false
Evenb (Nat.succ (Nat.succ k)) = Evenb k
Nós podemos definir Oddb (função para checar se um número é ímpar) com uma
declaração recursiva semelhante, mas também temos uma definição mais simples
e um pouco mais fácil de trabalhar:
Oddb (n: Nat) : Bool
Oddb n = Notb (Evenb n)
TestOddb1 : Equal Bool (Oddb 1n) Bool.true
TestOddb1 = Equal.refl
TestOddb2 : Equal Bool (Oddb 4n) Bool.false
TestOddb2 = Equal.refl
Naturalmente, nós também podemos definir funções com múltiplos argumentos por recursão.
Plus (n: Nat) (m: Nat) : Nat
Plus Nat.zero m = m
Plus (Nat.succ k) m = Nat.succ (Plus k m)
Somar 3n e 2n retornará 5n como esperado. A simplificação que o Kind realiza para chegar a esse valor pode ser visualizada assim:
Plus (Nat.succ (Nat.succ (Nat.succ Nat.zero))) (Nat.succ (Nat.succ Nat.zero))
> Nat.succ (Plus (Nat.succ (Nat.succ Nat.zero)) (Nat.succ (Nat.succ Nat.zero)))
pela segunda regra de Plus
> Nat.succ (Nat.succ (Plus (Nat.succ Nat.zero)) (Nat.succ (Nat.succ Nat.zero)))
pela segunda regra de Plus
> Nat.succ (Nat.succ (Nat.succ (Plus Nat.zero (Nat.succ (Nat.succ Nat.zero)))))
pela segunda regra de Plus
> Nat.succ (Nat.succ (Nat.succ (Nat.succ (Nat.succ Nat.zero))))
pela primeira regra de Plus
A multiplicação pode ser definida usando a definição de Plus, da seguinte forma:
Mult (n: Nat) (m: Nat) : Nat
Mult Nat.zero m = Nat.zero
Mult (Nat.succ k) m = Plus m (Mult k m)
TestMult1 : Equal Nat (Mult 3n 3n) 9n
TestMult1 = Equal.refl
Você também pode usar pattern matching em duas expressões ao mesmo tempo:
Minus (n: Nat) (m: Nat) : Nat
Minus Nat.zero m = Nat.zero
Minus n Nat.zero = n
Minus (Nat.succ k) (Nat.succ j) = Minus k j
O função Exp pode ser definida usando Mult
(de forma análoga a como se define Mult usando Plus):
Exp (base: Nat) (power: Nat) : Nat
Exp base Nat.zero = Nat.succ Nat.zero
Exp base (Nat.succ k) = Mult base (Exp base k)
Factorial
Lembrando da definição matemática básica de fatorial:
\[\mathrm factorial(n) = \begin{cases} \text{se $n$} = 0\,& 1\\ \text{caso contrário}, & n * factorial(n-1) \end{cases} \]
\[ f(x)=\begin{cases}x&(x = 1)\\xf(x-1)&(x\gt 1)\end{cases} , x\in \Bbb{N} \] Traduza a função fatorial para Kind2:
Factorial (n: Nat) : Nat
Factorial n = ?
TestFactorial1 : Equal Nat (Factorial 3n ) 6n
TestFactorial1 = ?
TestFactorial2 : Equal Nat (Factorial 5n) 120n
TestFactorial2 = ?
A função Eql testa a igualdade entre Naturais, retornando um booleano
Eql (n: Nat) (m: Nat) : Bool
Eql Nat.zero Nat.zero = Bool.true
Eql Nat.zero (Nat.succ j) = Bool.false
Eql (Nat.succ k) Nat.zero = Bool.false
Eql (Nat.succ k) (Nat.succ j) = Eql k j
A função Lte testa se o primeiro argumento é menor ou igual ao segundo,
retornando um booleano
Lte (n: Nat) (m: Nat) : Bool
Lte Nat.zero m = Bool.true
Lte (Nat.succ k) Nat.zero = Bool.false
Lte (Nat.succ k) (Nat.succ j) = Lte k j
TestLte1 : Equal Bool (Lte 2n 2n) Bool.true
TestLte1 = Equal.refl
TestLte2 : Equal Bool (Lte 2n 4n) Bool.true
TestLte2 = Equal.refl
TestLte3 : Equal Bool (Lte 4n 2n) Bool.false
TestLte3 = Equal.refl
Blt_nat
A função Blt_nat testa a relação "menor que" em números naturais.
Em vez de criar uma nova função recursiva, defina ela usando funções previamente definidas.
Blt_nat (n: Nat) (m: Nat) : Bool
Blt_nat n m = ?
Test_blt_nat_1 : Equal Bool (Blt_nat 2n 2n) Bool.false
Test_blt_nat_1 = ?
Test_blt_nat_2 : Equal Bool (Blt_nat 2n 4n) Bool.true
Test_blt_nat_2 = ?
Test_blt_nat_3 : Equal Bool (Blt_nat 4n 2n) Bool.false
Test_blt_nat_3 = ?