Aula 6 · Verificação Formal de Software

Programação Funcional

Tipos indutivos, recursão, estruturas e classes de tipos

Baseado no Hitchhiker's Guide to Logical Verification (LoVe), capítulo 5.

§6.1 Tipos indutivos e seus princípios

  • Um tipo indutivo é a sua lista de construtores, e todo valor é construído aplicando-os, cada um de uma única maneira.

  • Para um tipo de dados, Lean gera quatro princípios: o recursor T.rec, o casesOn não recursivo, a injetividade de cada construtor e a disjunção de construtores distintos.

Nat.succ.injEq : (u v : ), (u.succ = v.succ) = (u = v)#check @Nat.succ.injEq
Nat.succ.injEq :  (u v : ), (u.succ = v.succ) = (u = v)
  • O recursor é a forma bruta da recursão estrutural e, lido sobre uma proposição, a indução estrutural, que a Aula 7 deriva.

§6.1 Recursão e análise de casos a partir do recursor

  • O casamento de padrões e a tática cases são sintaxe de superfície que o compilador de equações e cases traduzem no recursor.

namespace Func def usingRec (n : ) : := Nat.rec (motive := fun _ => ) 0 (fun _ ih => ih + 2) n example : usingRec 3 = 6 := rfl end Func
namespace Func example (m n : ) (h : Nat.succ m = Nat.succ n) : m = n := m:n:h:m.succ = n.succm = n All goals completed! 🐙 example (n : ) : Nat.succ n 0 := Nat.succ_ne_zero n end Func

§6.2 Recursão estrutural

  • Cada chamada recursiva é sobre um argumento estruturalmente menor, então a definição termina e se traduz no recursor.

namespace Func def fact : | 0 => 1 | n + 1 => (n + 1) * fact n def fib : | 0 => 0 | 1 => 1 | n + 2 => fib n + fib (n + 1) end Func

§6.2 Por que a terminação

  • Uma definição total expõe a sua equação como teorema utilizável, então aceitar loopy = loopy + 1 provaria False. Lean rejeita tais definições; um laço genuíno precisa de partial def, que fica opaco.

A equação falsa dá False

namespace Func opaque loopy : axiom loopy_eq : loopy = loopy + 1 theorem loopy_false : False := False h:loopy = loopy + 1False All goals completed! 🐙 end Func

Rejeitada: nenhum argumento diminui

def loopForever {α : Type} :
    List α → List α
  | []      => []
  | x :: xs =>
      loopForever (x :: xs)
  • A saída de emergência termination_by fica para depois; esta aula é estrutural.

§6.3 Casamento de padrões como expressão

  • match e if trazem o casamento de padrões para um termo. Um match se traduz pelo recursor do tipo, e if ramifica sobre uma instância Decidable. Option empacota um resultado parcial.

namespace Func def classify (n : ) : String := match n with | 0 => "zero" | 1 => "one" | _ => "many" def isZero (n : ) : Bool := if n = 0 then true else false end Func
namespace Func def pred? : Option | 0 => none | n + 1 => some n example : pred? 0 = none := rfl end Func
  • Os padrões são tentados de cima para baixo; o coringa _ casa com qualquer coisa.

§6.4 Estruturas

  • Uma estrutura é um tipo indutivo de único construtor com campos nomeados, e Lean deriva uma projeção para cada um.

namespace Func structure Segment where lo : hi : def width (s : Segment) : := s.hi - s.lo def seg1 : Segment := { lo := 1, hi := 5 } end Func
namespace Func example : seg1 = 1, 5 := rfl example : width seg1 = 4 := width seg1 = 4 All goals completed! 🐙 end Func
  • { … } e ⟨…⟩ constroem o mesmo valor.

§6.4 Estendendo registros

  • extends constrói um registro maior sobre um menor, e { r with … } copia um registro mudando um campo.

namespace Func structure NamedSegment extends Segment where name : String def ns1 : NamedSegment := { lo := 0, hi := 2, name := "a" } end Func
namespace Func example : ns1.lo = 0 := ns1.lo = 0 All goals completed! 🐙 example : ns1.name = "a" := rfl def widen (s : Segment) : Segment := { s with hi := s.hi + 1 } end Func
  • Os campos herdados permanecem acessíveis.

§6.5 Classes de tipos

  • Uma class declara operações parametrizadas por um ou mais argumentos, uma instance as fornece, e a resolução encontra a instância a partir desses argumentos. Std.Associative op é indexada por uma operação, não só por um tipo.

namespace Func class Size (α : Type) where size : α instance {α : Type} : Size (List α) where size xs := xs.length instance {α : Type} : Size (Option α) where size | none => 0 | some _ => 1 end Func
namespace Func def usize {α : Type} [Size α] (a : α) : := Size.size a 3#eval usize [1, 2, 3] end Func
3

§6.5 As classes que já usamos

  • O da Aula 2 e a associatividade e a comutatividade da Aula 4 são classes de tipos resolvidas por tipo.

@Membership.mem : {α : outParam (Type u_1)} {γ : Type u_2} [self : Membership α γ] γ α Prop#check @Membership.mem @Std.Associative : {α : Sort u_1} (α α α) Prop#check @Std.Associative
  • Membership dá ao o seu significado por meio de uma instância escolhida pelo tipo do contêiner, e Std.Associative e Std.Commutative são as instâncias que ac_rfl consultou.

§6.6 Árvores binárias

  • Uma árvore é uma leaf ou uma branch com um valor e duas subárvores; as funções recorrem sobre as subárvores.

namespace Func inductive Tree (α : Type) where | leaf | branch (l : Tree α) (x : α) (r : Tree α) def mirror {α : Type} : Tree α Tree α | .leaf => .leaf | .branch l x r => .branch (mirror r) x (mirror l) end Func
namespace Func def treeSize {α : Type} : Tree α | .leaf => 0 | .branch l _ r => treeSize l + 1 + treeSize r def t1 : Tree := .branch (.branch .leaf 1 .leaf) 2 .leaf 2#eval treeSize t1 end Func
2

§6.6 Opções, somas e vetores dependentes

  • O mesmo esquema constrói opções, tipos soma e tipos dependentes que carregam informação no seu próprio tipo.

namespace Func def mapOption {α β : Type} (f : α β) : Option α Option β | none => none | some a => some (f a) def fromSum : Bool | .inl n => n | .inr b => if b then 1 else 0 end Func
namespace Func inductive Vec (α : Type) : Type where | nil : Vec α 0 | cons {n : } : α Vec α n Vec α (n + 1) end Func
  • Um Vec α n é uma lista de comprimento n. As semanas seguintes desenvolvem os tipos dependentes.

§6.7 Exemplo resolvido: expressões aritméticas

  • Uma expressão é uma constante, uma variável, uma soma ou um produto, e um avaliador computa o seu valor sob um ambiente.

namespace Func inductive AExp where | const (i : ) | var (x : String) | add (a b : AExp) | mul (a b : AExp) def eval (env : String ) : AExp | .const i => i | .var x => env x | .add a b => eval env a + eval env b | .mul a b => eval env a * eval env b end Func
namespace Func def sampleEnv : String := fun s => if s = "x" then 3 else 0 def e1 : AExp := .add (.const 2) (.mul (.var "x") (.const 5)) 17#eval eval sampleEnv e1 end Func
17
  • A sintaxe de uma pequena linguagem; o seu ambiente é o estado.

§6.7 Exemplo resolvido: uma classe de tipos para tamanho

  • Mais uma instância estende Size a árvores, e uma função mede uma lista inteira de valores dimensionáveis por resolução.

namespace Func instance {α : Type} : Size (Tree α) where size := treeSize def totalSize {α : Type} [Size α] : List α | [] => 0 | x :: xs => Size.size x + totalSize xs end Func
namespace Func 4#eval totalSize [t1, mirror t1] end Func
4
  • A resolução encontra a instância de árvore porque os elementos são árvores, o mesmo mecanismo de Membership.

§6.7 Exemplo resolvido: um registro com uma extensão

  • Uma conta se estende a uma conta nomeada, mantendo os dois campos herdados e acrescentando um.

namespace Func structure Account where owner : String balance : structure NamedAccount extends Account where nickname : String def acc : NamedAccount := { owner := "A", balance := 100, nickname := "main" } example : acc.owner = "A" := rfl end Func
  • O único construtor de uma estrutura é o And.intro da Aula 1, com campos nomeados em vez de posicionais.

§6.7 Exemplo resolvido: espelhando uma árvore

  • Espelhar duas vezes devolve a árvore original. Sobre uma árvore fechada isso vale por computação.

namespace Func example : mirror (mirror t1) = t1 := rfl end Func
  • A lei geral mirror (mirror t) = t, para toda árvore, não é uma computação. Ela precisa de indução estrutural, com as chamadas recursivas de mirror fornecendo as hipóteses de indução, e é o primeiro exemplo resolvido da Aula 7.

Resumo

  • Um tipo indutivo é os seus construtores, e o núcleo deriva um recursor, casesOn, injetividade e disjunção.

  • A recursão estrutural termina, e Lean admite apenas definições que terminam.

  • match e if são casamento de padrões como expressão, e Option empacota um resultado parcial.

  • Uma estrutura é um tipo indutivo de único construtor com campos nomeados, construída com ⟨…⟩ ou { … } e estendida com extends.

  • Uma classe de tipos é uma estrutura de operações resolvida por tipo, o mecanismo por trás do e do ac_rfl das aulas anteriores.

  • O mesmo esquema constrói árvores, opções, somas e vetores dependentes.

  • A Aula 7 deriva a indução estrutural do recursor e prova as leis enunciadas aqui.

Exercícios: veja as notas de aula.