From 7bde573e030eaf1e0ae3c20f1ed404b461eb8e78 Mon Sep 17 00:00:00 2001 From: Alexandre Rademaker Date: Sun, 13 Sep 2026 15:49:44 -0300 Subject: [PATCH 01/14] refactor(book): new chapter order, drop Mastermind, promote SeaBattle MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The order becomes IntroCS, IntroL, Logic, Sets, SeaBattle, Morphology, InfEngine, English. Two chapters move and one is created by promotion. `SeaBattle` moves after `Logic` and `Sets` because its exercises prove theorems about `WellFormed` by induction on an inductive predicate, which needs tactics `Logic` presents. `Morphology` moves after `SeaBattle` so its exercises may use those tactics too, instead of being confined to what `IntroL` alone allows; it depends on nothing but `IntroL`, so the move is free. Mastermind is dropped. It was disconnected from the Sea Battle material before it, and it announced a semantics in propositional logic that it never gave — the propositional content was about eighteen lines the implementation never used. Reinstating it would mean writing that encoding, not translating it. Two exercises go with it, `four-turn-game` and `chess-grammar`. With Mastermind gone `Games.lean` wrapped a single section, so `SeaBattle` becomes the chapter: `CSwL/Games/SeaBattle.lean` moves to `CSwL/SeaBattle.lean` and the glue file is deleted. The `Games` tag goes with it — `IntroL`'s `{ref "Games"}` becomes `{ref "SeaBattle"}`. Prose that named positions rather than chapters is corrected, as STYLE-WRITING.md requires after a move: `Morphology`'s opening no longer says "o capítulo anterior", and a stale comment in `Logic.lean` claiming English precedes it is fixed. Documentation that indexes by chapter order is updated: DEVIATIONS.md's table, dependency paragraph and per-chapter sections (reordered and renumbered); PROVENANCE.md's paths and dropped-exercise records; README.md's chapter list; and STYLE-CODE.md's first-use ledger. Re-deriving that ledger turned up something the original sweep missed: `native_decide` is used eleven times in `SeaBattle.lean`, in ordinary code rather than in solutions, so students see it. It is presented nowhere. The known-gaps entry is widened accordingly; the fix belongs to #7. Refs #3, #12. --- Book.lean | 8 +- CSwL/Games.lean | 25 ------ CSwL/Games/Mastermind.lean | 132 -------------------------------- CSwL/IntroL.lean | 2 +- CSwL/Logic.lean | 4 +- CSwL/Morphology.lean | 5 +- CSwL/{Games => }/SeaBattle.lean | 7 ++ DEVIATIONS.md | 91 ++++++++++------------ PROVENANCE.md | 36 ++++++--- README.md | 13 ++-- STYLE-CODE.md | 32 ++++---- 11 files changed, 105 insertions(+), 250 deletions(-) delete mode 100644 CSwL/Games.lean delete mode 100644 CSwL/Games/Mastermind.lean rename CSwL/{Games => }/SeaBattle.lean (99%) diff --git a/Book.lean b/Book.lean index 0cc72a4..45aebdd 100644 --- a/Book.lean +++ b/Book.lean @@ -3,10 +3,10 @@ import Bib import CSwL.IntroCS import CSwL.IntroL -import CSwL.Morphology -import CSwL.Games import CSwL.Logic import CSwL.Sets +import CSwL.SeaBattle +import CSwL.Morphology import CSwL.InfEngine import CSwL.English @@ -29,9 +29,9 @@ set_option verso.code.warnLineLength 80 #doc (Manual) "Semântica computacional com Lean" => {include CSwL.IntroCS} {include CSwL.IntroL} -{include CSwL.Morphology} -{include CSwL.Games} {include CSwL.Logic} {include CSwL.Sets} +{include CSwL.SeaBattle} +{include CSwL.Morphology} {include CSwL.InfEngine} {include CSwL.English} diff --git a/CSwL/Games.lean b/CSwL/Games.lean deleted file mode 100644 index 07797c7..0000000 --- a/CSwL/Games.lean +++ /dev/null @@ -1,25 +0,0 @@ -import CSwLMeta -import CSwLCompat -import Bib -import Mathlib.Data.List.Chain -import CSwL.Games.SeaBattle -import CSwL.Games.Mastermind - -import VersoManual - -open Verso Genre Manual - --- Capítulo "cola": reúne, via `{include 1 ...}`, duas linguagens de jogo --- como primeiros exemplos de gramática. -#doc (Manual) "Gramáticas para jogos" => -%%% -tag := "Games" -htmlSplit := .never -file := "Games" -%%% - -O capítulo trata de como definir uma língua — no sentido amplo: um conjunto de strings bem formadas — por meio de uma gramática. Os dois exemplos são de linguagens sobre jogos. - -{include 1 CSwL.Games.SeaBattle} - -{include 1 CSwL.Games.Mastermind} diff --git a/CSwL/Games/Mastermind.lean b/CSwL/Games/Mastermind.lean deleted file mode 100644 index ca1a95b..0000000 --- a/CSwL/Games/Mastermind.lean +++ /dev/null @@ -1,132 +0,0 @@ -import CSwLMeta -import Bib - -open Verso.Genre Manual -open CSwLMeta - -#doc (Manual) "Mastermind (Jogo Senha)" => -%%% -tag := "Mastermind" -%%% - -Outra linguagem bem simples é a do Mastermind (Jogo Senha). O Mastermind é um jogo de dois jogadores em que um deles tenta descobrir o código escolhido pelo outro. Um dos jogadores decide uma sequência de quatro pinos coloridos, com as cores escolhidas dentro de um conjunto fixo. O outro jogador (quem tenta decifrar) tenta adivinhar o padrão de cores. Depois de cada palpite, quem propôs o código dá uma resposta indicando sua correção. Essa resposta consiste numa sequência de pinos pretos e brancos: um pino preto para cada pino da cor certa na posição certa, e um pino branco para cada pino adicional da cor certa, mas na posição errada. Se o código secreto é vermelho, azul, verde, amarelo, e o palpite é verde, azul, vermelho, laranja, a resposta é um preto (o azul está na posição certa) e dois brancos (verde e vermelho aparecem no palpite, mas nas posições erradas). Os palpites e as respostas se alternam até que o padrão seja descoberto. O desafio é adivinhar o padrão no menor número de tentativas. - -```bnf -colour ::= "red" | "yellow" | "blue" | "green" | "orange" ; -answer ::= "black" | "white" ; -guess ::= colour colour colour colour ; -reaction ::= answer - | answer answer - | answer answer answer - | answer answer answer answer ; -turn ::= guess reaction ; -game ::= turn | turn game ; -``` - -Note que os pinos pretos e brancos são colocados em qualquer ordem, não correspondem a uma sinalização por posição. Uma desvantagem da implementação a seguir é que dois diferentes termos do tipo `Reaction` poderiam representar a mesma _resposta_ para uma tentativa. - -Dois tipos do Lean entram aqui, ambos porque a gramática fixa quantidades. Um palpite tem exatamente quatro pinos, e `Vector Colour 4` é a lista de `Colour` cujo comprimento é quatro — o tamanho faz parte do tipo, então uma lista de três cores sequer elabora como `Guess`. Seus valores se escrevem `#v[...]`, como em `turn1` abaixo. - -Uma resposta tem *no máximo* quatro pinos, que é uma condição e não um tamanho fixo. Para isso serve um *subtipo*: `{ r : List Answer // r.length ≤ 4 }` é o tipo das listas de `Answer` acompanhadas de uma prova de que seu comprimento não passa de quatro. Um valor seu é o par `⟨lista, prova⟩` — daí o `⟨[.black, .white], by simp⟩` mais abaixo, onde `by simp` é a prova de que essa lista tem comprimento menor ou igual a quatro. Sobre ambos, ver {citep Bib.LLR}[]; sobre subtipos em particular, {citep Bib.love2026}[Seção 12.4]. - -```lean -namespace Mastermind - -inductive Colour where - | red | yellow | blue | green | orange - deriving DecidableEq, Repr - -inductive Answer where - | black | white - deriving DecidableEq, Repr - -abbrev Guess := Vector Colour 4 - -/-- uma alternativa `Vector (Option Answer) 4` -/ -abbrev Reaction := { r : List Answer // r.length ≤ 4 } - -structure Turn where - guess : Guess - reaction : Reaction - deriving DecidableEq, Repr - -abbrev Game := List Turn - -def turn1 : Turn := - ⟨#v[.green, .blue, .red, .orange], - (⟨[.black, .white], by simp⟩ : Reaction) ⟩ - -end Mastermind -``` - -::::exercise (rating := 1) (name := "four-turn-game") - -Revise a gramática para garantir que um jogo tenha no máximo quatro -jogadas. - -```lean -namespace Mastermind - -abbrev Game₄ := solution!(Vector Turn 4) - -end Mastermind -``` -:::: - - -::::exercise (rating := 1) (name := "chess-grammar") - -Escreva suas próprias gramáticas para o xadrez e em seguida sua implementação no Lean. - -```bnf -figure ::= "King" | "Queen" | "Knight" - | "Rook" | "Bishop" | "Pawn" ; -row ::= "a" | "b" | "c" | "d" | "e" | "f" | "g" | "h" ; -column ::= "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" ; -move ::= figure row column ; -turn ::= move move ; -game ::= turn | turn game ; -``` - -```lean -namespace Chess - -inductive Figure where - | king | queen | knight | rook | bishop | pawn - deriving DecidableEq, Repr - -inductive Row where - | a | b | c | d | e | f | g | h - deriving DecidableEq, Repr - -structure Move where - figure : Figure - row : Row - column : Fin 8 - deriving DecidableEq, Repr - -structure Turn where - white : Move - black : Move - deriving DecidableEq, Repr - -abbrev Game := List Turn - -end Chess -``` -:::: - -::::quiz -Todas as gramáticas que discutimos geram linguagens infinitas? - -:::quizSolution -O sinal claro de uma linguagem livre de contexto infinita é uma regra de produção da forma `A → W A V`, em que `W` e `V` não são ambos vazios. Um exemplo é a regra `game → turn game`. Isso é chamado de uso recursivo de um não-terminal. A recursão também pode ser indireta, passando por um ou mais outros não-terminais: `A → W B V`, `B → Y A Z`. Basta procurar esse tipo de recursão nas gramáticas para identificar quais delas geram linguagens infinitas. -::: -:::: - -A partir das discussões acima, poderíamos sugerir uma primeira gramática para um fragmennto do inglês talvez um tanto quanto permissiva. Qualquer sequencia de caracteres ASCII. - -```bnf - character ::= _ascii ; - string ::= character | character string ; -``` diff --git a/CSwL/IntroL.lean b/CSwL/IntroL.lean index 7a065fa..b0cc417 100644 --- a/CSwL/IntroL.lean +++ b/CSwL/IntroL.lean @@ -632,7 +632,7 @@ Quando nenhuma forma carrega argumento, o tipo é uma enumeração; quando carrega, é um registro variante; quando a forma se refere ao próprio tipo sendo definido, é uma árvore. As três coisas são o mesmo mecanismo. -Essa é a construção mais importante do curso. Em {ref "Games"}[Gramáticas para jogos] +Essa é a construção mais importante do curso. Em {ref "SeaBattle"}[Batalha Naval] veremos que uma gramática escrita na notação usual — a Forma de Backus-Naur — é literalmente um tipo `inductive`, e daí em diante todo fragmento da língua é declarado assim. diff --git a/CSwL/Logic.lean b/CSwL/Logic.lean index 5bb403e..036a471 100644 --- a/CSwL/Logic.lean +++ b/CSwL/Logic.lean @@ -9,8 +9,8 @@ import VersoManual open Verso Genre Manual -- Capítulo "cola": reúne, via `{include 1 ...}`, lógica proposicional e --- lógica de predicados — a ferramenta básica que os fragmentos de inglês do --- capítulo anterior vão usar para representar significado. +-- lógica de predicados — a ferramenta básica com que os fragmentos de inglês +-- representarão significado. #doc (Manual) "Lógica" => %%% tag := "Logic" diff --git a/CSwL/Morphology.lean b/CSwL/Morphology.lean index 5b91298..63907bd 100644 --- a/CSwL/Morphology.lean +++ b/CSwL/Morphology.lean @@ -24,9 +24,8 @@ htmlSplit := .never file := "Morphology" %%% -Três exemplos de PLN que aplicam o Lean visto no capítulo anterior: -harmonia vocálica do finlandês, plural do sueco e uma representação de -fonemas por traços. +Três exemplos de processamento de língua natural: harmonia vocálica do +finlandês, plural do sueco e uma representação de fonemas por traços. {include 1 CSwL.Morphology.FinnishVowelHarmony} diff --git a/CSwL/Games/SeaBattle.lean b/CSwL/SeaBattle.lean similarity index 99% rename from CSwL/Games/SeaBattle.lean rename to CSwL/SeaBattle.lean index 0539a11..3765bc3 100644 --- a/CSwL/Games/SeaBattle.lean +++ b/CSwL/SeaBattle.lean @@ -3,6 +3,8 @@ import CSwLCompat import Bib import Mathlib.Data.List.Chain +import VersoManual + open Verso.Genre Manual open CSwLMeta @@ -11,8 +13,13 @@ set_option verso.code.warnLineLength 80 #doc (Manual) "Batalha Naval" => %%% tag := "SeaBattle" +htmlSplit := .never +file := "SeaBattle" %%% +Como definir uma língua — no sentido amplo: um conjunto de strings bem +formadas — por meio de uma gramática. O exemplo é a linguagem de um jogo. + # Sintaxe Batalha naval é um jogo de tabuleiro de dois jogadores, no qual os jogadores têm de adivinhar em que quadrados estão os navios do oponente. O jogo pode ser jogado com uma comunicação bastante limitada entre os jogadores. diff --git a/DEVIATIONS.md b/DEVIATIONS.md index 100e649..06b8974 100644 --- a/DEVIATIONS.md +++ b/DEVIATIONS.md @@ -51,7 +51,7 @@ cannot. The loosening: Lean's own basic types may be introduced *where they are first needed*, in a sentence or two, with a citation of the Lean Language Reference (`{citep Bib.LLR}[]`), instead of being pushed back into `IntroL.lean`. `Fin` -and `Vector` in `Games.lean` are the cases. `IntroL.lean` presents what the +in `SeaBattle.lean` is the case. `IntroL.lean` presents what the book builds on repeatedly; a type used in one chapter is better introduced there, next to its use. The loosening covers types the language already gives us — never a construct this book defines, and never one that needs more than a @@ -79,21 +79,23 @@ CSwFP/2 also cannot be split cleanly, because its sections form a definitional c |---|----------------------|---------------------------------------------------|----------|------------| | 1 | `IntroCS.lean` | 1.1–1.6 | — | — | | 2 | `IntroL.lean` | 3.1–3.10, 3.13; 2.3, 2.4, 2.5 | 1 | — | -| 3 | `Morphology.lean` | 3.11, 3.14 | 2 | — | -| 4 | `Games.lean` | 4.1, 5.1, 5.4 (implementation only) | 2 | — | -| 5 | `Logic.lean` | 4.4, 4.5, 4.6, 4.7, 5.2, 5.3, 5.5, 5.4 (encoding) | 2, 4 | — | -| 6 | `Sets.lean` | 2.1, 2.2 | 2, 5 | — | -| 7 | `InfEngine.lean` | 4.3, 5.7 | 2, 5 | 6 | -| 8 | `English.lean` | 4.2, 5.6 | 2, 5, 6 | — | -| 9 | `ModelChecking.lean` | 6.1–6.5 | 2, 5, 8 | 6 | +| 3 | `Logic.lean` | 4.4, 4.5, 4.6, 4.7, 5.2, 5.3, 5.5 | 2 | — | +| 4 | `Sets.lean` | 2.1, 2.2 | 2, 3 | — | +| 5 | `SeaBattle.lean` | 4.1, 5.1 | 2, 3 | 4 | +| 6 | `Morphology.lean` | 3.11, 3.14 | 2 | 3 | +| 7 | `InfEngine.lean` | 4.3, 5.7 | 2, 3 | 4 | +| 8 | `English.lean` | 4.2, 5.6 | 2, 3, 4 | — | +| 9 | `ModelChecking.lean` | 6.1–6.5 | 2, 3, 8 | 4 | -`Logic.lean` requires `Games.lean` because its closing section encodes a game the reader has already implemented. `English.lean` requires `Sets.lean` because its categorial section interprets a transitive verb over `Sets.Entity` and `Sets.likesR`, the domain and relation that chapter introduces. `Sets.lean` requires `Logic.lean` because its exercises are proofs, and the tactics they need — quantifiers, `cases`, `by_contra` — arrive there; but nothing requires `Sets.lean` in turn, so its position is fixed from below and free from above. +`Sets.lean` requires `Logic.lean` because its exercises are proofs, and the tactics they need — quantifiers, `cases`, `by_contra` — arrive there. `SeaBattle.lean` requires `Logic.lean` for the same reason: it proves theorems about `WellFormed` by induction on an inductive predicate, which no earlier chapter has the machinery for. `English.lean` requires `Sets.lean` because its categorial section interprets a transitive verb over `Sets.Entity` and `Sets.likesR`, the domain and relation that chapter introduces. + +`Morphology.lean` requires only `IntroL.lean` — its three sections are programs, and the proofs in them are `rfl` on concrete values. It is placed after `SeaBattle.lean` rather than at its old position right after `IntroL.lean` so that its exercises may use the tactics `Logic.lean` presents, rather than being confined to what `IntroL.lean` alone allows. ## Reusing Mathlib and CSLib Reusing Mathlib and CSLib is a declared intention of this project, and contributing back to CSLib is another. So the default is to reuse, and every place where this book defines something a library already has needs a reason. Two such places are recorded here; both are decisions for the chapters through CSwFP/6, and both are revisited in "Beyond CSwFP/6". -What is reused today is modest and worth stating plainly: Mathlib supplies `Set`, `Rel`, `Setoid`, `Finset` and `Fintype` to `Sets.lean`, `List.Chain` to `Games.lean`, and the tactic library throughout. No chapter imports CSLib yet. +What is reused today is modest and worth stating plainly: Mathlib supplies `Set`, `Rel`, `Setoid`, `Finset` and `Fintype` to `Sets.lean`, `List.Chain` to `SeaBattle.lean`, and the tactic library throughout. No chapter imports CSLib yet. ### Propositional logic: why not CSLib's, for now @@ -123,7 +125,7 @@ Decision: `PL.lean` defines a plain `eval : Valuation → Form → Bool` and sta ### Grammars: Mathlib's `ContextFreeGrammar`, deferred -`Games.lean` presents a BNF grammar for each game in prose, and then models it not as a grammar but as a handful of ordinary Lean types: enumerations for the terminal categories (`Colour`, `Answer`, `Column`, `Ship`), a `structure` for each rule with a fixed shape (`Turn`, `Attack`, `Move`), and `List`, `Vector` or a subtype where the BNF recurses or bounds a length (`abbrev Game := List Turn`, `abbrev Guess := Vector Colour 4`, `Reaction := { r : List Answer // r.length ≤ 4 }`). +`SeaBattle.lean` presents a BNF grammar in prose, and then models it not as a grammar but as a handful of ordinary Lean types: enumerations for the terminal categories (`Colour`, `Answer`, `Column`, `Ship`), a `structure` for each rule with a fixed shape (`Turn`, `Attack`, `Move`), and `List`, `Vector` or a subtype where the BNF recurses or bounds a length (`abbrev Game := List Turn`, `abbrev Guess := Vector Colour 4`, `Reaction := { r : List Answer // r.length ≤ 4 }`). This flattens the grammar. The BNF rule `game ::= turn | turn game` is recursive; `List Turn` is that recursion already collapsed, and nothing in the chapter connects the two — the correspondence between the displayed BNF and the types below it is asserted in prose and nowhere checked. Sea Battle then adds `inductive WellFormed : Game → Prop` by hand, which is a well-formedness judgement written out because the type alone does not carry it. @@ -138,13 +140,13 @@ The two are not the same object, and the difference is the point. An `inductive` So the library supplies exactly what neither encoding in the book states today: -- that a grammar *generates* a given sentence, as a theorem rather than as a `#eval` — and in `Games.lean` this is the missing link between the BNF in the prose and the types below it; +- that a grammar *generates* a given sentence, as a theorem rather than as a `#eval` — and in `SeaBattle.lean` this is the missing link between the BNF in the prose and the types below it; - derivation in the grammar sense, a sequence of rewriting steps, which is what a BNF actually describes and what `Derives` is. `SeaBattle.lean`'s hand-written `WellFormed` is a partial substitute for it; - unique readability, which `PL.lean` gives up precisely because there is no string to disambiguate. Against a `ContextFreeGrammar` there is one again, and the claim recovers its content: that `toString` lands in the language, and is injective. Deferred all the same, for now. Carrying both representations means giving each grammar twice, which is the duplication this book avoids; `Finset` rules and `Symbol T NT` are heavy machinery for the chapter right after the introduction to Lean; and proving `w ∈ g.language` for a concrete word means building `ReflTransGen` chains, which is real work with no payoff before CSwFP/6. Nothing in CSwFP/1–6 requires it, and the flattened types are what the rest of the chapter computes with. -Taken up after the pending work is done, the natural form is a closing section of `Games.lean` — one grammar given twice, as the flattened types the chapter computes with and as a `ContextFreeGrammar` value, with the bridge theorem between them — and a back-reference from `PL.lean`'s unique-readability discussion, which is the same question in a different chapter. +Taken up after the pending work is done, the natural form is a closing section of `SeaBattle.lean` — one grammar given twice, as the flattened types the chapter computes with and as a `ContextFreeGrammar` value, with the bridge theorem between them — and a back-reference from `PL.lean`'s unique-readability discussion, which is the same question in a different chapter. ## Chapter by chapter @@ -180,41 +182,13 @@ Two consequences of moving 2.3 here: **`instance` is presented here.** The chapter's "Classes de tipos" section shows type classes only from the *use* side — the `[BEq α]` in a signature, and the difference between `BEq` and `DecidableEq`. But instances are declared from `Logic.lean` onwards: `PL.lean` gives `ToString Form`, `FOL.lean` three more, and `English.lean` fifteen, all of them `ToString`. Declaring an instance is a small step from the section already there, and it is the last piece of type classes the book actually needs — no chapter declares a `class` of its own. -**`Prop` is presented here, minimally.** Not by choice: inductive types bring `deriving DecidableEq`, `decide` and `#check 1 = 1`, all of which display `Prop`. `Games/Mastermind.lean` already derives `DecidableEq` on `Colour` and `Answer` in its first code block, two chapters before any logic. The student sees `Prop` whether or not it is introduced. So the chapter presents proposition-as-type, proof-as-term, and `rfl`, `intro`, `exact`, `decide` — and leaves natural deduction and quantifiers to `Logic.lean`. Without this, `IntroL.lean`, `Morphology.lean` and `Games.lean` are `Bool` and `#eval` throughout, which is the original book with Lean as a costume. +**`Prop` is presented here, minimally.** Not by choice: inductive types bring `deriving DecidableEq`, `decide` and `#check 1 = 1`, all of which display `Prop`. `SeaBattle.lean` already derives `DecidableEq` on its enumerations and `Answer` in its first code block, two chapters before any logic. The student sees `Prop` whether or not it is introduced. So the chapter presents proposition-as-type, proof-as-term, and `rfl`, `intro`, `exact`, `decide` — and leaves natural deduction and quantifiers to `Logic.lean`. Without this, `IntroL.lean`, `Morphology.lean` and `SeaBattle.lean` are `Bool` and `#eval` throughout, which is the original book with Lean as a costume. 2.6 (Functional Programming) exists in CSwFP to motivate its chapter 3 from its chapter 2 — "functional programming languages actually are lambda calculi". With the order inverted, that bridge is not needed and the section is absorbed. 2.7 (Further reading) is omitted. -### 3. `Morphology.lean` — CSwFP/3.11, 3.14 - -`Applications` is too vague a name; the chapter is about morphology. Section 3.11 is split into two sections, `FinnishVowelHarmony` and `SwedishPlural` (CSwFP covers Swedish plural inside 3.11, pp. 54–55). Section 3.14 becomes `Phonemes`. - -A short introduction should note that although the book is not about morphology, these examples exercise the Lean concepts just learned. - -*Migration*: check that every Lean feature used here was presented in `IntroL.lean`. - -### 4. `Games.lean` — CSwFP/4.1, 5.1, 5.4 - -Syntax and semantics are presented one after the other for each game, instead of split across two chapters. CSwFP/4.1 carries the syntax of both games, so it is split between the two files. - -- `SeaBattle.lean` — 4.1 (Sea Battle part) + 5.1. Verified free of logic: 5.1 is state-transition semantics. -- `Mastermind.lean` — 4.1 (Mastermind part) + the implementation half of 5.4 (`samepos`, `occurscount`, `reaction`, `updateMM`, `playMM`). - -5.4 opens by announcing itself as an application of propositional logic, but the announcement is not kept: the propositional content is confined to about eighteen lines (the encoding paragraph, Exercise 5.14, and the remark that the secret pattern is *logically implied* by the rules plus the answers). Everything after that is list counting. So 5.4 splits: - -- the implementation stays here; -- the opening sentence, the encoding paragraph and Exercise 5.14 move to `Logic.lean`. Exercise 5.13 stays (combinatorics, and it sets up the size of the search space that `updateMM` filters). - -Splitting Mastermind bends the principle of keeping each game's syntax and semantics together, and the alternative that respects it is to move `Logic.lean` up, right after `IntroL.lean`. That alternative is rejected: it puts the two heaviest formal chapters back to back, before any linguistic payoff, for an audience that `IntroCS.lean` promised natural language to. The game keeps its syntax and its state semantics here; the propositional reading returns as commentary and exercises at the end of `PL.lean`, once the reader has the logic to state it. - -The grammars here are flattened into ordinary types — enumerations, `structure`s, `List` and `Vector` — rather than given as values of Mathlib's `ContextFreeGrammar`; that is a decision, argued in "Reusing Mathlib and CSLib", and the one deferred item that would change this chapter once taken up. - -**`Fin`, `Vector` and subtypes are introduced here, not in `IntroL.lean`.** They are Lean's own basic types, each needed by one grammar and nowhere else: `Fin 10` for a board row, `Vector Colour 4` for a guess of fixed length, and `{ r : List Answer // r.length ≤ 4 }` for a reaction of at most four pins. Each gets a short paragraph where it first appears, with a citation of the Lean Language Reference for the reader who wants more. `Fin` already has one; `Vector` and the subtype do not, and need it. - -CSwFP writes 5.4 as an *echo* of 5.3 — "As in the case of propositional logic, we can now give a Mastermind update function" — the same list comprehension discarding states incompatible with new information. `CSwL` inverts the direction of the analogy: here `updateMM` stands on its own, and in `Logic.lean` the valuation `update` presents itself as having the shape of the `updateMM` the reader already knows. - -### 5. `Logic.lean` — CSwFP/4.4–4.7, 5.2, 5.3, 5.5 +### 3. `Logic.lean` — CSwFP/4.4–4.7, 5.2, 5.3, 5.5 Two files, `PL.lean` (propositional logic) and `FOL.lean` (predicate logic). @@ -250,11 +224,9 @@ should be worth does not arise, since `top` and `bot` are constructors. 1. **`Prop` and proof in Lean** — meta level. Tactics presented as the rules they are, building on the `rfl`/`intro`/`exact`/`decide` of `IntroL.lean`: `apply`; `constructor` and anonymous constructors for ∧ and ↔; `left`, `right` and `cases` for ∨; `False.elim` and `absurd` for ¬; `by_contra`, `by_cases` and `em` for classical reasoning. 2. **`Form` as syntax** — object level: BNF, `inductive Form` (4.4). Glued to it, the section that separates the two levels: `Form.conj p q` is data, `p ∧ q` is a proposition. Glued, not deferred to the end of the chapter — the confusion is born the instant the `inductive` appears. This is a cost Lean creates and Haskell does not have: there the meta level is invisible, living in the prose, so `data Form = ...` cannot be confused with it. -3. **Valuation** — 5.2 and 5.3: truth tables, consequence, `update` over valuations. Then Mastermind's propositional encoding, from 5.4. +3. **Valuation** — 5.2 and 5.3: truth tables, consequence, `update` over valuations. 4. **The bridge** — interpreting `Form` into `Prop` and proving `eval v F = true ↔ ⟦F⟧`. Where deduction and valuation meet. CSwFP cannot have this section. -The Mastermind closing section gains something the original cannot state: because the game is already implemented, there is a theorem to prove — that filtering by `reaction` and filtering by the formula yield the same set of states, i.e. that the propositional encoding is faithful to the implementation. - `FOL.lean` mirrors `PL.lean`'s order: the quantifier rules first — `intro`/`apply` for ∀, `use` and `obtain` for ∃ — then 4.5, 4.6, 4.7, then 5.5. Putting the tactics last would have made the two logic chapters teach the same thing in opposite positions, for no reason. **`Formula` is binary too.** Its `conj` and `disj` take two arguments, with `top` and `bot` as constructors and `Formula.conjs`/`Formula.disjs` recovering the n-ary notation — the same design as `Form`, for the same reason. A constructor holding a `List (Formula α)` would make the type a nested inductive, costing `induction` and `deriving`. The `List α` in `atom name (args : List α)` does not: `α` is a parameter, not the type being defined, so an atom may still take any number of arguments. With that, the definition of truth in 5.5 is a plain recursion, one case per constructor, instead of three mutually recursive functions. The one `mutual` block left in the chapter belongs to `Term`, where a list of terms inside `Term` is what function symbols of arbitrary arity require. @@ -283,9 +255,9 @@ The Mastermind closing section gains something the original cannot state: becaus Unique readability is the other. In CSwFP it is a claim about *strings*: a formula written out as a sequence of symbols has exactly one parse tree, so the notation is unambiguous. In Lean there is no string to disambiguate — a term of type `Form` already *is* the tree, and `Form.conj p q` cannot be read two ways. The claim has nothing left to assert. -Decision: `PL.lean` does not state unique readability as a theorem. What it states instead is what survives the translation — that the constructors are injective and pairwise disjoint, provable by `injection`, which is what Exercise 4.11 already does. The prose says why the original statement dissolves: the ambiguity it rules out is a property of writing formulas down, and the type never writes them down. Recovering the original claim would take a string to disambiguate — either a parser `String → Option Form` with a round-trip theorem, or the grammar stated as a `ContextFreeGrammar` so that `toString` can be shown to land in its language and to be injective. Both are deferred for the same reason: parsing and grammars-as-data are topics of their own, and nothing before CSwFP/6 needs either. See "Reusing Mathlib and CSLib"; it is the same question this chapter and `Games.lean` both run into. +Decision: `PL.lean` does not state unique readability as a theorem. What it states instead is what survives the translation — that the constructors are injective and pairwise disjoint, provable by `injection`, which is what Exercise 4.11 already does. The prose says why the original statement dissolves: the ambiguity it rules out is a property of writing formulas down, and the type never writes them down. Recovering the original claim would take a string to disambiguate — either a parser `String → Option Form` with a round-trip theorem, or the grammar stated as a `ContextFreeGrammar` so that `toString` can be shown to land in its language and to be injective. Both are deferred for the same reason: parsing and grammars-as-data are topics of their own, and nothing before CSwFP/6 needs either. See "Reusing Mathlib and CSLib"; it is the same question this chapter and `SeaBattle.lean` both run into. -### 6. `Sets.lean` — CSwFP/2.1, 2.2 +### 4. `Sets.lean` — CSwFP/2.1, 2.2 What is left of CSwFP/2 after 2.3, 2.4 and 2.5 moved to `IntroL.lean` and `English.lean`: sets and relations. Renamed from `Foundation.lean`, which promised a foundations chapter that no longer exists. `Sets` covers both halves honestly, because CSwFP/2.2 *defines* a relation as a subset of A × B — a relation is a set — and because what the chapter adds in Lean is precisely the representation choice for `Set α`. @@ -300,6 +272,27 @@ What is left of CSwFP/2 after 2.3, 2.4 and 2.5 moved to `IntroL.lean` and `Engli From the `logic_and_proof`, the chapters `sets_in_lean` and `relations_in_lean` can give some exercises or ideas on how to present sets and relations in Lean. +### 5. `SeaBattle.lean` — CSwFP/4.1, 5.1 + +Syntax and semantics are presented one after the other, instead of split across two chapters: 4.1 for the grammar, 5.1 for the state-transition semantics. + +The grammar here is flattened into ordinary types — enumerations, `structure`s and `List` — rather than given as a value of Mathlib's `ContextFreeGrammar`; that is a decision, argued in "Reusing Mathlib and CSLib" above. + +**`Fin` is introduced here, not in `IntroL.lean`.** It is one of Lean's own basic types, needed by this grammar and nowhere else: `Fin 10` for a board row. `DEVIATIONS.md` states this as a general loosening of "presented" — a type needed in exactly one place is introduced where it is used. + +**The chapter comes after `Logic.lean` and `Sets.lean`.** Its exercises prove theorems about `WellFormed` by induction on an inductive predicate, which needs the tactics `Logic.lean` presents. Placing it earlier would mean either weaker exercises or a chapter that uses what the book has not shown. + +**Mastermind was removed.** CSwFP/4.1 carries the syntax of two games and 5.4 gives Mastermind's implementation, and an earlier arrangement of this book had both. It is dropped: the section was disconnected from the Sea Battle material that preceded it, and it announced a semantics in propositional logic that it never delivered — 5.4 opens by calling itself an application of propositional logic, but the propositional content is about eighteen lines that the implementation never uses. Reinstating Mastermind would mean writing that encoding rather than translating it. `PROVENANCE.md` records the exercises that went with it. + + +### 6. `Morphology.lean` — CSwFP/3.11, 3.14 + +`Applications` is too vague a name; the chapter is about morphology. Section 3.11 is split into two sections, `FinnishVowelHarmony` and `SwedishPlural` (CSwFP covers Swedish plural inside 3.11, pp. 54–55). Section 3.14 becomes `Phonemes`. + +A short introduction should note that although the book is not about morphology, these examples exercise the Lean concepts just learned. + +*Migration*: check that every Lean feature used here was presented in `IntroL.lean`. + ### 7. `InfEngine.lean` — CSwFP/4.3, 5.7 The language for talking about classes (4.3) and the inference engine over it (5.7), together instead of split across two chapters. @@ -353,7 +346,7 @@ CSwFP/7 (The Composition of Meaning in Natural Language) goes after `ModelChecki A proof system as data — CSLib's `Cslib.Logic.PL.Theory.Derivation` — is deferred rather than rejected, for the reasons in the `Logic.lean` section. It becomes attractive exactly where `CSwL` would have something to give back: CSLib has the derivations but no propositional semantics, and this book builds the valuation. Soundness — every derivable sequent is true under every valuation satisfying its context — needs both halves, and neither project has both today. `Cslib/Logics/README.md` invites exactly this ("we are interested in expanding them or creating new ones that can cover your use cases"). Its natural place is after `Sets.lean`, once relations and quantifiers are available. It stays out of the plan until CSwFP/1–6 are in place. -Mathlib's `ContextFreeGrammar` for the grammars of `Games.lean` is deferred on the same footing, and for reasons that are ours rather than the library's — see "Reusing Mathlib and CSLib". It is the one deferred item that would change a chapter already written, so it belongs after the pending work already tracked, not before it. +Mathlib's `ContextFreeGrammar` for the grammar of `SeaBattle.lean` is deferred on the same footing, and for reasons that are ours rather than the library's — see "Reusing Mathlib and CSLib". It is the one deferred item that would change a chapter already written, so it belongs after the pending work already tracked, not before it. The related question — whether `PL.lean`'s valuation is written in CSLib's shape from the start — is settled above, and settled against it. diff --git a/PROVENANCE.md b/PROVENANCE.md index ef830ab..33eeb8e 100644 --- a/PROVENANCE.md +++ b/PROVENANCE.md @@ -20,12 +20,16 @@ passage it comes from**. Keep it in step with any further rename. stated in Lean. In the source these carry a `✎` marker, which is otherwise undocumented. -The book has 81 exercises as of 2026-09-02, and every one of them -appears somewhere below — either in a table that names its source, or +The book has 76 exercises as of 2026-09-13, and all but one of them +appear somewhere below — either in a table that names its source, or in the list of those with no counterpart. Two checks keep it that way: no `(name := …)` in `CSwL/` should be absent from this file, and no name cited here should have stopped existing. +The exception is `free-vars-in-formula`, in `Logic/FOL.lean`, which has +no entry here; it predates the chapter reorganization of 2026-09-13 and +still needs one. + ## A correction to the numbers The 17 references in `Sets.lean` were all off by one chapter: they @@ -92,11 +96,24 @@ stay with this chapter when it moves after `Logic.lean`; 2.3 (`Ā̄ = A`) is the one that needs classical reasoning, and is the reason for the move. -### `Games/SeaBattle.lean` - CSwFP/4.1 +### `SeaBattle.lean` - CSwFP/4.1 - Exercise 4.1 is `game-over-grammar` (prose now, but better change to Lean code) +### Mastermind — dropped + +| CSwL id | Rating | CSwFP | Page | Notes | +|---------|--------|-------|------|-------| +| — | — | Exercise 5.13 | — | dropped 2026-09-13 | +| — | — | Exercise 5.14 | — | dropped 2026-09-13 | +| — | — | Exercise 5.15 | — | dropped 2026-09-13 | +| — | — | Exercise 5.16 | — | dropped 2026-09-13 | +`Games/Mastermind.lean` was removed with the chapter reorganization: it was +disconnected from what preceded it, and it promised a semantics in +propositional logic that it never gave. Two exercises went with it, +`four-turn-game` and `chess-grammar`, along with CSwFP/5.13–5.16, which had no +`CSwL` counterpart in the first place. ### `Logic/PL.lean` — CSwFP/4.4 @@ -197,7 +214,6 @@ the definitions rather than queries against them. equivalence, which `Form.equivalent` already is; 5.12 asks to reimplement the semantics with `[String]` instead of `[(String, Bool)]` for valuations. -CSwFP/5.13–5.16 belong to Mastermind and are recorded with `Games.lean`. CSwFP/5.18 is ported as `translate-quantified`. Its propositional counterpart, 4.9, was dropped; the two chapters no longer mirror each other here. @@ -226,13 +242,11 @@ as an oversight. | `Sets.lean` | `above5-subset-above2` | 1 | | `Sets.lean` | `union-contains` | 1 | | `Sets.lean` | `intersection-contained` | 1 | -| `Games/Mastermind.lean` | `four-turn-game` | 1 | -| `Games/Mastermind.lean` | `chess-grammar` | 1 | -| `Games/SeaBattle.lean` | `game-over-grammar` | 2 | -| `Games/SeaBattle.lean` | `defeated-last` | 3 | -| `Games/SeaBattle.lean` | `add-ship` | 3 | -| `Games/SeaBattle.lean` | `sunk` | 2 | -| `Games/SeaBattle.lean` | `grice-maxims` | 1 | +| `SeaBattle.lean` | `game-over-grammar` | 2 | +| `SeaBattle.lean` | `defeated-last` | 3 | +| `SeaBattle.lean` | `add-ship` | 3 | +| `SeaBattle.lean` | `sunk` | 2 | +| `SeaBattle.lean` | `grice-maxims` | 1 | | `IntroL.lean` | `sum-of-squares` | 1 | | `IntroL.lean` | `building-terms` | 1 | | `IntroL.lean` | `rfl-arithmetic` | 1 | diff --git a/README.md b/README.md index 7f08e23..6b2a577 100644 --- a/README.md +++ b/README.md @@ -38,14 +38,13 @@ adapts — see [DEVIATIONS.md](DEVIATIONS.md) for why. - [X] The formal study of natural language — [source](CSwL/IntroCS.lean) - [X] Introduction to Lean — [source](CSwL/IntroL.lean) -- [~] Morphology (Finnish vowel harmony, Swedish plural, phonemes) — - [source](CSwL/Morphology.lean) (sections in - [`CSwL/Morphology/`](CSwL/Morphology)) -- [~] Grammars for games (Sea Battle, Mastermind) — - [source](CSwL/Games.lean) (sections in [`CSwL/Games/`](CSwL/Games)) - [~] Logics (propositional and predicate) — [source](CSwL/Logic.lean) (sections in [`CSwL/Logic/`](CSwL/Logic)) - [~] Sets and relations — [source](CSwL/Sets.lean) +- [~] Sea Battle: a grammar for a game — [source](CSwL/SeaBattle.lean) +- [~] Morphology (Finnish vowel harmony, Swedish plural, phonemes) — + [source](CSwL/Morphology.lean) (sections in + [`CSwL/Morphology/`](CSwL/Morphology)) - [~] An inference engine — [source](CSwL/InfEngine.lean) - [~] A fragment of English — [source](CSwL/English.lean) - [ ] Model checking with predicate logic — `CSwL/ModelChecking.lean` @@ -68,9 +67,9 @@ exercises not reused later in the chapter itself). Mnemonic file names, never numbers. A short chapter is a single file (`CSwL/Sets.lean`, `namespace Sets`); a chapter whose sections are long enough -to deserve their own file is a "glue" file (`CSwL/Games.lean`) that only +to deserve their own file is a "glue" file (`CSwL/Logic.lean`) that only gathers them, via `{include 1 ...}`, from a same-named directory -(`CSwL/Games/SeaBattle.lean`, `CSwL/Games/Mastermind.lean`) — the same pattern +(`CSwL/Logic/PL.lean`, `CSwL/Logic/FOL.lean`) — the same pattern used by [Functional Programming in Lean](https://lean-lang.org/functional_programming_in_lean/). CSwL developments connect with those in [CSLib](https://github.com/leanprover/cslib/) where possible. We aim to reuse CSLib and contribute to CSLib. diff --git a/STYLE-CODE.md b/STYLE-CODE.md index 6b3a722..584d634 100644 --- a/STYLE-CODE.md +++ b/STYLE-CODE.md @@ -52,20 +52,16 @@ this way is usually a mistake; see "Known gaps." | --- | --- | --- | --- | | `IntroCS` | `namespace`, `def` (by pattern matching), `inductive`, `deriving Repr`, `example`, `#eval` | `Nat`, function type `→`, dot-notation constructors (`.num`) | `rfl`, `induction … with`, `rw`, `rewrite`, `unfold`, `repeat` | | `IntroL` | `#check`, `#print`, `theorem`, `structure`, `instance`, `section`, `variable` | `Type`, `Prop`, `Bool`, `List`, `Option`, `Char`, `String`, `fun`/`λ`, `match`, `if … then … else`, `⟨…⟩`, implicit `{}`, instance-implicit `[]`, `∘`, `BEq`, `∀`, `∃`, `∧`, `∨`, `↔`, `≠` | `intro`, `exact`, `apply`, `cases … with`, `constructor`, `obtain`, `show`, `funext`, `omega`, `decide` (solution only) | -| `Morphology` | `abbrev`, `open`, `deriving BEq` | `×` | `native_decide` (solution only) | -| `Games` | — | `DecidableEq`, `Vector`, subtypes (`{ x : T // p x }`), `¬`, `∈` | `simp` (solution only) | -| `Logic` | `mutual`, `private` | `\|>` | `have`, `use`, `left`, `right`, `rcases`, `by_cases`, `by_contra` | -| `Sets` | — | `Set`, `Rel`, `Finset`, `Fintype`, `Setoid`, `⊆`, `∪`, `∩` | `assumption`, `trivial`, `symm`, `simp_all` | +| `Logic` | `abbrev`, `mutual`, `private` | `DecidableEq`, `×`, `¬`, `\|>` | `have`, `use`, `left`, `right`, `rcases`, `by_cases`, `by_contra`, `simp` | +| `Sets` | `open` | `Set`, `Rel`, `Finset`, `Fintype`, `Setoid`, `∈`, `⊆`, `∪`, `∩` | `assumption`, `trivial`, `symm`, `simp_all` | +| `SeaBattle` | — | `Fin` | `native_decide` | +| `Morphology` | `deriving BEq` | — | — | | `InfEngine` | — | `do`-notation | — | | `English` | *(none new)* | *(none new)* | *(none new)* | `English` introduces no new feature: it is where `abbrev` and `ToString` instances become the dominant idiom, but both arrive earlier. -`Games` introduces `Vector` and subtypes in `Games/Mastermind.lean`, and the -subtype gets a paragraph of prose before its first use — the rule working as -intended. - The table records features, not every piece of notation. Type ascription, list literals, projection dots, and the like are not tracked: they arrive with the constructs that use them and tracking them would produce a ledger nobody @@ -76,13 +72,17 @@ maintains. Open questions about the table, recorded so they are not lost. Each needs an author decision, not a mechanical fix. -- **`native_decide`** (`Morphology/SwedishPlural.lean:69`, `:72`) is used - inside solutions and is not in `IntroL`'s tactic table. It closes a goal by - compiling and running it, trusting the compiler rather than the kernel — a - materially different promise from `decide`. Its use is justified where it - appears, and a comment at `:63-66` says why, but that comment is inside the - solution and in Portuguese, so the explanation reaches neither the student - nor the English code-comment rule. +- **`native_decide`** is used eleven times in `SeaBattle.lean` (`:290`, `:291`, + `:307`, `:308`, `:334`, `:338`, `:342`, `:395`, `:396`, `:399`, `:402`) and + twice in `Morphology/SwedishPlural.lean` (`:69`, `:72`), and is presented + nowhere. It closes a goal by compiling and running it, trusting the compiler + rather than the kernel — a materially different promise from `decide`, and a + reader who meets it without being told will draw the wrong conclusion about + what a Lean proof is worth. Only the `SwedishPlural` uses carry an + explanation, in a Portuguese comment inside a solution (`:63-66`), which + reaches neither the student nor the English code-comment rule. The + `SeaBattle` uses are in ordinary code, not solutions, so the student does + see them. - **`trivial`** — one term-level use in `Sets.lean:507`, not presented anywhere. Give it a line or replace it when that chapter is revised. @@ -117,7 +117,7 @@ Material moves between chapters, and a number would be wrong the moment it did. A short chapter is a single file (`CSwL/Sets.lean`). A chapter whose sections -are long enough to deserve their own files is a "glue" file (`CSwL/Games.lean`) +are long enough to deserve their own files is a "glue" file (`CSwL/Logic.lean`) that only gathers them via `{include 1 …}` from a same-named directory. Each content file has its own `namespace`: the book redefines the same names in different chapters, deliberately. From 7ab81dccbd5062c1595a047fb316469c01d8b9f9 Mon Sep 17 00:00:00 2001 From: Alexandre Rademaker Date: Sun, 13 Sep 2026 17:00:49 -0300 Subject: [PATCH 02/14] refactor(logic): promote proving in Lean to its own section MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Issues #9 and #12. The proof material was scattered: `IntroL` presented `Prop`, the tactic table and induction; `PL` and `FOL` each re-presented `Prop` before getting to their own subject. Four blocks now move verbatim into a new `CSwL/Logic/Proof.lean`, which `Logic.lean` includes first: IntroL:300-623 "O tipo Prop e Provas" IntroL:724-748 "Prova por indução" PL:91-368 "Lógica Proposicional em Lean" FOL:44-126 "As regras dos quantificadores em Lean" `IntroL` is now deliberately pre-proof: it uses `example`, `theorem` and `rfl` only as the shape an exercise's tests take, and new prose says so and forwards to `Proof` for what proving means. `PL` and `FOL` keep syntax as a data type, computable semantics, and the bridge to `Prop`. `PL`'s `variable (p q : Prop)` was leaking past the "Sintaxe" section and shadowing the `valuation-table` exercise's own `p`, `q`, `r`; it is now scoped. This only surfaced in the variant builds, not in `lake build`. `STYLE-CODE.md`'s ledger is re-derived: the `Logic` row becomes three, every proof tactic moves from `IntroL` to `Logic/Proof`, and `show`, `omega` and `decide` are recorded under "Known gaps" — they survive only inside the `twice` exercise's solutions, where nothing presents them. A `:::dev` note in `Sets.lean` records that `Sets.Entity` and `FOL.Entity` are distinct types sharing a name, accepted deliberately. This commit was prepared with Claude Code. The AI performed the verbatim block moves, re-derived the feature ledger, and drafted the new Portuguese prose in `IntroL.lean` and `Logic/Proof.lean`; that prose is a draft awaiting human review. --- CSwL/IntroL.lean | 360 +------------------- CSwL/Logic.lean | 15 +- CSwL/Logic/FOL.lean | 83 ----- CSwL/Logic/PL.lean | 289 +--------------- CSwL/Logic/Proof.lean | 748 ++++++++++++++++++++++++++++++++++++++++++ CSwL/Sets.lean | 10 + STYLE-CODE.md | 24 +- 7 files changed, 805 insertions(+), 724 deletions(-) create mode 100644 CSwL/Logic/Proof.lean diff --git a/CSwL/IntroL.lean b/CSwL/IntroL.lean index b0cc417..6823c19 100644 --- a/CSwL/IntroL.lean +++ b/CSwL/IntroL.lean @@ -124,6 +124,17 @@ Conferir tipo não demanda computação, logo o comando `#check` funciona retorn #check g a ``` +Os exercícios deste capítulo vêm com *testes*, e os testes são escritos na +mesma linguagem. Um `example` enuncia uma afirmação sem lhe dar nome; o que +vem depois do `:=` é a justificativa. A tática `rfl` fecha uma igualdade +quando os dois lados *calculam* o mesmo valor — é o que confere se a +definição pedida faz o que se pediu. Um `theorem` é um `example` com nome, +e o nome serve para que o enunciado possa ser citado depois. + +Aqui esses três recursos aparecem só como ferramenta de teste. O que +significa provar em Lean, e como se constrói uma prova que `rfl` não fecha +sozinha, é o assunto de {ref "Proof"}[Prova em Lean]. + ::::exercise (rating := 1) (name := "sum-of-squares") Defina `sumOfSquares` que recebe dois naturais e devolve `m² + n²`. @@ -297,330 +308,6 @@ def scaleX (p : Point) (factor : Float) : Point := #eval scaleX ⟨2.0, 3.0⟩ 10.0 ``` -# O tipo Prop e Provas - -O que diferencia Lean de outras linguagens como Python e Java é a capacidade de na mesma linguagem que usamos para 'programar' funções, escrevermos 'provas' sobre estas funções. - -Nesta 'Exemplos extraídos de {citep Bib.FAA2025}[]. Uma proposição é um enunciado que pode ser verdadeiro ou falso. O enunciado `1 = 1` é verdadeiro, enquanto `square₁ 12 = 2` é falso. Toda proposição é todo tipo `Prop`. - -```lean -#check square₁ 12 = 2 -``` - -Podemos declarar proposições como a seguir e verificar que `1 = 1 : Prop`, mas não podemos _avaliar_ uma proposição. - -```lean -def p1 : Prop := 1 = 1 - -#check p1 -``` - -Toda proposição verdadeira tem uma prova, e uma prova é um _termo_ do tipo da proposição que testemunha a verdade da proposição. Provar `1 = 1` é exibir um termo de tipo `1 = 1`, exatamente o que o termo `Eq.refl 1` faz abaixo. Declarar um teorema é muito parecido com declarar uma função. - -```lean -theorem OneEqSelf : 1 = 1 := Eq.refl 1 -``` - -A mesma ideia vale para dizer que duas funções são a mesma coisa — não é -analogia, é a proposição `f = g`, provável do mesmo jeito. Agora usando o -modo `tactic` iniciado com `by`. Usamos as taticas `rfl` e `intro` que iremos explicar a seguir. Com `example` não precisamos dar nomes a teoremas que não serão reusados. - -```lean -example : - ∀ (z : Nat), (λ x ↦ x * x) z = (fun y => y * y) z := by - intro n - rfl -``` - -Note que perguntar pelo tipo não é o mesmo que decidir se ela é verdadeira: - -```lean -#check (square₁ = square₂) -``` - -Provar é dar um termo cujo tipo é a proposição. Para uma igualdade em que -os dois lados reduzem ao mesmo valor, o termo é `rfl` — de _reflexividade_, -que é o princípio de que tudo é igual a si mesmo. Ver {citep Bib.love2026}[] -para uma explicação sobre `rfl`. - -```lean -theorem square₁_eq_square₂ : square₁ = square₂ := by - rfl -``` - -Escrito com `by`, `rfl` é uma _tática_: uma instrução para construir a -prova. Você pode inspecionar a definição de Lean para `Eq.refl`. - -```lean (name := c2print1) -#print square₁_eq_square₂ -``` - -```leanOutput c2print1 -theorem IntroL.square₁_eq_square₂ : square₁ = square₂ := -Eq.refl square₁ -``` - -Além de `rfl`, um pequeno repertório de táticas resolve o que os capítulos -seguintes precisam — conferido nos próprios arquivos, não escolhido a -priori. A -ordem abaixo é a de {citep Bib.FAA2025}[], que apresenta as táticas nesta -sequência; `decide`, `omega`, `obtain`, `cases`, `simp` e `induction` não -vêm de lá (o curso os introduz onde a necessidade aparece) e ficam ao -final, fora da ordem do FAA2025: - -``` -rfl fecha a = b quando os dois lados calculam o mesmo valor -exact e fornece o termo que é a prova -intro h introduz uma hipótese, para provar uma implicação ou ∀ -constructor parte um ∧ ou um ↔ em dois objetivos -apply h aplica uma implicação ou lema, deixando a(s) premissa(s) - como novo(s) objetivo(s) -unfold nome desdobra uma definição, antes de continuar -rw [h] reescreve o objetivo usando a igualdade h, da esquerda para - a direita -assumption fecha o objetivo com uma hipótese já disponível -decide fecha um objetivo decidível calculando a resposta -omega resolve aritmética linear em Nat e Int -obtain ⟨_,_⟩ := h desmonta uma hipótese composta (conjunção, existencial) -cases h dado h : P ∨ Q, parte a prova em dois casos -simp [...] reescreve com um conjunto de lemas até não haver mais o que - simplificar -induction x prova por casos sobre a forma como x foi construído -funext x duas funções são iguais quando concordam em todo ponto -``` - -Duas notações de prova não são táticas: `⟨t, h⟩` monta um par (para provar -uma conjunção ou exibir a testemunha de um existencial), e `h.1`/`h.2` -desmontam um par que está numa hipótese. - -::::exercise (rating := 1) (name := "rfl-arithmetic") - -Termine a prova usando `rfl`. - -```lean -example : 7 * 6 = 42 := - solution!(rfl) -``` - -:::: - -::::exercise (rating := 1) (name := "square-unfold") - -Prove que `square₁ n = n * n`; uma variável aparece, então `rfl` não basta -sozinho — é preciso desdobrar a definição antes. - -```lean -example (n : Nat) : square₁ n = n * n := by - solution! - unfold square₁ - rfl -``` - -:::: - -::::exercise (rating := 1) (name := "identity-implication") - -Provar `P → Q` é: suponha `P`, derive `Q`. Prove `P → P`. Fonte: -{citep Bib.FAA2025}[] - -```lean -example (P : Prop) : P → P := by - solution! - intro h - exact h -``` - -:::: - -::::exercise (rating := 1) (name := "p-implies-q-implies-p") - -Complete a prova abaixo. Fonte: {citep Bib.FAA2025}[] - -```lean -example (P Q : Prop) : P → (Q → P) := by - solution! - intro h _ - exact h -``` - -:::: - -::::exercise (rating := 1) (name := "and-intro") - -Prove `P ∧ Q` a partir de `P` e de `Q`. Fonte: {citep Bib.FAA2025}[]. Dica: -`constructor` parte o objetivo `P ∧ Q` em dois; cada um se fecha com -`exact`. - -```lean (name := c2check24) -#check And.intro -``` - -```leanOutput c2check24 -And.intro {a b : Prop} (left : a) (right : b) : a ∧ b -``` - -```lean -example (P Q : Prop) (hP : P) (hQ : Q) : P ∧ Q := by - solution! - apply And.intro - · exact hP - · exact hQ -``` - -:::: - -::::exercise (rating := 2) (name := "and-comm") - -Prove que a conjunção comuta. Fonte: {citep Bib.FAA2025}[]. Dica: um `↔` se parte em dois objetivos com -`constructor`; em cada um, `intro h` seguido de `obtain ⟨_,_⟩ := h` desmonta -a conjunção da hipótese, e `constructor` reconstrói a conjunção invertida. - -Veja também o que acontece ao avaliar `(10,20).1`. `And` em Lean é uma -`structure` com dois campos. - -```lean -example (P Q : Prop) : P ∧ Q ↔ Q ∧ P := by - solution! - constructor - · intro h - obtain ⟨h1, h2⟩ := h - apply And.intro - · exact h2 - · exact h1 - · intro h - constructor - · exact h.2 - · exact h.1 -``` - -:::: - -::::exercise (rating := 1) (name := "implication-transitivity") - -Fonte: {citep Bib.FAA2025}[]. Dica: `intro`, depois `apply` duas vezes, -encadeando as duas hipóteses. - -```lean -example (P Q R : Prop) (h : P → Q) (h2 : Q → R) : - P → R := by - solution! - intro hp - apply h2 - apply h - exact hp -``` - -:::: - -::::exercise (rating := 1) (name := "apply-several-premises") - -Adaptado de {citep Bib.FAA2025}[]. - -```lean -example (P Q R S : Prop) (h0 : P ∧ Q ∧ R) - (h : P → Q → R → S) : S := by - solution! - apply h - · exact h0.1 - · exact h0.2.1 - · exact h0.2.2 -``` - -:::: - -Nem toda prova precisa de lógica proposicional abstrata — às vezes o que -falta é desdobrar uma definição local antes de concluir. - -::::exercise (rating := 1) (name := "unfold-direct-proof") - -Fonte: {citep Bib.FAA2025}[], com `f` definida localmente igual ao arquivo. -Dica: `intro h`, `unfold f at h` (ou `rw [f] at h`), depois concluir por -`omega` ou `assumption`. - -```lean -def f₁ (x y : Nat) : Prop := x = y - -example (x : Nat) : f₁ x 1 → x ≠ 2 := by - solution! - intro h - unfold f₁ at h - omega -``` - -:::: - -::::exercise (rating := 1) (name := "unfold-conjunction") - -Fonte: {citep Bib.FAA2025}[]. - -```lean -example (x y : Nat) : f₁ 0 x ∧ f₁ 0 y → x = y := by - solution! - intro h - obtain ⟨h1, h2⟩ := h - unfold f₁ at h1 h2 - omega -``` - -:::: - -::::exercise (rating := 1) (name := "exists-witness") - -Prove que `∃ n : Nat, n + n = 10`, exibindo a testemunha com `⟨_, _⟩` ou -usando `Exists.intro`. - -```lean (name := c2check25) -#check Exists.intro -``` - -```leanOutput c2check25 -Exists.intro.{u} {α : Sort u} {p : α → Prop} (w : α) (h : p w) : Exists p -``` - -```lean -example : ∃ n : Nat, n + n = 10 := by - solution! - apply Exists.intro 5 - rfl -``` - -:::: - -::::exercise (rating := 1) (name := "cases-on-or") - -Prove que `P ∨ Q → Q ∨ P`, usando `cases` sobre a hipótese, complete a -prova. - -```lean (name := c2check26) -#check Or.intro_left -``` - -```leanOutput c2check26 -Or.intro_left {a : Prop} (b : Prop) (h : a) : a ∨ b -``` - -```lean (name := c2check27) -#check Or.intro_right -``` - -```leanOutput c2check27 -Or.intro_right {b : Prop} (a : Prop) (h : b) : a ∨ b -``` - -```lean -example (P Q : Prop) : P ∨ Q → Q ∨ P := by - intro h - cases h with - | inl hp => - solution! - exact Or.inr hp - | inr hq => - solution! - exact Or.inl hq -``` - -:::: - # Tipos indutivos Tipos indutivos vêm antes da recursão porque, em Lean, uma função @@ -721,31 +408,6 @@ Nat.zero)`. example : 2 = Nat.succ (Nat.succ Nat.zero) := rfl ``` -# Prova por indução - -A última tática da tabela, `induction`, prova algo para todo valor de um -tipo indutivo, e não para um valor de cada vez. - -::::exercise (rating := 1) (name := "add-zero-induction") - -Prove que `n + 0 = n` para todo `n`, usando `induction n`. No caso `0`, -`rfl` fecha; no caso `n + 1`, a hipótese de indução (`ih`) resolve `omega`. - -```lean -example (n : Nat) : n + 0 = n := by - solution! - induction n with - | zero => rfl - | succ a ih => - -- try `apply?` - omega -``` - -:::: - -Quem quiser praticar Lean provas em Lean, pode jogar o [Natural Number -Game](https://adam.math.hhu.de/#/g/leanprover-community/nng4/). - # Recursão Uma definição recursiva precisa de duas coisas: ter caso base, e chegar diff --git a/CSwL/Logic.lean b/CSwL/Logic.lean index 036a471..390f83e 100644 --- a/CSwL/Logic.lean +++ b/CSwL/Logic.lean @@ -1,5 +1,6 @@ import CSwLMeta import Bib +import CSwL.Logic.Proof import CSwL.Logic.PL import CSwL.Logic.FOL import CSwLCompat @@ -8,8 +9,8 @@ import VersoManual open Verso Genre Manual --- Capítulo "cola": reúne, via `{include 1 ...}`, lógica proposicional e --- lógica de predicados — a ferramenta básica com que os fragmentos de inglês +-- Capítulo "cola": reúne, via `{include 1 ...}`, a prova em Lean e as duas +-- lógicas — a ferramenta básica com que os fragmentos de inglês -- representarão significado. #doc (Manual) "Lógica" => %%% @@ -18,9 +19,13 @@ htmlSplit := .never file := "Logic" %%% -Como preparação para a semântica de fragmentos de inglês, introduzimos a -lógica proposicional e a lógica de predicados, e mostramos como -implementar sua sintaxe em Lean. +O capítulo tem três seções. A primeira é sobre provar em Lean: o tipo `Prop`, +o que conta como prova, e as táticas que constroem uma. As outras duas +implementam a lógica proposicional e a de predicados como tipos de dados, +cada uma com sua sintaxe, sua semântica computável, e a ponte entre a fórmula +como dado e a proposição que ela afirma. + +{include 1 CSwL.Logic.Proof} {include 1 CSwL.Logic.PL} diff --git a/CSwL/Logic/FOL.lean b/CSwL/Logic/FOL.lean index 636d7c4..347036e 100644 --- a/CSwL/Logic/FOL.lean +++ b/CSwL/Logic/FOL.lean @@ -41,89 +41,6 @@ F ::= atom Em Lean, o mesmo tipo `Prop` em Lean pode ser usado na representação de fórmulas de primeira ordem. Também veremos como as fórmulas podem ser manipuladas como dados. -# As regras dos quantificadores em Lean - -O Lean se baseia em na teoria dos tipos, na qual se assume que cada variável pertence a algum tipo. Você pode pensar em um tipo como um "universo" ou um "domínio de discurso", no sentido da lógica de primeira ordem. - -Seguindo a apresentação de Lógica Proposicional, quatro novas regras precisam ser explicadas, duas para cada quantificador. - -```lean -section - -variable (U : Type) -variable (P Q : U → Prop) -``` - -A introdução de `∀` diz que para provar que algo vale de todo `x`, tome um `x` -arbitrário e prove que vale para ele. É a mesma `intro` agora sobre um objeto em vez de uma hipótese. A eliminação de `∀` é aplicação: de `∀ x P x` e de um objeto `d`, sai `P d`. - -```lean -example (h : ∀ x, P x) : ∀ y, P y := by - intro n - exact h n -``` - -A introdução de `∃` exige exibir a testemunha. A tática `use` substitui a variável quantificada pelo objeto passado, e deixa como objetivo o que falta provar sobre ele. - -```lean -example (y : U) (h : P y) : ∃ x, P x := - Exists.intro y h - -example (y : U) (h : P y) : ∃ x, P x := by - use y -``` - -A eliminação de `∃` é a mais delicada. De `∃ x P x` sabe-se que há uma testemunha, mas não sabemos qual elemento do domínio ela é. A tática `obtain` aplica o teorema `Exists.elim`, introduz com um nome, junto com a propriedade que ele satisfaz. - -```lean -example (h : ∃ x, P x ∧ Q x) : ∃ x, Q x := by - apply Exists.elim h - intro d hd - use d - exact hd.2 - -example (h : ∃ x, P x ∧ Q x) : ∃ x, Q x := by - obtain ⟨d, hP, hQ⟩ := h - exact ⟨d, hQ⟩ -``` - -Podemos ainda considera uma lógica de múltiplos tipos, onde podemos ter múltiplos universos. Por exemplo, podemos querer usar a lógica de primeira ordem para geometria, com quantificadores sobre pontos e linhas. Mas acima restringimos os predicados a um único universo `U`. - -A demonstração abaixo não é válida se não declararmos uma variável `u : U`, mesmo que `u` não apareça no enunciado do teorema. Isso destaca uma diferença entre a lógica de primeira ordem e a lógica implementada em Lean. Na dedução natural, podemos provar `∀ x P x → ∃ x P x`, o que mostra que nosso sistema de prova assume implicitamente que o universo tem pelo menos um objeto. Em contraste, a afirmação `(∀ x : U, P x) → ∃ x : U, P x` não é demonstrável em Lean. Em outras palavras, em Lean, é possível que um tipo esteja vazio, e, portanto, a prova acima requer uma suposição explícita de que existe um elemento `u : U`. - -```lean -variable (u : U) - -example: (∀ x , P x) → ∃ x, P x := by - intro h - use u - exact h u - -end -``` - -::::exercise (rating := 2) (name := "forall-exists-swap") - -Prove o primeiro exemplo. - -```lean -example {U : Type} (R : U → U → Prop) : - (∃ y, ∀ x, R x y) → (∀ x, ∃ y, R x y) := - solution!(by - intro h - obtain ⟨d, hd⟩ := h - intro x - exact ⟨d, hd x⟩) -``` - -Explique porque a volta da implicação não vale. - -:::solution -A volta não vale. De `∀x ∃y Rxy` cada `x` pode ter a sua testemunha, e nada obriga que seja a mesma para todos. -::: - -:::: - # Ligação de variáveis Numa fórmula `∀x F` (ou `∃x F`), o quantificador liga toda ocorrência de diff --git a/CSwL/Logic/PL.lean b/CSwL/Logic/PL.lean index 3664984..e96cf64 100644 --- a/CSwL/Logic/PL.lean +++ b/CSwL/Logic/PL.lean @@ -88,291 +88,16 @@ Finalmente, precisamos de um método para definir se a fórmula `α` é *consequ No problema dos vestidos, o número de personagens e atributos é finito, portanto há apenas um número finito de possíveis proposições. Os números também são pequenos o suficiente para que análise sistemática de todas as combinações de valores verdade seja viável na prática. Para demonstrar que todo número par maior que dois pode ser escrito como uma soma de dois números primos esta estratégia não seria válida. -# Lógica Proposicional em Lean - -O Lean possui `Prop`, como tipo predefinido, cujos elementos são proposições. Os conectivos lógicos `∧`, `∨`, `→`, `↔` e `¬` estão disponíveis diretamente no Lean, de modo que uma fórmula proposicional pode ser representada como uma proposição em Lean. Isso nos fornece uma ponte conveniente entre a semântica da linguagem natural e o raciocínio formal. Podemos traduzir o conteúdo semântico de uma sentença para uma proposição em Lean e, em seguida, usar Lean para verificar se uma conclusão decorre de um conjunto de hipóteses. - -Continuando a partir do quiz anterior. Para começar, vamos introduzir variáveis do tipo `Prop`, cada uma delas representado uma proposição. São 3 pessoas e 3 cores. Vamos representar "Ana veste azul" por `Aa` e assim por diante. - -```lean -variable ( - Aa Ab Ap - Ma Mb Mp - Ca Cb Cp : Prop) -``` -A ideia é que as condições do problema sejam traduzidas em fórmulas proposicionais. Por exemplo, podemos formalizar a sentença "Ana veste azul, branco ou preto" com a fórmula em LP. - -```lean -#check Aa ∨ Ab ∨ Ap -``` - -Aqui cabe a observação de que a formalização em LP não foi obtida diretamente a partir da construção linguística original, uma oração coordenando seus constituintes no predicado. Intuitivamente, a sentença foi antes interpretada como três orações coordenadas (proposições completas), "Ana veste azul ou Ana veste branco ou Ana veste preto". - -A formalização completa do problema deve levar em consideração não apenas o que foi dito explicitamente mas algumas condições implicitamente assumidas. Definimos a estrutura `Premissas` por conveniência, ao invés de uma variável por premissa. - -```lean -structure Premissas : Prop where - -- cada pessoa veste algum vestido - hA : Aa ∨ Ab ∨ Ap - hM : Ma ∨ Mb ∨ Mp - hC : Ca ∨ Cb ∨ Cp - - -- cada vestido é de alguma pessoa - ha : Ma ∨ Aa ∨ Ca - hb : Ab ∨ Mb ∨ Cb - hp : Ap ∨ Mp ∨ Cp - - -- uma pessoa veste apenas um vestido - hA1 : (Aa → ¬ Ab ∧ ¬ Ap) ∧ (Ab → ¬ Aa ∧ ¬ Ap) ∧ (Ap → ¬ Aa ∧ ¬ Ab) - hM1 : (Ma → ¬ Mb ∧ ¬ Mp) ∧ (Mb → ¬ Ma ∧ ¬ Mp) ∧ (Mp → ¬ Ma ∧ ¬ Mb) - hC1 : (Ca → ¬ Cb ∧ ¬ Cp) ∧ (Cb → ¬ Ca ∧ ¬ Cp) ∧ (Cp → ¬ Ca ∧ ¬ Cb) - - -- cada vestido é de apenas uma pessoa - ha1 : (Ma → ¬ Aa ∧ ¬ Ca) ∧ (Ca → ¬ Aa ∧ ¬ Ma) ∧ (Aa → ¬ Ma ∧ ¬ Ca) - hb1 : (Mb → ¬ Ab ∧ ¬ Cb) ∧ (Cb → ¬ Ab ∧ ¬ Mb) ∧ (Ab → ¬ Mb ∧ ¬ Cb) - hp1 : (Mp → ¬ Ap ∧ ¬ Cp) ∧ (Cp → ¬ Ap ∧ ¬ Mp) ∧ (Ap → ¬ Mp ∧ ¬ Cp) - - -- resposta 1 - h1 : Aa → Ab - h2 : Ca → ¬ Ab - - -- resposta 2 - h3 : ¬ Ab - - -- resposta 3 - h4 : Ap → Cb - h5 : Cp → ¬ Cb -``` - -Podemos então enunciar o problema do quiz na forma do teorema abaixo. Neste caso, - -```lean -theorem vestidos (h : Premissas Aa Ab Ap Ma Mb Mp Ca Cb Cp) - : Ap ∧ Cb ∧ Ma := sorry -``` - -Consultar o tipo deste teorema com `#check vestidos` nos revela que ele tem o formato de uma implicação, que pode ser lido como `Γ ⊢ α` Do conjunto `Γ` de premissas em `Premissas` posso *derivar* `Ap ∧ Cb ∧ Ma`. A leitura é sintática. Podemos construir a prova de `α` a partir da aplicação de regras de dedução a partir das fórmulas de `Γ`. - -Chamamos "sistema dedutivo" um conjunto das regras de dedução. Existem vários sistemas dedutivos. A formalização de Prop em Lean corresponde a implementação do sistema chamado *dedução natural* definido por Gerhard Gentzen em 1930s. - -Neste sistema dedutivo, cada conectivo vem com dois tipos de regra: as de *introdução*, que dizem como construir uma prova cuja conclusão usa o conectivo, e as de *eliminação*, que dizem como usar uma prova cuja hipótese o usa. - -```lean -variable {P Q R : Prop} -``` - -A regra de introdução de `→` diz que para provar `P → Q`, supomos `P` e derivamos `Q`. A tatica `intro` move o antecedente para as hipóteses. A regra de eliminação é a chamada regra *modus ponens*. De `P → Q` e de `P`, conclua `Q`. Em Lean isso é aplicação `h hP` já é a prova de `Q`. A tática `apply` faz o mesmo de trás para frente, ela transforma o objetivo `Q` no objetivo `P`. - - -```lean -example : P → (Q → P) := by - intro hP hQ - exact hP - -example (h₁ : P → Q) (h₂ : Q → R) : P → R := by - intro hP - apply h₂ - apply h₁ - exact hP - -example (h : P → Q) (hP : P) : Q := h hP -``` - -Para a conjunção. Provar `P ∧ Q` depende de uma prova de `P` e `Q`. A tática `constructor` parte o objetivo em dois; o construtor anônimo `⟨_, _⟩` faz o mesmo em forma de termo. A eliminação de `∧` em `P ∧ Q` significa que podemos concluir `P` ou `Q`. São duas regras, e em Lean são as projeções `.1` (ou `.left`) e `.2` (ou `.right`). A tática `obtain` desmonta a hipótese de uma vez, dando nome às duas partes. - - -```lean -example (hP : P) (hQ : Q) : P ∧ Q := by - constructor - · exact hP - · exact hQ - -example (hP : P) (hQ : Q) : P ∧ Q := ⟨hP, hQ⟩ -example (hP : P) (hQ : Q) : P ∧ Q := And.intro hP hQ - -example (h : P ∧ Q) : Q ∧ P := by - obtain ⟨hP, hQ⟩ := h - exact ⟨hQ, hP⟩ - -example (h : P ∧ Q) : Q ∧ P := ⟨h.2, h.1⟩ -``` - -Para provar `P ∨ Q` basta provar um dos dois lados. São duas regras, e as táticas `left` e `right` escolhem qual. A eliminação de `∨` é a prova por casos. De `P ∨ Q` não se sabe qual dos dois vale. Para concluir `R` a partir dela é preciso concluir `R` nos dois casos. A tática `cases` abre exatamente esses dois objetivos. - - -```lean -example (hP : P) : P ∨ Q := by - left - exact hP - -example (h : P ∨ Q) : Q ∨ P := by - cases h with - | inl hP => right; exact hP - | inr hQ => left; exact hQ -``` - -Não há um conectivo primitivo para a negação: `¬ P` é notação para `P → False` onde `False` é a proposição sem nenhuma prova. A introdução de `¬` é a introdução de `→`, para provar `¬P`, suponha `P` e derive `False`. A eliminação é a eliminação de `→`. A regra que a tradição chama de *ex falso quodlibet* (princípio da explosão), é uma regra que dita que, a partir de uma contradição ou de uma premissa falsa, qualquer conclusão pode ser deduzida. `False.elim` em Lean. As duas juntas são `absurd`. - - -```lean -example (h : P → Q) : ¬Q → ¬P := by - intro hnQ hP - exact hnQ (h hP) - -example (hP : P) (hn : ¬P) : False := hn hP -example (h : False) : P := False.elim h -example (hP : P) (hn : ¬P) : Q := absurd hP hn -``` - -A `P ↔ Q` é a conjunção das duas implicações, e as regras seguem disso. A tática `constructor` parte o objetivo nas duas direções, e `.mp` e `.mpr` são as eliminações de `P → Q` e de `Q → P`. - -```lean -example : P ∧ Q ↔ Q ∧ P := by - constructor - · intro h; exact ⟨h.2, h.1⟩ - · intro h; exact ⟨h.2, h.1⟩ - -example (h : P ↔ Q) (hP : P) : Q := h.mp hP -``` - -Até aqui não usamos em nenhum momento "ou `P` vale ou não vale". Todas as regras até aqui são *construtivas*, uma prova de `P ∨ Q` traz consigo qual dos dois lados foi usado. Uma prova de `P` é uma construção de `P`. O raciocínio *clássico* acrescenta o princípio chamado de terceiro excluído. Dele saem as duas táticas. A primeira é `by_cases`, que parte a prova em dois casos, supondo `P` num e `¬P` no outro. E a tatica `by_contra` prova `P` supondo `¬P` e derivando `False`, a redução ao absurdo. - -```lean -example : P ∨ ¬P := Classical.em P - -example : ¬¬P → P := by - intro h - by_cases hP : P - · exact hP - · exact absurd hP h - -example (h : ¬¬P) : P := by - by_contra hn - exact h hn -``` - -::::exercise (rating := 1) (name := "contrapositive") - -Prove a contrapositiva. Só uma das direções precisa de raciocínio clássico. - -```lean -example : (P → Q) ↔ (¬Q → ¬P) := solution!(by - constructor - · intro h hnQ hP - exact hnQ (h hP) - · intro h hP - by_contra hnQ - exact h hnQ hP) -``` - -:::: - -::::exercise (rating := 2) (name := "de-morgan") - -Uma das leis de De Morgan vale construtivamente; a outra precisa do terceiro -excluído. - -```lean -example : ¬(P ∨ Q) ↔ (¬P ∧ ¬Q) := solution!(by - constructor - · intro h - exact ⟨fun hP => h (Or.inl hP), fun hQ => h (Or.inr hQ)⟩ - · intro h hor - cases hor with - | inl hP => exact h.1 hP - | inr hQ => exact h.2 hQ) - -example : ¬(P ∧ Q) ↔ (¬P ∨ ¬Q) := solution!(by - constructor - · intro h - by_cases hP : P - · right; intro hQ; exact h ⟨hP, hQ⟩ - · left; exact hP - · intro h hand - cases h with - | inl hnP => exact hnP hand.1 - | inr hnQ => exact hnQ hand.2) -``` - -:::: - -::::exercise (rating := 1) (name := "exchange-prop") -Complete a representação do argumento abaixo em linguagem lógica. +# Sintaxe -> Se o câmbio cair, temos inflação. Se as exportações crescerem, diminuímos o déficit. O câmbio cai ou diminuímos o déficit. Logo, temos inflação ou as exportações crescem. +Em Lean, `Prop` é um tipo e proposições particulares também são tipos. A variável `h` abaixo pode ser entendida como um identificador para uma "prova qualquer" da proposição `p ∨ q`. ```lean section +variable (p q : Prop) -variable ( - p -- o câmbio cai - q -- temos inflação - r -- as exportações crescem - s -- Diminuimos o déficit - : Prop) - -def exchange : Prop := - solution!( - ((p → q) ∧ (r → s) ∧ (p ∨ s)) → (q ∨ r) - ) -end -``` -:::: - -::::exercise (rating := 2) (name := "dresses") -Complete a prova do teorema que responde o quiz anterior. - -```lean -theorem vestidos₁ (h : Premissas Aa Ab Ap Ma Mb Mp Ca Cb Cp) - : Ap ∧ Cb ∧ Ma := by - obtain - ⟨hA, hM, hC, ha, hb, hp, hA1, hM1, - hC1, ha1, hb1, hp1, h1, h2, h3, h4, h5⟩ := h - - -- Ana não está de azul: se estivesse, por `h1` ela estaria de branco, mas Ana - -- não está de branco por `h3`. - have hnAa : ¬ Aa := by - solution!( - intro hAa - exact h3 (h1 hAa) - ) - - have hAp : Ap := by - cases hA with - | inl hAa => exact absurd hAa hnAa - | inr hx => - cases hx with - | inl hAb => exact absurd hAb h3 - | inr hAp => exact hAp - - have hCb : Cb := solution!( - h4 hAp - ) - - have hnCa : ¬ Ca := solution!( - (hC1.2.1 hCb).1 - ) - - have hMa : Ma := by - solution!( - rcases ha with hMa | hAa | hCa - · exact hMa - · exact absurd hAa hnAa - · exact absurd hCa hnCa - ) - - exact ⟨hAp, hCb, hMa⟩ -``` - -:::: - -# Sintaxe - -Em Lean, `Prop` é um tipo e proposições particulares também são tipos. A variável `h` abaixo pode ser entendida como um identificador para uma "prova qualquer" da proposição `Aa ∨ Ab ∨ Ap`. - -```lean -#check Aa ∨ Ab ∨ Ap -variable (h : Aa ∨ Ab ∨ Ap) +#check p ∨ q +variable (h : p ∨ q) ``` Mas Lean adota o princípio da "irrelevância da prova", ou seja, Lean não distingue diferentes provas de uma proposição. Como consequência, o tipo `Prop` não é computável, não é um "dado" que pode ser manipulado. Por exemplo, não conseguimos extrair os componentes de uma conjunção `a ∧ b`, para fora do tipo `Prop`. Lean proíbe a extração de `Prop` para `Type`, ele sabe que todas as provas de `a ∧ b` são irrelevantes e iguais, então ele não permite que você use uma prova para tomar decisões no mundo dos dados programáveis (`Type`). @@ -385,6 +110,10 @@ def cannotExtractLeft (h : a ∧ b) : Type := | And.intro ha hb => ha ``` +```lean +end +``` + Como vamos precisar manipular fórmulas lógicas, teremos que definir um tipo de dado para representar fórmulas proposicionais. Formalmente, a sintaxe da LP é definida pela BNF abaixo. As variáveis proposicionais (ou símbolos sentenciais) são os `atom`. O uso do sufixo `'` no não-terminal `atom` é uma forma conveniente de expressar que podemos gerar quantos átomos forem necessários. diff --git a/CSwL/Logic/Proof.lean b/CSwL/Logic/Proof.lean new file mode 100644 index 0000000..dc14f8c --- /dev/null +++ b/CSwL/Logic/Proof.lean @@ -0,0 +1,748 @@ +import CSwLMeta +import Bib +import Mathlib.Tactic +import CSwLCompat +import CSwL.IntroL + +open Verso.Genre Manual +open CSwLMeta + +set_option verso.code.warnLineLength 100 + +#doc (Manual) "Prova em Lean" => +%%% +tag := "Proof" +%%% + +Lógica, aqui, é duas coisas ao mesmo tempo. É o assunto — proposições, +conectivos, quantificadores, o que se segue de quê — e é a ferramenta com que +se escreve e se confere qualquer afirmação neste livro. Esta seção trata da +ferramenta: o tipo `Prop`, o que conta como prova em Lean, e as táticas com +que se constrói uma. As seções seguintes tratam do assunto, e o fazem +implementando cada lógica como um tipo de dado. + +Supomos conhecida a lógica proposicional e a de predicados — sintaxe, +semântica, e a noção de consequência. Para uma apresentação a partir do +início, ver {citep Bib.logicandproof}[]. + +```lean +namespace Proof + +-- `square₁` and `square₂` are the two definitions of squaring from the Lean +-- chapter; the examples below reuse them rather than introducing new ones. +open IntroL +``` + +# O tipo Prop e Provas + +O que diferencia Lean de outras linguagens como Python e Java é a capacidade de na mesma linguagem que usamos para 'programar' funções, escrevermos 'provas' sobre estas funções. + +Nesta 'Exemplos extraídos de {citep Bib.FAA2025}[]. Uma proposição é um enunciado que pode ser verdadeiro ou falso. O enunciado `1 = 1` é verdadeiro, enquanto `square₁ 12 = 2` é falso. Toda proposição é todo tipo `Prop`. + +```lean +#check square₁ 12 = 2 +``` + +Podemos declarar proposições como a seguir e verificar que `1 = 1 : Prop`, mas não podemos _avaliar_ uma proposição. + +```lean +def p1 : Prop := 1 = 1 + +#check p1 +``` + +Toda proposição verdadeira tem uma prova, e uma prova é um _termo_ do tipo da proposição que testemunha a verdade da proposição. Provar `1 = 1` é exibir um termo de tipo `1 = 1`, exatamente o que o termo `Eq.refl 1` faz abaixo. Declarar um teorema é muito parecido com declarar uma função. + +```lean +theorem OneEqSelf : 1 = 1 := Eq.refl 1 +``` + +A mesma ideia vale para dizer que duas funções são a mesma coisa — não é +analogia, é a proposição `f = g`, provável do mesmo jeito. Agora usando o +modo `tactic` iniciado com `by`. Usamos as taticas `rfl` e `intro` que iremos explicar a seguir. Com `example` não precisamos dar nomes a teoremas que não serão reusados. + +```lean +example : + ∀ (z : Nat), (λ x ↦ x * x) z = (fun y => y * y) z := by + intro n + rfl +``` + +Note que perguntar pelo tipo não é o mesmo que decidir se ela é verdadeira: + +```lean +#check (square₁ = square₂) +``` + +Provar é dar um termo cujo tipo é a proposição. Para uma igualdade em que +os dois lados reduzem ao mesmo valor, o termo é `rfl` — de _reflexividade_, +que é o princípio de que tudo é igual a si mesmo. Ver {citep Bib.love2026}[] +para uma explicação sobre `rfl`. + +```lean +theorem square₁_eq_square₂ : square₁ = square₂ := by + rfl +``` + +Escrito com `by`, `rfl` é uma _tática_: uma instrução para construir a +prova. Você pode inspecionar a definição de Lean para `Eq.refl`. + +```lean (name := c2print1) +#print square₁_eq_square₂ +``` + +```leanOutput c2print1 +theorem Proof.square₁_eq_square₂ : square₁ = square₂ := +Eq.refl square₁ +``` + +Além de `rfl`, um pequeno repertório de táticas resolve o que os capítulos +seguintes precisam — conferido nos próprios arquivos, não escolhido a +priori. A +ordem abaixo é a de {citep Bib.FAA2025}[], que apresenta as táticas nesta +sequência; `decide`, `omega`, `obtain`, `cases`, `simp` e `induction` não +vêm de lá (o curso os introduz onde a necessidade aparece) e ficam ao +final, fora da ordem do FAA2025: + +``` +rfl fecha a = b quando os dois lados calculam o mesmo valor +exact e fornece o termo que é a prova +intro h introduz uma hipótese, para provar uma implicação ou ∀ +constructor parte um ∧ ou um ↔ em dois objetivos +apply h aplica uma implicação ou lema, deixando a(s) premissa(s) + como novo(s) objetivo(s) +unfold nome desdobra uma definição, antes de continuar +rw [h] reescreve o objetivo usando a igualdade h, da esquerda para + a direita +assumption fecha o objetivo com uma hipótese já disponível +decide fecha um objetivo decidível calculando a resposta +omega resolve aritmética linear em Nat e Int +obtain ⟨_,_⟩ := h desmonta uma hipótese composta (conjunção, existencial) +cases h dado h : P ∨ Q, parte a prova em dois casos +simp [...] reescreve com um conjunto de lemas até não haver mais o que + simplificar +induction x prova por casos sobre a forma como x foi construído +funext x duas funções são iguais quando concordam em todo ponto +``` + +Duas notações de prova não são táticas: `⟨t, h⟩` monta um par (para provar +uma conjunção ou exibir a testemunha de um existencial), e `h.1`/`h.2` +desmontam um par que está numa hipótese. + +::::exercise (rating := 1) (name := "rfl-arithmetic") + +Termine a prova usando `rfl`. + +```lean +example : 7 * 6 = 42 := + solution!(rfl) +``` + +:::: + +::::exercise (rating := 1) (name := "square-unfold") + +Prove que `square₁ n = n * n`; uma variável aparece, então `rfl` não basta +sozinho — é preciso desdobrar a definição antes. + +```lean +example (n : Nat) : square₁ n = n * n := by + solution! + unfold square₁ + rfl +``` + +:::: + +::::exercise (rating := 1) (name := "identity-implication") + +Provar `P → Q` é: suponha `P`, derive `Q`. Prove `P → P`. Fonte: +{citep Bib.FAA2025}[] + +```lean +example (P : Prop) : P → P := by + solution! + intro h + exact h +``` + +:::: + +::::exercise (rating := 1) (name := "p-implies-q-implies-p") + +Complete a prova abaixo. Fonte: {citep Bib.FAA2025}[] + +```lean +example (P Q : Prop) : P → (Q → P) := by + solution! + intro h _ + exact h +``` + +:::: + +::::exercise (rating := 1) (name := "and-intro") + +Prove `P ∧ Q` a partir de `P` e de `Q`. Fonte: {citep Bib.FAA2025}[]. Dica: +`constructor` parte o objetivo `P ∧ Q` em dois; cada um se fecha com +`exact`. + +```lean (name := c2check24) +#check And.intro +``` + +```leanOutput c2check24 +And.intro {a b : Prop} (left : a) (right : b) : a ∧ b +``` + +```lean +example (P Q : Prop) (hP : P) (hQ : Q) : P ∧ Q := by + solution! + apply And.intro + · exact hP + · exact hQ +``` + +:::: + +::::exercise (rating := 2) (name := "and-comm") + +Prove que a conjunção comuta. Fonte: {citep Bib.FAA2025}[]. Dica: um `↔` se parte em dois objetivos com +`constructor`; em cada um, `intro h` seguido de `obtain ⟨_,_⟩ := h` desmonta +a conjunção da hipótese, e `constructor` reconstrói a conjunção invertida. + +Veja também o que acontece ao avaliar `(10,20).1`. `And` em Lean é uma +`structure` com dois campos. + +```lean +example (P Q : Prop) : P ∧ Q ↔ Q ∧ P := by + solution! + constructor + · intro h + obtain ⟨h1, h2⟩ := h + apply And.intro + · exact h2 + · exact h1 + · intro h + constructor + · exact h.2 + · exact h.1 +``` + +:::: + +::::exercise (rating := 1) (name := "implication-transitivity") + +Fonte: {citep Bib.FAA2025}[]. Dica: `intro`, depois `apply` duas vezes, +encadeando as duas hipóteses. + +```lean +example (P Q R : Prop) (h : P → Q) (h2 : Q → R) : + P → R := by + solution! + intro hp + apply h2 + apply h + exact hp +``` + +:::: + +::::exercise (rating := 1) (name := "apply-several-premises") + +Adaptado de {citep Bib.FAA2025}[]. + +```lean +example (P Q R S : Prop) (h0 : P ∧ Q ∧ R) + (h : P → Q → R → S) : S := by + solution! + apply h + · exact h0.1 + · exact h0.2.1 + · exact h0.2.2 +``` + +:::: + +Nem toda prova precisa de lógica proposicional abstrata — às vezes o que +falta é desdobrar uma definição local antes de concluir. + +::::exercise (rating := 1) (name := "unfold-direct-proof") + +Fonte: {citep Bib.FAA2025}[], com `f` definida localmente igual ao arquivo. +Dica: `intro h`, `unfold f at h` (ou `rw [f] at h`), depois concluir por +`omega` ou `assumption`. + +```lean +def f₁ (x y : Nat) : Prop := x = y + +example (x : Nat) : f₁ x 1 → x ≠ 2 := by + solution! + intro h + unfold f₁ at h + omega +``` + +:::: + +::::exercise (rating := 1) (name := "unfold-conjunction") + +Fonte: {citep Bib.FAA2025}[]. + +```lean +example (x y : Nat) : f₁ 0 x ∧ f₁ 0 y → x = y := by + solution! + intro h + obtain ⟨h1, h2⟩ := h + unfold f₁ at h1 h2 + omega +``` + +:::: + +::::exercise (rating := 1) (name := "exists-witness") + +Prove que `∃ n : Nat, n + n = 10`, exibindo a testemunha com `⟨_, _⟩` ou +usando `Exists.intro`. + +```lean (name := c2check25) +#check Exists.intro +``` + +```leanOutput c2check25 +Exists.intro.{u} {α : Sort u} {p : α → Prop} (w : α) (h : p w) : Exists p +``` + +```lean +example : ∃ n : Nat, n + n = 10 := by + solution! + apply Exists.intro 5 + rfl +``` + +:::: + +::::exercise (rating := 1) (name := "cases-on-or") + +Prove que `P ∨ Q → Q ∨ P`, usando `cases` sobre a hipótese, complete a +prova. + +```lean (name := c2check26) +#check Or.intro_left +``` + +```leanOutput c2check26 +Or.intro_left {a : Prop} (b : Prop) (h : a) : a ∨ b +``` + +```lean (name := c2check27) +#check Or.intro_right +``` + +```leanOutput c2check27 +Or.intro_right {b : Prop} (a : Prop) (h : b) : a ∨ b +``` + +```lean +example (P Q : Prop) : P ∨ Q → Q ∨ P := by + intro h + cases h with + | inl hp => + solution! + exact Or.inr hp + | inr hq => + solution! + exact Or.inl hq +``` + +:::: + +# Prova por indução + +A última tática da tabela, `induction`, prova algo para todo valor de um +tipo indutivo, e não para um valor de cada vez. + +::::exercise (rating := 1) (name := "add-zero-induction") + +Prove que `n + 0 = n` para todo `n`, usando `induction n`. No caso `0`, +`rfl` fecha; no caso `n + 1`, a hipótese de indução (`ih`) resolve `omega`. + +```lean +example (n : Nat) : n + 0 = n := by + solution! + induction n with + | zero => rfl + | succ a ih => + -- try `apply?` + omega +``` + +:::: + +Quem quiser praticar Lean provas em Lean, pode jogar o [Natural Number +Game](https://adam.math.hhu.de/#/g/leanprover-community/nng4/). + +# Lógica Proposicional em Lean + +O Lean possui `Prop`, como tipo predefinido, cujos elementos são proposições. Os conectivos lógicos `∧`, `∨`, `→`, `↔` e `¬` estão disponíveis diretamente no Lean, de modo que uma fórmula proposicional pode ser representada como uma proposição em Lean. Isso nos fornece uma ponte conveniente entre a semântica da linguagem natural e o raciocínio formal. Podemos traduzir o conteúdo semântico de uma sentença para uma proposição em Lean e, em seguida, usar Lean para verificar se uma conclusão decorre de um conjunto de hipóteses. + +Continuando a partir do quiz anterior. Para começar, vamos introduzir variáveis do tipo `Prop`, cada uma delas representado uma proposição. São 3 pessoas e 3 cores. Vamos representar "Ana veste azul" por `Aa` e assim por diante. + +```lean +variable ( + Aa Ab Ap + Ma Mb Mp + Ca Cb Cp : Prop) +``` +A ideia é que as condições do problema sejam traduzidas em fórmulas proposicionais. Por exemplo, podemos formalizar a sentença "Ana veste azul, branco ou preto" com a fórmula em LP. + +```lean +#check Aa ∨ Ab ∨ Ap +``` + +Aqui cabe a observação de que a formalização em LP não foi obtida diretamente a partir da construção linguística original, uma oração coordenando seus constituintes no predicado. Intuitivamente, a sentença foi antes interpretada como três orações coordenadas (proposições completas), "Ana veste azul ou Ana veste branco ou Ana veste preto". + +A formalização completa do problema deve levar em consideração não apenas o que foi dito explicitamente mas algumas condições implicitamente assumidas. Definimos a estrutura `Premissas` por conveniência, ao invés de uma variável por premissa. + +```lean +structure Premissas : Prop where + -- cada pessoa veste algum vestido + hA : Aa ∨ Ab ∨ Ap + hM : Ma ∨ Mb ∨ Mp + hC : Ca ∨ Cb ∨ Cp + + -- cada vestido é de alguma pessoa + ha : Ma ∨ Aa ∨ Ca + hb : Ab ∨ Mb ∨ Cb + hp : Ap ∨ Mp ∨ Cp + + -- uma pessoa veste apenas um vestido + hA1 : (Aa → ¬ Ab ∧ ¬ Ap) ∧ (Ab → ¬ Aa ∧ ¬ Ap) ∧ (Ap → ¬ Aa ∧ ¬ Ab) + hM1 : (Ma → ¬ Mb ∧ ¬ Mp) ∧ (Mb → ¬ Ma ∧ ¬ Mp) ∧ (Mp → ¬ Ma ∧ ¬ Mb) + hC1 : (Ca → ¬ Cb ∧ ¬ Cp) ∧ (Cb → ¬ Ca ∧ ¬ Cp) ∧ (Cp → ¬ Ca ∧ ¬ Cb) + + -- cada vestido é de apenas uma pessoa + ha1 : (Ma → ¬ Aa ∧ ¬ Ca) ∧ (Ca → ¬ Aa ∧ ¬ Ma) ∧ (Aa → ¬ Ma ∧ ¬ Ca) + hb1 : (Mb → ¬ Ab ∧ ¬ Cb) ∧ (Cb → ¬ Ab ∧ ¬ Mb) ∧ (Ab → ¬ Mb ∧ ¬ Cb) + hp1 : (Mp → ¬ Ap ∧ ¬ Cp) ∧ (Cp → ¬ Ap ∧ ¬ Mp) ∧ (Ap → ¬ Mp ∧ ¬ Cp) + + -- resposta 1 + h1 : Aa → Ab + h2 : Ca → ¬ Ab + + -- resposta 2 + h3 : ¬ Ab + + -- resposta 3 + h4 : Ap → Cb + h5 : Cp → ¬ Cb +``` + +Podemos então enunciar o problema do quiz na forma do teorema abaixo. Neste caso, + +```lean +theorem vestidos (h : Premissas Aa Ab Ap Ma Mb Mp Ca Cb Cp) + : Ap ∧ Cb ∧ Ma := sorry +``` + +Consultar o tipo deste teorema com `#check vestidos` nos revela que ele tem o formato de uma implicação, que pode ser lido como `Γ ⊢ α` Do conjunto `Γ` de premissas em `Premissas` posso *derivar* `Ap ∧ Cb ∧ Ma`. A leitura é sintática. Podemos construir a prova de `α` a partir da aplicação de regras de dedução a partir das fórmulas de `Γ`. + +Chamamos "sistema dedutivo" um conjunto das regras de dedução. Existem vários sistemas dedutivos. A formalização de Prop em Lean corresponde a implementação do sistema chamado *dedução natural* definido por Gerhard Gentzen em 1930s. + +Neste sistema dedutivo, cada conectivo vem com dois tipos de regra: as de *introdução*, que dizem como construir uma prova cuja conclusão usa o conectivo, e as de *eliminação*, que dizem como usar uma prova cuja hipótese o usa. + +```lean +variable {P Q R : Prop} +``` + +A regra de introdução de `→` diz que para provar `P → Q`, supomos `P` e derivamos `Q`. A tatica `intro` move o antecedente para as hipóteses. A regra de eliminação é a chamada regra *modus ponens*. De `P → Q` e de `P`, conclua `Q`. Em Lean isso é aplicação `h hP` já é a prova de `Q`. A tática `apply` faz o mesmo de trás para frente, ela transforma o objetivo `Q` no objetivo `P`. + + +```lean +example : P → (Q → P) := by + intro hP hQ + exact hP + +example (h₁ : P → Q) (h₂ : Q → R) : P → R := by + intro hP + apply h₂ + apply h₁ + exact hP + +example (h : P → Q) (hP : P) : Q := h hP +``` + +Para a conjunção. Provar `P ∧ Q` depende de uma prova de `P` e `Q`. A tática `constructor` parte o objetivo em dois; o construtor anônimo `⟨_, _⟩` faz o mesmo em forma de termo. A eliminação de `∧` em `P ∧ Q` significa que podemos concluir `P` ou `Q`. São duas regras, e em Lean são as projeções `.1` (ou `.left`) e `.2` (ou `.right`). A tática `obtain` desmonta a hipótese de uma vez, dando nome às duas partes. + + +```lean +example (hP : P) (hQ : Q) : P ∧ Q := by + constructor + · exact hP + · exact hQ + +example (hP : P) (hQ : Q) : P ∧ Q := ⟨hP, hQ⟩ +example (hP : P) (hQ : Q) : P ∧ Q := And.intro hP hQ + +example (h : P ∧ Q) : Q ∧ P := by + obtain ⟨hP, hQ⟩ := h + exact ⟨hQ, hP⟩ + +example (h : P ∧ Q) : Q ∧ P := ⟨h.2, h.1⟩ +``` + +Para provar `P ∨ Q` basta provar um dos dois lados. São duas regras, e as táticas `left` e `right` escolhem qual. A eliminação de `∨` é a prova por casos. De `P ∨ Q` não se sabe qual dos dois vale. Para concluir `R` a partir dela é preciso concluir `R` nos dois casos. A tática `cases` abre exatamente esses dois objetivos. + + +```lean +example (hP : P) : P ∨ Q := by + left + exact hP + +example (h : P ∨ Q) : Q ∨ P := by + cases h with + | inl hP => right; exact hP + | inr hQ => left; exact hQ +``` + +Não há um conectivo primitivo para a negação: `¬ P` é notação para `P → False` onde `False` é a proposição sem nenhuma prova. A introdução de `¬` é a introdução de `→`, para provar `¬P`, suponha `P` e derive `False`. A eliminação é a eliminação de `→`. A regra que a tradição chama de *ex falso quodlibet* (princípio da explosão), é uma regra que dita que, a partir de uma contradição ou de uma premissa falsa, qualquer conclusão pode ser deduzida. `False.elim` em Lean. As duas juntas são `absurd`. + + +```lean +example (h : P → Q) : ¬Q → ¬P := by + intro hnQ hP + exact hnQ (h hP) + +example (hP : P) (hn : ¬P) : False := hn hP +example (h : False) : P := False.elim h +example (hP : P) (hn : ¬P) : Q := absurd hP hn +``` + +A `P ↔ Q` é a conjunção das duas implicações, e as regras seguem disso. A tática `constructor` parte o objetivo nas duas direções, e `.mp` e `.mpr` são as eliminações de `P → Q` e de `Q → P`. + +```lean +example : P ∧ Q ↔ Q ∧ P := by + constructor + · intro h; exact ⟨h.2, h.1⟩ + · intro h; exact ⟨h.2, h.1⟩ + +example (h : P ↔ Q) (hP : P) : Q := h.mp hP +``` + +Até aqui não usamos em nenhum momento "ou `P` vale ou não vale". Todas as regras até aqui são *construtivas*, uma prova de `P ∨ Q` traz consigo qual dos dois lados foi usado. Uma prova de `P` é uma construção de `P`. O raciocínio *clássico* acrescenta o princípio chamado de terceiro excluído. Dele saem as duas táticas. A primeira é `by_cases`, que parte a prova em dois casos, supondo `P` num e `¬P` no outro. E a tatica `by_contra` prova `P` supondo `¬P` e derivando `False`, a redução ao absurdo. + +```lean +example : P ∨ ¬P := Classical.em P + +example : ¬¬P → P := by + intro h + by_cases hP : P + · exact hP + · exact absurd hP h + +example (h : ¬¬P) : P := by + by_contra hn + exact h hn +``` + +::::exercise (rating := 1) (name := "contrapositive") + +Prove a contrapositiva. Só uma das direções precisa de raciocínio clássico. + +```lean +example : (P → Q) ↔ (¬Q → ¬P) := solution!(by + constructor + · intro h hnQ hP + exact hnQ (h hP) + · intro h hP + by_contra hnQ + exact h hnQ hP) +``` + +:::: + +::::exercise (rating := 2) (name := "de-morgan") + +Uma das leis de De Morgan vale construtivamente; a outra precisa do terceiro +excluído. + +```lean +example : ¬(P ∨ Q) ↔ (¬P ∧ ¬Q) := solution!(by + constructor + · intro h + exact ⟨fun hP => h (Or.inl hP), fun hQ => h (Or.inr hQ)⟩ + · intro h hor + cases hor with + | inl hP => exact h.1 hP + | inr hQ => exact h.2 hQ) + +example : ¬(P ∧ Q) ↔ (¬P ∨ ¬Q) := solution!(by + constructor + · intro h + by_cases hP : P + · right; intro hQ; exact h ⟨hP, hQ⟩ + · left; exact hP + · intro h hand + cases h with + | inl hnP => exact hnP hand.1 + | inr hnQ => exact hnQ hand.2) +``` + +:::: + +::::exercise (rating := 1) (name := "exchange-prop") +Complete a representação do argumento abaixo em linguagem lógica. + +> Se o câmbio cair, temos inflação. Se as exportações crescerem, diminuímos o déficit. O câmbio cai ou diminuímos o déficit. Logo, temos inflação ou as exportações crescem. + +```lean +section + +variable ( + p -- o câmbio cai + q -- temos inflação + r -- as exportações crescem + s -- Diminuimos o déficit + : Prop) + +def exchange : Prop := + solution!( + ((p → q) ∧ (r → s) ∧ (p ∨ s)) → (q ∨ r) + ) +end +``` +:::: + +::::exercise (rating := 2) (name := "dresses") +Complete a prova do teorema que responde o quiz anterior. + +```lean +theorem vestidos₁ (h : Premissas Aa Ab Ap Ma Mb Mp Ca Cb Cp) + : Ap ∧ Cb ∧ Ma := by + obtain + ⟨hA, hM, hC, ha, hb, hp, hA1, hM1, + hC1, ha1, hb1, hp1, h1, h2, h3, h4, h5⟩ := h + + -- Ana não está de azul: se estivesse, por `h1` ela estaria de branco, mas Ana + -- não está de branco por `h3`. + have hnAa : ¬ Aa := by + solution!( + intro hAa + exact h3 (h1 hAa) + ) + + have hAp : Ap := by + cases hA with + | inl hAa => exact absurd hAa hnAa + | inr hx => + cases hx with + | inl hAb => exact absurd hAb h3 + | inr hAp => exact hAp + + have hCb : Cb := solution!( + h4 hAp + ) + + have hnCa : ¬ Ca := solution!( + (hC1.2.1 hCb).1 + ) + + have hMa : Ma := by + solution!( + rcases ha with hMa | hAa | hCa + · exact hMa + · exact absurd hAa hnAa + · exact absurd hCa hnCa + ) + + exact ⟨hAp, hCb, hMa⟩ +``` + +:::: + +# As regras dos quantificadores em Lean + +O Lean se baseia em na teoria dos tipos, na qual se assume que cada variável pertence a algum tipo. Você pode pensar em um tipo como um "universo" ou um "domínio de discurso", no sentido da lógica de primeira ordem. + +Seguindo a apresentação de Lógica Proposicional, quatro novas regras precisam ser explicadas, duas para cada quantificador. + +```lean +section + +variable (U : Type) +variable (P Q : U → Prop) +``` + +A introdução de `∀` diz que para provar que algo vale de todo `x`, tome um `x` +arbitrário e prove que vale para ele. É a mesma `intro` agora sobre um objeto em vez de uma hipótese. A eliminação de `∀` é aplicação: de `∀ x P x` e de um objeto `d`, sai `P d`. + +```lean +example (h : ∀ x, P x) : ∀ y, P y := by + intro n + exact h n +``` + +A introdução de `∃` exige exibir a testemunha. A tática `use` substitui a variável quantificada pelo objeto passado, e deixa como objetivo o que falta provar sobre ele. + +```lean +example (y : U) (h : P y) : ∃ x, P x := + Exists.intro y h + +example (y : U) (h : P y) : ∃ x, P x := by + use y +``` + +A eliminação de `∃` é a mais delicada. De `∃ x P x` sabe-se que há uma testemunha, mas não sabemos qual elemento do domínio ela é. A tática `obtain` aplica o teorema `Exists.elim`, introduz com um nome, junto com a propriedade que ele satisfaz. + +```lean +example (h : ∃ x, P x ∧ Q x) : ∃ x, Q x := by + apply Exists.elim h + intro d hd + use d + exact hd.2 + +example (h : ∃ x, P x ∧ Q x) : ∃ x, Q x := by + obtain ⟨d, hP, hQ⟩ := h + exact ⟨d, hQ⟩ +``` + +Podemos ainda considera uma lógica de múltiplos tipos, onde podemos ter múltiplos universos. Por exemplo, podemos querer usar a lógica de primeira ordem para geometria, com quantificadores sobre pontos e linhas. Mas acima restringimos os predicados a um único universo `U`. + +A demonstração abaixo não é válida se não declararmos uma variável `u : U`, mesmo que `u` não apareça no enunciado do teorema. Isso destaca uma diferença entre a lógica de primeira ordem e a lógica implementada em Lean. Na dedução natural, podemos provar `∀ x P x → ∃ x P x`, o que mostra que nosso sistema de prova assume implicitamente que o universo tem pelo menos um objeto. Em contraste, a afirmação `(∀ x : U, P x) → ∃ x : U, P x` não é demonstrável em Lean. Em outras palavras, em Lean, é possível que um tipo esteja vazio, e, portanto, a prova acima requer uma suposição explícita de que existe um elemento `u : U`. + +```lean +variable (u : U) + +example: (∀ x , P x) → ∃ x, P x := by + intro h + use u + exact h u + +end +``` + +::::exercise (rating := 2) (name := "forall-exists-swap") + +Prove o primeiro exemplo. + +```lean +example {U : Type} (R : U → U → Prop) : + (∃ y, ∀ x, R x y) → (∀ x, ∃ y, R x y) := + solution!(by + intro h + obtain ⟨d, hd⟩ := h + intro x + exact ⟨d, hd x⟩) +``` + +Explique porque a volta da implicação não vale. + +:::solution +A volta não vale. De `∀x ∃y Rxy` cada `x` pode ter a sua testemunha, e nada obriga que seja a mesma para todos. +::: + +:::: + +```lean +end Proof +``` diff --git a/CSwL/Sets.lean b/CSwL/Sets.lean index 110c3ea..257da7d 100644 --- a/CSwL/Sets.lean +++ b/CSwL/Sets.lean @@ -453,6 +453,16 @@ um domínio de duas entidades, e a relação de gostar entre elas. O domínio de entidades. Duas bastam para os exemplos deste capítulo. +:::dev +`Sets.Entity` and `FOL.Entity` are two different types with the same name. +Nothing breaks — each lives in its own namespace, and no file opens both — but +a reader who meets `Entity` twice with different constructors may take them for +one type. The collision was accepted deliberately: `Entity` is the right name +in both places, and renaming either to something like `Ent2` or `SetEntity` +would cost more in clarity than the ambiguity costs. If a later chapter ever +needs both in scope at once, that is when to revisit it. +::: + ```lean inductive Entity where | dorothy | toto diff --git a/STYLE-CODE.md b/STYLE-CODE.md index 584d634..4950aeb 100644 --- a/STYLE-CODE.md +++ b/STYLE-CODE.md @@ -35,10 +35,10 @@ never presented is a defect, not an entry. Rows known to be in that state are marked, and the open ones are listed under "Known gaps" below. **A feature can be presented outside a `lean` block**, so the table's first-use -column does not by itself settle whether the rule holds. `IntroL.lean` presents -fourteen tactics in a plain code fence — one line each, `rfl` through `funext` — -and a scan that reads only ```` ```lean ```` blocks misses it. Check the prose -before recording a gap. +column does not by itself settle whether the rule holds. `Logic/Proof.lean` +presents fourteen tactics in a plain code fence — one line each, `rfl` through +`funext` — and a scan that reads only ```` ```lean ```` blocks misses it. Check +the prose before recording a gap. A feature marked **(solution only)** first appears inside a `solution!(…)` block. Those rows are a distinct case: the feature is invisible in the @@ -51,9 +51,11 @@ this way is usually a mistake; see "Known gaps." | Chapter | Commands and declarations | Types and syntax | Tactics | | --- | --- | --- | --- | | `IntroCS` | `namespace`, `def` (by pattern matching), `inductive`, `deriving Repr`, `example`, `#eval` | `Nat`, function type `→`, dot-notation constructors (`.num`) | `rfl`, `induction … with`, `rw`, `rewrite`, `unfold`, `repeat` | -| `IntroL` | `#check`, `#print`, `theorem`, `structure`, `instance`, `section`, `variable` | `Type`, `Prop`, `Bool`, `List`, `Option`, `Char`, `String`, `fun`/`λ`, `match`, `if … then … else`, `⟨…⟩`, implicit `{}`, instance-implicit `[]`, `∘`, `BEq`, `∀`, `∃`, `∧`, `∨`, `↔`, `≠` | `intro`, `exact`, `apply`, `cases … with`, `constructor`, `obtain`, `show`, `funext`, `omega`, `decide` (solution only) | -| `Logic` | `abbrev`, `mutual`, `private` | `DecidableEq`, `×`, `¬`, `\|>` | `have`, `use`, `left`, `right`, `rcases`, `by_cases`, `by_contra`, `simp` | -| `Sets` | `open` | `Set`, `Rel`, `Finset`, `Fintype`, `Setoid`, `∈`, `⊆`, `∪`, `∩` | `assumption`, `trivial`, `symm`, `simp_all` | +| `IntroL` | `#check`, `#print`, `theorem`, `structure`, `instance`, `section`, `variable` | `Type`, `Prop`, `Bool`, `List`, `Option`, `Char`, `String`, `fun`/`λ`, `match`, `if … then … else`, `⟨…⟩`, implicit `{}`, instance-implicit `[]`, `∘`, `BEq` | `funext`, `show` (solution only), `omega` (solution only), `decide` (solution only) | +| `Logic/Proof` | `open` | `¬`, `∀`, `∃`, `∧`, `∨`, `↔`, `≠` | `intro`, `exact`, `apply`, `cases … with`, `constructor`, `obtain`, `have`, `use`, `left`, `right`, `rcases`, `by_cases`, `by_contra` | +| `Logic/PL` | `abbrev`, `private` | `DecidableEq`, `×` | `simp` | +| `Logic/FOL` | `mutual` | `\|>` | — | +| `Sets` | — | `Set`, `Rel`, `Finset`, `Fintype`, `Setoid`, `∈`, `⊆`, `∪`, `∩` | `assumption`, `trivial`, `symm`, `simp_all` | | `SeaBattle` | — | `Fin` | `native_decide` | | `Morphology` | `deriving BEq` | — | — | | `InfEngine` | — | `do`-notation | — | @@ -85,6 +87,14 @@ author decision, not a mechanical fix. see them. - **`trivial`** — one term-level use in `Sets.lean:507`, not presented anywhere. Give it a line or replace it when that chapter is revised. +- **`show`, `omega`, `decide` in `IntroL`** — all three appear only inside the + `twice` exercise's `solution!(…)` blocks (`:1154`, `:1155`, `:1158`), so the + student and `terse` variants never show them, but the `solutions` and + `grading` variants do, and nothing presents them before `Logic/Proof`. This + is the cost of moving every proof tactic out of `IntroL`: the chapter is now + deliberately pre-proof, so presenting them here would undo that. Either move + the exercise's tests to `Logic/Proof`, or weaken them to what `rfl` closes. + `decide` → `rfl` is known to work for `twice_test2`. `IntroCS` is the constraint's one accepted exception: it uses Lean that `IntroL` only presents later, deliberately, and the chapter says so where its From 57a6702b3d28872728b7d027ce86f5184813be7e Mon Sep 17 00:00:00 2001 From: Alexandre Rademaker Date: Sun, 13 Sep 2026 22:31:02 -0300 Subject: [PATCH 03/14] docs(logic): revise the prose of Logic, Proof, PL and part of FOL MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A reading pass over the chapter that the two preceding commits rearranged, checking the text against what the code now does rather than against what it did before the restructuring. FOL gains a computable `Formula.eval` and the bridge section that PL already had, so the chapter now has the same three-part shape as PL: syntax as data, computable semantics, and the theorem relating the two readings. PL's semantics section was rewritten around that same shape, and Proof was condensed. Fix five cross-references that resolved to nothing. Record in `PROVENANCE.md` that CSwFP/5.19 and 5.20 became portable only with this reorganization, since they need a semantics to be stated against, and update the `STYLE-CODE.md` ledger for the features that moved with the text: `List.all`/`List.any` to `IntroL`, `deriving BEq` from `Morphology` to `FOL`, and `List.contains` and `induction … generalizing` newly used in `FOL`. Add Nederpelt & Geuvers (2014) to the bibliography, cited where `Proof.lean` explains that Lean's foundation makes types and programs computable. --- Bib.lean | 9 + CSwL/IntroL.lean | 2 +- CSwL/Logic.lean | 6 +- CSwL/Logic/FOL.lean | 298 +++++++++++++++++------ CSwL/Logic/PL.lean | 300 ++++++++--------------- CSwL/Logic/Proof.lean | 539 +++++++++++++----------------------------- DEVIATIONS.md | 75 ++++-- PROVENANCE.md | 7 + STYLE-CODE.md | 6 +- 9 files changed, 578 insertions(+), 664 deletions(-) diff --git a/Bib.lean b/Bib.lean index 5ecff29..c4d7b9c 100644 --- a/Bib.lean +++ b/Bib.lean @@ -104,6 +104,15 @@ def enderton2001 : Article where volume := inlines!"" number := inlines!"" +def nederpelt2014 : Article where + title := inlines!"Type Theory and Formal Proof: An Introduction" + authors := #[inlines!"Rob Nederpelt", inlines!"Herman Geuvers"] + journal := inlines!"Cambridge University Press, Cambridge" + year := 2014 + month := none + volume := inlines!"" + number := inlines!"" + def FAA2025 : Article where title := inlines!"Formalizing Analysis of Algorithms, Autumn 2025" authors := #[inlines!"Sorrachai Yingchareonthawornchai"] diff --git a/CSwL/IntroL.lean b/CSwL/IntroL.lean index 6823c19..d40f0c9 100644 --- a/CSwL/IntroL.lean +++ b/CSwL/IntroL.lean @@ -9,7 +9,7 @@ open CSwLMeta %%% tag := "IntroL" htmlSplit := .never -file := "IntroL" +file := "introL" %%% Neste capítulo, apresentamos o essencial sobre a linguagem de programação Lean. Nosso objetivo é apresentar o suficiente para que o leitor possa acompanhar os exemplos do restante do livro. Para uma apresentação completa, sugerimos a leitura de {citep Bib.FPiL}[] e {citep Bib.LLR}[]. diff --git a/CSwL/Logic.lean b/CSwL/Logic.lean index 390f83e..6d10eeb 100644 --- a/CSwL/Logic.lean +++ b/CSwL/Logic.lean @@ -19,11 +19,7 @@ htmlSplit := .never file := "Logic" %%% -O capítulo tem três seções. A primeira é sobre provar em Lean: o tipo `Prop`, -o que conta como prova, e as táticas que constroem uma. As outras duas -implementam a lógica proposicional e a de predicados como tipos de dados, -cada uma com sua sintaxe, sua semântica computável, e a ponte entre a fórmula -como dado e a proposição que ela afirma. +O capítulo tem três seções. Em {ref "Proof"}[Proof] vamos usar Lean como assistente de prova, entendendo como usar o tipo `Prop` e como construir provas de proposições a partir de termos ou táticas. Em {ref "PL"}[PL] trataremos da implementação de lógica proposicional usando Lean como linguagem de programação, daremos a sintática e semântica de PL. Finalmente, em {ref "FOL"}[FOL], vamos implementar a lógica de predicados, novamente com sua sintaxe e semântica computável. {include 1 CSwL.Logic.Proof} diff --git a/CSwL/Logic/FOL.lean b/CSwL/Logic/FOL.lean index 347036e..3ad7e12 100644 --- a/CSwL/Logic/FOL.lean +++ b/CSwL/Logic/FOL.lean @@ -16,13 +16,24 @@ tag := "FOL" namespace FOL ``` -Frases como "Todo príncipe viu uma dama" não podem ser expressas em lógica proposicional — ficariam como átomos `p`/`q` totalmente desconectados, sem capturar que a mesma entidade que é "príncipe" foi a que realizou o ato de ver. Lógica de predicados acrescenta três ingredientes: +# Introdução +%%% +tag := "fol-intro" +%%% + +Se usarmos lógica proposicional para formalizar a frase "Toda maça é vermelha", teremos uma letra proposicional, um átomo indivisível que não nos permitiria capturar a idéia do quantificador e da dependencia declarada entre as _coisas_ que são maças e a cor destas mesmas _coisas_. Lógica de predicados acrescenta três ingredientes: -* proposições básicas, predicados `n`-ário seguidos de `n` variáveis; +* termos para representar indivíduos de um domínio. Os termos poderão ser variáveis ou funções aplicadas sobre termos; +* proposições básicas serão predicados `n`-ários sobre termos; * fórmulas universalmente quantificadas, `∀` seguido de variável e fórmula; * fórmulas existencialmente quantificadas, `∃` seguido de variável e fórmula. -Também chamada de "lógica de primeira ordem" (FOL, "first order logic") está relacionado a quantificação ser sobre entidades, objetos de primeira ordem. Vamos assumir que predicados aridade até 3 (relações unárias, binárias e ternárias). Relações com mais de três argumentos quase nunca são necessárias para capturar a semântica de linguagem natural. A BNF completa segue abaixo e gera fórmulas como `¬P x`, `∀ x R x x` e `∀ x ∃ y R x y`. +# Sintaxe de Lógica de Primeira Ordem +%%% +tag := "fol-syntax" +%%% + +Também chamada de "lógica de primeira ordem" (FOL, "first order logic"). Vamos assumir que predicados terão aridade de 1 até 3 (relações unárias, binárias e ternárias). Relações com mais de três argumentos quase nunca são necessárias para capturar a semântica de linguagem natural. A BNF completa segue abaixo e gera fórmulas como `¬P x`, `∀ x R x x` e `∀ x ∃ y R x y`. ```bnf v ::= "x" | "y" | "z" | v "'" ; @@ -39,12 +50,8 @@ F ::= atom | "∃" v F ("quantificação existencial") ; ``` -Em Lean, o mesmo tipo `Prop` em Lean pode ser usado na representação de fórmulas de primeira ordem. Também veremos como as fórmulas podem ser manipuladas como dados. - -# Ligação de variáveis - -Numa fórmula `∀x F` (ou `∃x F`), o quantificador liga toda ocorrência de -`x` em `F` que não esteja já ligada por um `∀x`/`∃x` interno a `F`. Uma fórmula é *aberta* se tem ao menos uma ocorrência livre de variável, e *fechada* (também chamada *sentença*) caso contrário. Por exemplo, `(Px ∧ ∃x Rxx)` é aberta, o `x` de `Px` está fora do escopo do `∃x`. Mas `∃x (Px ∧ ∃x Rxx)` é uma sentença. +Em uma fórmula `∀x F` (ou `∃x F`), o quantificador liga toda ocorrência de +`x` em `F` que não esteja já ligada por um `∀x`/`∃x` interno a `F`. Uma fórmula é *aberta* se tem ao menos uma ocorrência livre de variável, e *fechada* (também chamada *sentença*) caso contrário. Por exemplo, `(P x ∧ ∃x, R x x)` é aberta, o `x` de `P x` está fora do escopo do `∃x`. Mas `∃x (P x ∧ ∃x R x x)` é uma sentença. Essa distinção é o que motiva a ambiguidade de escopo de "Todo príncipe viu uma dama". Duas leituras possíveis, "para cada príncipe existe uma dama (talvez diferente) que ele viu" contra "existe uma dama que todo príncipe viu", formalizadas respectivamente como: @@ -57,20 +64,14 @@ Repare que a leitura universal usa `→` como conectivo principal, e a existencial usa `∧`. Já "Algum príncipe viu uma dama bonita" admite apenas uma formalização, `∃x∃y (Prince x ∧ Lady y ∧ Beautiful y ∧ Saw x y)`. :::dev "Alexandre (arademaker)" - Em Lean, indexar por aridade é mais natural do que empilhar primos: um `structure PredSymbol` com campos `name : String` e `arity : Nat` já representa "infinitos predicados de cada aridade finita" sem precisar de uma família de gramáticas, uma por aridade. Fica como observação, `Formula` (abaixo) não adota `PredSymbol`. - ::: - -# O tipo Fórmulas de FOL - -Uma variável carrega nome e um índice (lista de naturais usada para gerar -variáveis "frescas" a partir de uma dada variável): +Como fizemos em {ref "pl-syntax"}[pl-syntax], vamos agora definir um tipo para representar fórmulas FOL. Uma variável carrega nome e um índice (lista de naturais usada para gerar variáveis "frescas" a partir de uma dada variável): ```lean structure Variable where @@ -402,10 +403,10 @@ def openForm (f : Formula Term) : Bool := Por conveniência, nos limitamos a um fragmento de língua com apenas três letras de predicado: `P` (unário), `R` (binário), e `S` (ternário). -Como deve ser uma estrutura extralinguística para as constantes `P`, `R` e `S`? Tal estrutura deve conter ao menos um domínio de discurso `D`, formado por entidades individuais, com uma interpretação para `P`, para `R` e para `S`. Essas interpretações são dadas por uma função `Interp`, que a cada nome de predicado e a cada lista de elementos do domínio associa a afirmação de que a relação vale entre eles. +Como deve ser uma estrutura extralinguística para as constantes `P`, `R` e `S`? Tal estrutura deve conter ao menos um domínio de discurso `D`, formado por entidades individuais, com uma interpretação para `P`, para `R` e para `S`. Essas interpretações são dadas por uma função `Interp`, que a cada nome de predicado e a cada lista de elementos do domínio associa um valor de verdade. ```lean -abbrev Interp (D : Type) := String → List D → Prop +abbrev Interp (D : Type) := String → List D → Bool ``` Um conjunto de símbolos de relação, com suas aridades, especifica uma linguagem @@ -414,17 +415,54 @@ não vazio `D` com uma função de interpretação para os símbolos de relaçã é chamada de *modelo* para `L`. Sempre suporemos que o domínio de um modelo é não vazio. -Eis um modelo concreto, com domínio de três elementos. `P` vale para `1` ou `3`; -`R` relaciona `1` a `1` e `2`, `2` a `2`, e `3` a `1` e `2`. +Eis um modelo concreto: dez entidades de contos de fadas, nomeadas por letras. +Nada aqui depende da escolha das letras — o que importa é que o domínio seja +finito e que cada predicado diga, de cada entidade, se vale ou não. + +```lean +inductive Entity where + | A | B | D | E | G | M | R | S | T | Y +deriving Repr, DecidableEq, BEq + +def entities : List Entity := + [.A, .B, .D, .E, .G, .M, .R, .S, .T, .Y] +``` + +`S` é Branca de Neve, `A` é Alice, `D` é Dorothy, `G` é Cachinhos Dourados, +`M` é o Pequeno Mook, `Y` é Atreyu, `E` é a princesa, `B` e `R` são os anões, +e `T` é o gigante. + +Os predicados unários são a pertinência a uma lista, exatamente como no +original. Os binários se dão por enumeração dos pares, ou por uma regra. + +```lean +def girl : Entity → Bool := ([Entity.S, .A, .D, .G].contains ·) +def boy : Entity → Bool := ([Entity.M, .Y].contains ·) +def princess : Entity → Bool := ([Entity.E].contains ·) +def dwarf : Entity → Bool := ([Entity.B, .R].contains ·) +def giant : Entity → Bool := ([Entity.T].contains ·) +def child : Entity → Bool := fun x => girl x || boy x + +def love : Entity → Entity → Bool := fun x y => + [(Entity.Y, Entity.E), (.B, .S), (.R, .S)].contains (x, y) + +def defeat : Entity → Entity → Bool := fun x y => dwarf x && giant y +``` + +A função de interpretação amarra os nomes de predicado ao modelo. Nomes fora +da lista, ou usados com o número errado de argumentos, recebem `false`. ```lean -def M : Interp Nat - | "P", [d] => d = 1 ∨ d = 3 - | "R", [d, e] => - (d = 1 ∧ (e = 1 ∨ e = 2)) - ∨ (d = 2 ∧ e = 2) - ∨ (d = 3 ∧ (e = 1 ∨ e = 2)) - | _, _ => False +def int0 : Interp Entity + | "Girl", [x] => girl x + | "Boy", [x] => boy x + | "Princess", [x] => princess x + | "Dwarf", [x] => dwarf x + | "Giant", [x] => giant x + | "Child", [x] => child x + | "Love", [x, y] => love x y + | "Defeat", [x, y] => defeat x y + | _, _ => false ``` Dada uma estrutura com função de interpretação `M = (D, I)`, podemos definir uma @@ -450,33 +488,76 @@ noção `M ⊨ᵍ F`, "F é verdadeira em M sob a atribuição g", ou: "g satisf modelo M". O que segue é uma definição recursiva de verdade para as fórmulas da lógica de -predicados. As cláusulas dos quantificadores são as que fazem a atribuição mudar: -`∀v F` vale quando `F` vale para toda escolha de valor de `v`, e `∃v F` quando -vale para ao menos uma. +predicados. Como em {ref "PL"}[lógica proposicional], a definição *calcula*: o +resultado é um `Bool`, e o valor de uma fórmula pode ser obtido com `#eval`. As +cláusulas dos quantificadores são as que fazem a atribuição mudar: `∀v F` vale +quando `F` vale para toda escolha de valor de `v`, e `∃v F` quando vale para ao +menos uma. + +Aqui aparece a diferença em relação à lógica proposicional. Para decidir um +quantificador é preciso percorrer o domínio, e percorrer exige que o domínio +esteja disponível como uma lista. Por isso `eval` recebe um argumento a mais, +`dom`, e usa `List.all` e `List.any` — as versões computáveis de `∀` e `∃`. ```lean -def Formula.holds {D : Type} (I : Interp D) - (g : Assign D) : Formula Variable → Prop +def Formula.eval {D : Type} [DecidableEq D] + (dom : List D) (I : Interp D) + (g : Assign D) : Formula Variable → Bool | .atom name args => I name (args.map g) - | .eq t1 t2 => g t1 = g t2 - | .top => True - | .bot => False - | .neg f => ¬ Formula.holds I g f + | .eq t1 t2 => g t1 == g t2 + | .top => true + | .bot => false + | .neg f => !(Formula.eval dom I g f) | .impl f1 f2 => - Formula.holds I g f1 → Formula.holds I g f2 + !(Formula.eval dom I g f1) || Formula.eval dom I g f2 | .equi f1 f2 => - Formula.holds I g f1 ↔ Formula.holds I g f2 + Formula.eval dom I g f1 == Formula.eval dom I g f2 | .conj f1 f2 => - Formula.holds I g f1 ∧ Formula.holds I g f2 + Formula.eval dom I g f1 && Formula.eval dom I g f2 | .disj f1 f2 => - Formula.holds I g f1 ∨ Formula.holds I g f2 + Formula.eval dom I g f1 || Formula.eval dom I g f2 | .forall_ v f => - ∀ d : D, Formula.holds I (g.update v d) f + dom.all fun d => Formula.eval dom I (g.update v d) f | .exists_ v f => - ∃ d : D, Formula.holds I (g.update v d) f + dom.any fun d => Formula.eval dom I (g.update v d) f +``` + +Um caso por construtor, e cada caso troca o construtor pela operação +correspondente sobre `Bool`. Se avaliamos fórmulas fechadas, isto é, sem +variáveis livres, a atribuição `g` se torna irrelevante — mas ainda é preciso +fornecer alguma. + +```lean +def g0 : Assign Entity := fun _ => .S + +def someDwarfDefeatsSomeGiant : Formula Variable := + .exists_ x (.conj (.atom "Dwarf" [x]) + (.exists_ y (.conj (.atom "Giant" [y]) + (.atom "Defeat" [x, y])))) + +def everyChildIsGirlOrBoy : Formula Variable := + .forall_ x (.impl (.atom "Child" [x]) + (.disj (.atom "Girl" [x]) (.atom "Boy" [x]))) + +def everyDwarfLovesAPrincess : Formula Variable := + .forall_ x (.impl (.atom "Dwarf" [x]) + (.exists_ y (.conj (.atom "Princess" [y]) + (.atom "Love" [x, y])))) +``` + +```lean (name := folEval1) +#eval (Formula.eval entities int0 g0 someDwarfDefeatsSomeGiant, + Formula.eval entities int0 g0 everyChildIsGirlOrBoy, + Formula.eval entities int0 g0 everyDwarfLovesAPrincess) +``` + +```leanOutput folEval1 +(true, true, false) ``` -Um caso por construtor, e cada caso troca o construtor pelo conectivo correspondente do Lean. Se avaliamos fórmulas fechadas, isto é, sem variáveis livres, a atribuição `g` se torna irrelevante. +A terceira é falsa no modelo: os anões `B` e `R` amam `S`, que é Branca de +Neve, e Branca de Neve não é a princesa. Quem ama a princesa é `Y`, que não é +anão. A definição de verdade faz uso essencial das atribuições e, ainda assim, nos exercícios em que se olha apenas para fórmulas fechadas, a verdade ou a falsidade @@ -567,31 +648,10 @@ def knightFightsDragon : Formula Variable := (.atom "Fights" [x, y])))) ``` -A fórmula é uma proposta; a verificação é mostrar que ela afirma o que se -queria. `Formula.holds` leva uma fórmula à proposição que ela afirma, dada uma -interpretação, então basta enunciar a condição de verdade pretendida com os -quantificadores do próprio Lean e exigir que as duas coincidam. Como `holds` -calcula, cada teorema fecha por `Iff.rfl`. - -```lean -theorem someoneWalksAndTalks_means {D : Type} - (I : Interp D) (g : Assign D) : - Formula.holds I g someoneWalksAndTalks ↔ - ((∃ d : D, I "Walk" [d]) ∧ (∃ d : D, I "Talk" [d])) := - solution!(Iff.rfl) - -theorem knightFightsDragon_means {D : Type} - (I : Interp D) (g : Assign D) : - Formula.holds I g knightFightsDragon ↔ - (∀ a : D, ∀ b : D, - I "Knight" [a] ∧ I "Dragon" [b] ∧ I "Finds" [a, b] → - I "Fights" [a, b]) := - solution!(Iff.rfl) -``` - -O segundo é o que torna a discussão abaixo verificável: a força universal dos -indefinidos não é uma opinião sobre a tradução, é o que o `∀` do lado direito -diz, e o `Iff.rfl` confirma que a fórmula proposta diz o mesmo. +A fórmula é uma proposta; a verificação de que ela afirma o que se queria fica +para a seção seguinte, que dá o meio de enunciar a condição de verdade +pretendida com os quantificadores do próprio Lean e exigir que as duas +coincidam. :::solution As duas primeiras são diretas, mas repare no escopo da negação em (2): _no @@ -635,6 +695,108 @@ dê um contraexemplo. :::: +# Traduzindo `Formula` para `Prop` + +Como em {ref "PL"}[lógica proposicional], fechamos o capítulo ligando as duas +leituras de uma fórmula. `Formula.eval` calcula um `Bool`; `Formula.denote` +produz a proposição que a fórmula afirma. A interpretação muda junto: onde +`Interp` devolvia um `Bool`, `Denot` devolve uma `Prop`. + +```lean +abbrev Denot (D : Type) := String → List D → Prop + +def Formula.denote {D : Type} (I : Denot D) + (g : Assign D) : Formula Variable → Prop + | .atom name args => I name (args.map g) + | .eq t1 t2 => g t1 = g t2 + | .top => True + | .bot => False + | .neg f => ¬ Formula.denote I g f + | .impl f1 f2 => + Formula.denote I g f1 → Formula.denote I g f2 + | .equi f1 f2 => + Formula.denote I g f1 ↔ Formula.denote I g f2 + | .conj f1 f2 => + Formula.denote I g f1 ∧ Formula.denote I g f2 + | .disj f1 f2 => + Formula.denote I g f1 ∨ Formula.denote I g f2 + | .forall_ v f => + ∀ d : D, Formula.denote I (g.update v d) f + | .exists_ v f => + ∃ d : D, Formula.denote I (g.update v d) f +``` + +Cada caso troca um construtor de `Formula` pelo conectivo correspondente de +`Prop` — o `conj` do dado vira o `∧` da proposição, e o `forall_` vira o `∀` +do próprio Lean. + +Com isso podemos voltar às traduções do exercício anterior e verificá-las. +Enunciamos a condição de verdade pretendida à direita, com os quantificadores +do Lean, e exigimos que coincida com o que a fórmula proposta afirma. Como +`denote` calcula, cada teorema fecha por `Iff.rfl`. + +```lean +theorem someoneWalksAndTalks_means {D : Type} + (I : Denot D) (g : Assign D) : + Formula.denote I g someoneWalksAndTalks ↔ + ((∃ d : D, I "Walk" [d]) ∧ (∃ d : D, I "Talk" [d])) := + solution!(Iff.rfl) + +theorem knightFightsDragon_means {D : Type} + (I : Denot D) (g : Assign D) : + Formula.denote I g knightFightsDragon ↔ + (∀ a : D, ∀ b : D, + I "Knight" [a] ∧ I "Dragon" [b] ∧ I "Finds" [a, b] → + I "Fights" [a, b]) := + solution!(Iff.rfl) +``` + +O segundo é o que torna verificável a discussão sobre os indefinidos: a força +universal de _a knight_ e _a dragon_ não é uma opinião sobre a tradução, é o +que o `∀` do lado direito diz, e o `Iff.rfl` confirma que a fórmula proposta +diz o mesmo. + +Falta o teorema que diz que as duas leituras concordam. Ele precisa de uma +hipótese que não aparecia em lógica proposicional: `eval` decide um +quantificador percorrendo `dom`, então só podemos esperar que ele concorde com +o `∀` do Lean — que fala de *todo* elemento do tipo `D` — se `dom` de fato +listar todos eles. É isso que `hdom` exige. + +```lean +theorem Formula.eval_iff_denote {D : Type} [DecidableEq D] + (dom : List D) (hdom : ∀ d : D, d ∈ dom) + (I : Interp D) (g : Assign D) (f : Formula Variable) : + f.eval dom I g = true ↔ + f.denote (fun n as => I n as = true) g := by + induction f generalizing g with + | atom name args => simp [Formula.eval, Formula.denote] + | eq t1 t2 => simp [Formula.eval, Formula.denote] + | top => simp [Formula.eval, Formula.denote] + | bot => simp [Formula.eval, Formula.denote] + | neg f ih => simp [Formula.eval, Formula.denote, ← ih] + | impl f1 f2 ih1 ih2 => + simp [Formula.eval, Formula.denote, ← ih1, ← ih2] + cases Formula.eval dom I g f1 <;> simp + | equi f1 f2 ih1 ih2 => + simp [Formula.eval, Formula.denote, ← ih1, ← ih2] + | conj f1 f2 ih1 ih2 => + simp [Formula.eval, Formula.denote, ih1, ih2] + | disj f1 f2 ih1 ih2 => + simp [Formula.eval, Formula.denote, ih1, ih2] + | forall_ v f ih => + simp [Formula.eval, Formula.denote, ih] + exact ⟨fun h d => h d (hdom d), fun h d _ => h d⟩ + | exists_ v f ih => + simp [Formula.eval, Formula.denote, ih] + exact ⟨fun ⟨d, _, h⟩ => ⟨d, h⟩, + fun ⟨d, h⟩ => ⟨d, hdom d, h⟩⟩ +``` + +A hipótese `hdom` é a contrapartida formal de uma limitação real: só se pode +calcular o valor de uma fórmula quantificada quando o domínio é finito e +conhecido. Para domínios infinitos, `denote` continua dizendo o que a fórmula +afirma, mas nenhum `#eval` responde. + ```lean end FOL ``` diff --git a/CSwL/Logic/PL.lean b/CSwL/Logic/PL.lean index e96cf64..dfc3cf0 100644 --- a/CSwL/Logic/PL.lean +++ b/CSwL/Logic/PL.lean @@ -18,103 +18,38 @@ namespace PL ``` # Introdução +%%% +tag := "pl-intro" +%%% -A lógica proposicional (PL, "Propositional Logic") trata de fórmulas construídas a partir de variáveis proposicionais usando os conectivos `¬`, `∧`, `∨`, `→` e `↔`. Intuitivamente, uma variável proposicional `p` representa uma sentença ou proposição que pode ser verdadeira ou falsa. Podemos usar lógica proposicional para fugir das impressões das línguas naturais. Queremos tornar precisa a noção de que uma proposição `α` decorre de outra proposição `β`. - -Como primeiro exemplo, adaptado de {citet Bib.enderton2001}[], a sentença "traços de potássio foram observados" pode ser traduzida para a linguagem formal como o símbolo `K`. Já para a sentença relacionada "traços de potássio não foram observados", podemos usar `¬ K`. Aqui `¬` é o nosso símbolo de negação, lido como "não". Poderíamos também pensar em traduzir "traços de potássio não foram observados" por algum símbolo novo `J`, mas preferimos decompor sentenças em suas partes atômicas tanto quanto possível. Para uma sentença não relacionada, "a amostra continha cloro" escolhemos o símbolo `C`. Assim, as seguintes sentenças compostas podem ser formalizadas. - -- A sentença "Se traços de potássio foram observados, então a amostra não continha cloro." é formalizada como `(K → (¬C))` com símbolo `→` significando "se ... então ...". -- A sentença "A amostra continha cloro, e traços de potássio foram observados." é formalizada como `(C ∧ K)` com símbolo `∧` significando a conjunção "e". -- A sentença "Ou traços de potássio não foram observados, ou a amostra não continha cloro." formalizamos como `((¬K) ∨ (¬C))` com símbolo `∨` significando a disjunção "ou". -- E a sentença "Nem a amostra continha cloro, nem traços de potássio foram observados." é formalizada como `(¬(C ∨ K))` ou `((¬C) ∧ (¬K))`, são *equivalentes*. - -Sempre que nos é dada a verdade ou falsidade das partes atômicas de uma sentença, podemos calcular a verdade ou falsidade da sentença. Suponha, por exemplo, que um químico saia do laboratório e anuncie que observou traços de potássio, mas que a amostra não continha cloro. A partir destas afirmações, podemos então determinar quais das sentenças acima são verdadeiras ou falsas. De fato, podemos construir uma tabela analisando os valores das sentenças para cada possível combinação possível dos valores verdade das proposições atômicas. - -:::table +header (align := center) -* - * `K` - * `C` - * `(¬(C ∨ K))` - * `((¬C) ∧ (¬K))` -* - * F - * F - * T - * T -* - * F - * T - * F - * F -* - * T - * F - * F - * F -* - * T - * T - * F - * F -::: - -::::quiz -Três irmãs - Ana, Maria e Cláudia — foram a uma festa com vestidos de cores -diferentes. Uma vestiu azul, a outra branco, e a terceira, preto. - -Chegando à festa, o anfitrião perguntou quem era cada uma delas. - -- A de azul respondeu: "Ana é a que está de branco"; -- A de branco disse: "Eu sou Maria"; -- A de preto respondeu: "Cláudia é quem está de branco". - -O anfitrião foi capaz de identificar cada irmã considerando que: - -- Ana sempre diz a verdade; -- Maria às vezes diz a verdade; -- Cláudia nunca diz a verdade. - -:::quizSolution -Ana é quem veste preto, Cláudia quem veste branco e Maria quem veste azul. -::: - -:::: - -Podemos formalizar o problema anterior em LP. Uma das motivações é tornar a argumentação precisa e convincente e, se possível, mecânica. - -Para isso, primeiro precisamos identificar as proposições mais elementares do problema e associar cada proposição a um símbolo. Em seguida, precisamos formalizar cada afirmação (ou enunciado) do problema como uma fórmula em LP. Vamos chamar de `Γ` o conjunto destas fórmulas. Também precisamos formalizar a resposta em uma fórmula em LP, vamos chamar de `α`. - -Finalmente, precisamos de um método para definir se a fórmula `α` é *consequência lógica* das premissas `Γ`. Um dos métodos possíveis é semântico. Quando para toda possível escolha de valores verdade para os símbolos proposicionais, sempre que todas as premissas forem *verdade* a conclusão deve ser *verdade*. Usamos a notação `Γ ⊧ α` para indicar que `α` é consequência lógica das premissas. - -No problema dos vestidos, o número de personagens e atributos é finito, portanto há apenas um número finito de possíveis proposições. Os números também são pequenos o suficiente para que análise sistemática de todas as combinações de valores verdade seja viável na prática. Para demonstrar que todo número par maior que dois pode ser escrito como uma soma de dois números primos esta estratégia não seria válida. - -# Sintaxe +Em {ref "Proof"}["Proof"] as fórmulas proposicionais foram escritas diretamente como termos do tipo `Prop`, e usando táticas construimos provas de proposições `α` a partir de um conjunto de hipóteses `Γ`. Isto é, em Lean mostramos como derivar `α` a partir de `Γ`, isto é `Γ ⊢ α`, de forma sintática. -Em Lean, `Prop` é um tipo e proposições particulares também são tipos. A variável `h` abaixo pode ser entendida como um identificador para uma "prova qualquer" da proposição `p ∨ q`. +Mas em Lean, `Prop` é um tipo e proposições particulares também são tipos. A variável `h` abaixo pode ser entendida como um identificador para uma "prova qualquer" da proposição `p ∧ q`. E Lean adota o princípio da "irrelevância da prova", ou seja, Lean não distingue diferentes provas de uma proposição. Como consequência, o tipo `Prop` não é computável, não é um "dado" que pode ser manipulado. Por exemplo, não conseguimos extrair os componentes de uma conjunção `a ∧ b`. Lean sabe que todas as provas de `a ∧ b` são irrelevantes e iguais, então ele não permite que você use uma prova para tomar decisões no mundo dos dados programáveis (`Type`). Em outras palavras, não podemos realizar casamento de padrões em `h` abaixo. -```lean +```lean +error section variable (p q : Prop) -#check p ∨ q -variable (h : p ∨ q) -``` - -Mas Lean adota o princípio da "irrelevância da prova", ou seja, Lean não distingue diferentes provas de uma proposição. Como consequência, o tipo `Prop` não é computável, não é um "dado" que pode ser manipulado. Por exemplo, não conseguimos extrair os componentes de uma conjunção `a ∧ b`, para fora do tipo `Prop`. Lean proíbe a extração de `Prop` para `Type`, ele sabe que todas as provas de `a ∧ b` são irrelevantes e iguais, então ele não permite que você use uma prova para tomar decisões no mundo dos dados programáveis (`Type`). +variable (h : p ∧ q) +#check p ∧ q +#check h -```lean +error -variable (a b : Prop) - -def cannotExtractLeft (h : a ∧ b) : Type := +def doesNotWork (h : p ∧ q) : Type := match h with | And.intro ha hb => ha -``` -```lean end ``` -Como vamos precisar manipular fórmulas lógicas, teremos que definir um tipo de dado para representar fórmulas proposicionais. +Nesta seção, queremos manipular fórmulas e decidir quando uma fórmula `α` é consequência lógica de `β`, isto é `β ⊧ α `. A noção de consequência lógica é semântica. Para toda possível escolha de valores verdade para os símbolos proposicionais em `α` e `β`, sempre que `β` for verdade, `α` deve ser verdade. Para _computar_ o valor verdade de uma fórmula, vamos precisar manipula a formula como dado, e calcular seu valor verdade a partir do mapeamento de variáveis proposicionais em valores verdade. Em tempo, a relação dentre duas fórmulas pode ser naturalmente estendida para uma relação entre um conjunto de fórmulas `Γ` e uma fórmula, `Γ ⊧ α`. + +Em um problema com um número finito de proposições, e os números costumam ser pequenos o suficiente para que a análise sistemática de todas as combinações de valores verdade seja viável na prática. Para demonstrar que todo número par maior que dois pode ser escrito como uma soma de dois números primos esta estratégia não seria válida. + + +# Sintaxe de Lógica Proposicional +%%% +tag := "pl-syntax" +%%% Formalmente, a sintaxe da LP é definida pela BNF abaixo. As variáveis proposicionais (ou símbolos sentenciais) são os `atom`. O uso do sufixo `'` no não-terminal `atom` é uma forma conveniente de expressar que podemos gerar quantos átomos forem necessários. @@ -128,9 +63,9 @@ F ::= atom | "(" F "↔" F ")" ("se-somente-se") ; ``` -Com esta gramática, podemos gerar fórmulas como `¬¬¬p'''`, `((p ∨ p') ∧ p')`, `(p ∧ (p' ∧ p'''))`. Sem parênteses a gramática pode gerar strings ambíguas: `p ∧ p′ ∨ p″` lê-se tanto como `(p ∧ p′) ∨ p″` quanto como `p ∧ (p′ ∨ p″)`, e a ambiguidade estrutural afeta o significado, como na sentença "era jovem e bonita ou triste". Nem todos os conectivos precisam ser definidos como "primitivos". Em algumas apresentações, o conectivo `→` é definido como uma abreviação para `p → q ≃ ¬ p ∨ q`. +Com esta gramática, podemos gerar fórmulas como `¬¬¬p'''`, `((p ∨ p') ∧ p')`, `(p ∧ (p' ∧ p'''))`. Sem parênteses a gramática pode gerar strings ambíguas: `p ∧ p′ ∨ p″` lê-se tanto como `(p ∧ p′) ∨ p″` quanto como `p ∧ (p′ ∨ p″)`, e a ambiguidade estrutural afeta o significado, como na sentença "era jovem e bonita ou triste". Nem todos os conectivos precisam ser definidos como "primitivos". O conectivo `→` poderia ser definido como uma abreviação para `p → q ≃ ¬ p ∨ q`. -Como anteriormente, iremos formalizar a gramática acima como um tipo indutivo. Um átomo é identificado por um nome, e o nome é uma `String`. Isso dá o inventário ilimitado que a gramática pede sem precisar enumerar símbolo por símbolo. +A gramática acima será representada pelo tipo indutivo `Form`. Um átomo é identificado por um nome, e o nome é uma `String`. Isso dá o inventário ilimitado que a gramática pede sem precisar enumerar símbolo por símbolo. ```lean inductive Form where @@ -151,7 +86,7 @@ def Form.equi (f g : Form) : Form := .conj (Form.impl f g) (Form.impl g f) ``` -A conjunção e a disjunção são binárias. Poderiam receber uma lista de fórmulas `conj (fs : List Form)`, mas um construtor que guarda uma `List Form` dentro do próprio tipo o torna um indutivo _nested_, mais complicado em Lean. Mas podemos definir funções que recebem listas de fórmulas e constrem conjunções e disjunções. Abaixo `top`/`bot` são a base da recursão de `conjs`/`disjs`. Uma conjunção vazia é sempre verdadeira, uma disjunção vazia é sempre falsa. +A conjunção e a disjunção são binárias. Poderiam receber uma lista de fórmulas `conj (fs : List Form)`, mas um construtor que guarda uma `List Form` dentro do próprio tipo o torna um indutivo _nested_, mais complicado de manipular em Lean. Mas podemos definir funções que recebem listas de fórmulas e constrem conjunções e disjunções. Abaixo `top`/`bot` são a base da recursão de `conjs`/`disjs`. Uma conjunção vazia é sempre verdadeira, uma disjunção vazia é sempre falsa. ```lean def Form.conjs : List Form → Form @@ -165,7 +100,7 @@ def Form.disjs : List Form → Form | f :: fs => .disj f (Form.disjs fs) ``` -::::exercise (rating := 1) (name := "bangu-form") +:::exercise (rating := 1) (name := "bangu-form") Três pessoas são suspeitas de torcer pelo Bangu F.C. Aparecido entrevistou os três, para tentar descobrir, e obteve os seguintes depoimentos: - Auro: Joaquim não torce pelo BFC e Cláudia torce pelo BFC. @@ -183,18 +118,17 @@ def depo1 : Form := solution!(.conj (.neg J) C) def depo2 : Form := solution!(.impl (.neg A) (.neg C)) def depo3 : Form := solution!(.conj C (.disj (.neg A) (.neg J))) ``` -:::: - +::: -::::exercise (rating := 1) (name := "exclusive-or") +:::exercise (rating := 1) (name := "exclusive-or") A expressão `p ∨ q` é verdadeira mesmo quando `p` e `q` são ambos verdadeiros. Em português, "ou" costuma ser exclusivo, como em "Você pode ficar com o sorvete ou com o algodão-doce, mas não com os dois." Defina um conectivo `xor` para "ou exclusivo", usando os conectivos já definidos. ```lean def Form.xor (f g : Form) : Form := solution!(.disj (.conj f (.neg g)) (.conj (.neg f) g)) ``` -:::: +::: O tipo `Form` é um `inductive`. Um valor de `Form` é dado. Nenhum dos exercícios abaixo seriam possíveis em `Prop`. Não há como perguntar "quantos `∧` tem esta proposição" a um valor de tipo `Prop`, porque `Prop` não guarda a fórmula que o provou. Vamos definir duas fórmulas para usar nos exercícios seguintes. @@ -208,8 +142,8 @@ def form2 : Form := #eval form2 ``` -::::exercise (rating := 1) (name := "count-operators") -Implemente uma função `opsNr` para contar o número de operadores de uma fórmula. +:::exercise (rating := 1) (name := "count-operators") +Implemente uma função `opsNr` para contar o número de operadores de uma fórmula. a tática {tactic}`decide` é como pedir ao Lean para executar a decisão de uma proposição booleana e, se o resultado for true, transformar esse resultado em uma prova. ```lean def Form.opsNr : Form → Nat := @@ -221,11 +155,11 @@ def Form.opsNr : Form → Nat := | .conj f g => 1 + f.opsNr + g.opsNr | .disj f g => 1 + f.opsNr + g.opsNr) -theorem opsNr_test : form1.opsNr = 2 := solution!(by decide) +example : form2.opsNr = 3 := by decide ``` -:::: +::: -::::exercise (rating := 1) (name := "formula-depth") +:::exercise (rating := 1) (name := "formula-depth") Implemente uma função `depth` para calcular a profundidade da árvore de análise de uma fórmula. ```lean @@ -238,12 +172,11 @@ def Form.depth : Form → Nat := | .conj f g => 1 + max f.depth g.depth | .disj f g => 1 + max f.depth g.depth) -theorem depth_test : form1.depth = 2 := solution!(by decide) +example : form2.depth = 3 := by decide ``` -:::: - -::::exercise (rating := 2) (name := "collect-atoms") +::: +:::exercise (rating := 2) (name := "collect-atoms") Implemente `propNames` para coletar a lista de nomes de átomos proposicionais que ocorrem numa fórmula. A lista resultante deve estar ordenada e sem repetições. ```lean @@ -259,24 +192,25 @@ private def Form.propNamesRaw : Form → List String := def Form.propNames (f : Form) : List String := solution!(f.propNamesRaw.eraseDups.mergeSort (· ≤ ·)) ``` -:::: +::: -# Semântica -Vimos que a noção de "derivação" é diretamente implementada no Lean. Isto é, dizemos que `P ∧ Q ⊢ P` porque conseguimos construir uma prova de `P` a partir da existência de uma prova de `P ∧ Q`. +# Semântica de Lógica Proposicional +%%% +tag := "pl-semantics" +%%% -Mas as regras de derivação que usamos correspondem a (ou são justificadas por) uma noção semântica de *consequência lógica*, `P ∧ Q ⊧ P`. Entendemos que `P` deve ser verdade sempre que `P ∧ Q` for verdade, para qualquer possível tradução de `P` e `Q` de volta para expressões em uma linguagem natural. Para formalizar esta noção de "todas as possíveis traduções", vamos precisar de um processo para avaliar fórmulas lógicas em valores verdade. +Todas as regras de derivação que usamos em {ref "Proof"}["Proof"] são justificadas por uma noção semântica de *consequência lógica*. Entendemos que `P` deve ser verdade sempre que `P ∧ Q` for verdade, para qualquer possível tradução de `P` e `Q` de volta para expressões em uma linguagem natural, por isso aceitamos `P ∧ Q ⊧ P`. Para formalizar esta noção de "todas as possíveis traduções", vamos precisar de um processo para avaliar fórmulas lógicas em valores verdade. Vamos chamar de *valorações* um mapeamento de símbolos proposicionais no conjunto dos booleanos, que em Lean correspondem aos valores `True` e `False` do tipo `Bool`. -Uma valoração é uma lista de pares, e um átomo ausente da lista conta como -falso. +Podemos representar uma valoração como uma lista de pares, e um átomo ausente da lista conta como falso. ```lean abbrev Valuation := List (String × Bool) ``` -Se `V` é uma valoração, ela se estende a uma função que mapea qualquer fórmula para um valor de verdade. A extensão é definida por recursão sobre a estrutura da fórmula, um caso por construtor. Os construtores `top` e `bot` são constantes, nenhuma valoração os afeta. +Se `V` é uma valoração, ela se estende a uma função que mapea qualquer fórmula para um valor de verdade. A extensão é definida por recursão sobre a estrutura da fórmula, um caso por construtor. Os construtores `top` e `bot` são constantes, nenhuma valoração os afeta. Se um átomo ocorrer mais de uma vez, vamos assumir que seu valor verdade é a primeira ocorrência dele na lista, isto corresponde ao comportamento da função {name}`List.lookup`. ```lean def Form.eval (f : Form) (v : Valuation) : Bool := @@ -289,34 +223,46 @@ def Form.eval (f : Form) (v : Valuation) : Bool := | .disj g h => g.eval v || h.eval v ``` -Chamamos de *tautologias* (válidas) as fórmulas que são sempre verdade, independente da valoração. A notação usual para "`α` é uma tautologia" é `⊨ α`. As fórmulas que são sempre falsas para toda valoração são chamadas de *contradições* (ou insatisfatíveis) e podemos concluir que se `α` é uma contradição, então `⊨ ¬ α` (sua negação é válida). Uma fórmula é *satisfatível* se há ao menos uma valoração que a torna verdadeira. Uma fórmula é *contingente* se é satisfatível mas não é uma tautologia. Toda tautologia é satisfatível, mas nem toda fórmula satisfatível é uma tautologia. +Chamamos as fórmulas que são sempre verdade para qualquer valoração de suas variáveis proposicionais de *tautologias*, ou, simplesmente, fórmulas *válidas*. Se `α` é uma tautologia, significa que `⊨ α`, não depende de nenhuma hipótese para ser verdade. As fórmulas que são sempre falsas para toda valoração são chamadas de *contradições* (ou insatisfatíveis). Uma fórmula é *satisfatível* se há pelo menos uma valoração que a torna verdadeira. Uma fórmula é *contingente* se existe pelo menos uma valoração que torna a fórmula verdadeira e pelo menos uma que a torna falsa. Podemos concluir que se `α` é uma contradição, então `⊨ ¬ α` (sua negação é válida). Toda tautologia é satisfatível, mas nem toda fórmula satisfatível é uma tautologia. + +:::exercise (rating := 1) (name := "taut-contradiction") +Construa as valorações `vs1` e `vs2` de tal forma que os exemplos possam ser provados com a tática {tactic}`decide`. ```lean -def taut : Form := (.disj (.atom "p") (.neg (.atom "p"))) -def unsat : Form := (.conj (.atom "p") (.neg (.atom "p"))) +def form3 : Form := + .disj (.atom "p") (.conj (.atom "q") (.atom "r")) + +def form4 : Form := + .neg (.conj (.atom "p") (.neg (.atom "q"))) + +def form5 : Form := + .conj (.atom "a") (.impl (.neg (.atom "b")) (.atom "c")) + +def vs1 : List (String × Bool) := solution!([("p", true),("q", true)]) +def vs2 : List (String × Bool) := solution!([("a", true),("b", true)]) -#eval taut.eval [("p1", True)] -#eval taut.eval [("p1", False)] -#eval unsat.eval [("p", False)] -#eval unsat.eval [("p", True)] +example : form3.eval vs1 = true := by solution!(decide) +example : form4.eval vs1 = true := by solution!(decide) +example : form5.eval vs2 = true := by solution!(decide) ``` +::: A função a seguir gera a lista de todas as valorações sobre o conjunto dos nomes de átomos presentes em um termo do tipo `Form`. Com estas funções, podemos construir a tabela verdade de uma fórmula. ```lean def genVals : List String → List Valuation | [] => [[]] - | name :: names => - (genVals names).map ((name, true) :: ·) - ++ (genVals names).map ((name, false) :: ·) + | n :: ns => + let vs := (genVals ns) + vs.map ((n, true) :: ·) ++ vs.map ((n, false) :: ·) def Form.allVals (f : Form) : List Valuation := genVals f.propNames -#eval List.zip form2.allVals (form2.allVals.map (form2.eval ·)) +#eval List.zip form1.allVals (form2.allVals.map (form2.eval ·)) ``` -Para decidir se uma fórmula é tautologia, satisfatível ou contradição, podemos percorrer todas as valorações relevantes, que são finitas, porque uma fórmula tem finitos átomos. +Para decidir se uma fórmula é tautologia, satisfatível ou contradição, podemos percorrer todas as valorações possíveis, que são finitas, porque uma fórmula tem finitos átomos. ```lean def Form.tautology (f : Form) : Bool := @@ -326,13 +272,23 @@ def Form.satisfiable (f : Form) : Bool := f.allVals.any (fun v => f.eval v) def Form.contradiction (f : Form) : Bool := - !f.satisfiable + ¬ f.satisfiable -#eval (form1.contradiction, - (Form.neg form1).tautology, - form2.satisfiable) +#eval (form1.contradiction, (Form.neg form1).tautology, form1.satisfiable) ``` +:::exercise (rating := 1) (name := "def-contingente") +Complete a definição de fórmula contingente. Para provar o exemplo, use {tactic}`native_decide`. + +```lean +def Form.contingent (f : Form) : Bool := + solution!(f.satisfiable ∧ ¬ f.tautology) + +example : (Form.atom "q").satisfiable = true := by + solution!(native_decide) +``` +::: + A seguir, escrevemos implies para a relação de consequência lógica, chamando atenção para a relação entre `P ⊨ Q` e `⊨ P → Q`. Uma proposição `Q` é consequência lógica de `P` se, e somente se, a implicação `P → Q` é uma tautologia. Se `P → Q ≡ ¬ P ∨ Q ≡ ¬ (P ∧ ¬ Q)` então podemos também dizer que `P ⊧ Q` se e somente se `⊨ ¬ (P ∧ ¬ Q)`. Podemos estender para uma consequência lógica de fórmulas `{P₁, …, Pₙ} ⊧ α`, indicando que toda valoração que torna as fórmulas `P₁, …, Pₙ` verdadeiras também torna `α` verdadeira. O que equivale afirmar que a implicação da conjunção das premissas na conclusão é válida `⊧ (P₁ ∧ … ∧ Pₙ) → α`. @@ -347,86 +303,37 @@ def Form.equivalent (f g : Form) : Bool := f.implies g && g.implies f ``` -A nossa definição de `Form.impl` acima pode ser justificada pelas equivalência abaixo. A relação de equivalência entre fórmulas é transitiva. +:::exercise (rating := 2) (name := "equiv-cases") +Complete a definição de `Feq2` com uma fómula equivalente a `Feq1` e feche o exemplo com {tactic}`native_decide`. ```lean -#eval - let p := (.atom "p") - let q := (.atom "q") - - let α := (Form.impl p q) - let β := Form.disj (.neg p) q - let γ := Form.neg $ .conj p (.neg q) - - let r1 := [Form.equivalent α β, Form.equivalent β γ, Form.equivalent α γ] - let r2 := [Form.implies p (.disj p q), (Form.impl p (.disj p q)).tautology] - let r3 := [Form.implies (.disj p q) p, (Form.impl (.disj p q) p).tautology] - let r4 := [Form.implies (Form.conj p (.neg p)) q] - (r1, r2, r3, r4) +def p : Form := Form.atom "p" +def q : Form := Form.atom "q" + +def Feq1 : Form := Form.neg (.equi p q) +def Feq2 : Form := solution!(.disj (.conj (.neg p) q) (.conj (.neg q) p)) + +example : Feq1.equivalent Feq2 := by + solution!(native_decide) ``` +::: -A semântica da lógica proposicional também pode ser dada em formato de -*atualização*. Fixe primeiro um conjunto de valorações relevantes como -estado corrente e depois defina uma função de atualização que deixa apenas as -valorações que satisfazem uma dada fórmula. +A semântica da lógica proposicional também pode ser dada em formato de *atualização*. Fixe primeiro um conjunto de valorações como estado corrente e depois defina uma função de atualização que deixa apenas as valorações que satisfazem uma dada fórmula. ```lean def update (vals : List Valuation) (f : Form) : List Valuation := vals.filter (fun v => f.eval v) ``` -Atualizar o estado de todas as valorações relevantes com uma contradição não deixa nada; atualizar com uma tautologia não tira nada. Atualizar com uma fórmula contingente tira alguma coisa, e atualizar com sua negação tira o complemento. +Atualizar o estado de todas as valorações com uma contradição não deixa nada; atualizar com uma tautologia não tira nada. Atualizar com uma fórmula contingente tira alguma coisa, e atualizar com sua negação tira o complemento. ```lean +#eval form1.allVals #eval (update form1.allVals form1) #eval (update form1.allVals (.neg form1)) -#eval (form2.allVals.length, - (update form2.allVals form2).length, - (update form2.allVals (.neg form2))) +#eval (update form2.allVals (.neg form2)) ``` -::::exercise (rating := 1) (name := "valuation-table") -Seja `V` dada por `p ↦ 0`, `q ↦ 1`, `r ↦ 1`. Dê os valores das fórmulas -seguintes: `¬p ∨ p`, `p ∧ ¬p`, `¬¬(p ∨ ¬r)`, `¬(p ∧ ¬r)`, `p ∨ (q ∧ r)`. - -```lean -namespace ValuationTableEx - -def p := Form.atom "p" -def q := Form.atom "q" -def r := Form.atom "r" - -def vs : Valuation := - [("p", false), ("q", true), ("r", true)] - -example : - (Form.disj (.neg p) p).eval vs = solution!(true) := - by decide - -example : - (Form.neg (.neg (.conj p (.neg r)))).eval vs = solution!(false) := - by decide - -example : - (Form.neg (.conj p (.neg r))).eval vs = solution!(true) := - by decide - -example : - (Form.disj p (.conj q r)).eval vs = solution!(true) := - by decide - -end ValuationTableEx -``` -:::: - -::::exercise (rating := 1) (name := "negated-tautology") -Explique por que a negação de uma tautologia é sempre uma contradição, e vice-versa. - -:::solution -Uma fórmula `F` é tautologia quando `F.eval v = true` para toda `v`. Como `(Form.neg F).eval v = !(F.eval v)`, isso vale exatamente quando `(Form.neg F).eval v = false` para toda `v`, que é a definição de contradição. O argumento se lê igual nas duas direções. -::: -:::: - ::::exercise (rating := 2) (name := "implies-list") Estenda a checagem de implicação proposicional para o caso de uma lista de premissas. O tipo é `Form.impliesL : List Form → Form → Bool`. @@ -436,22 +343,21 @@ def Form.impliesL (ps : List Form) (c : Form) : Bool := ``` :::: -::::exercise (rating := 1) (name := "bangu-proof") -Como podemos identificar os torcedores do Bangu e os não torcedores, supondo que todos os depoimentos são verdadeiros? +:::exercise (rating := 1) (name := "bangu-proof") +Complete a definição de `banguSolution` para que a fórmula represente a solução do problema dos torcedores do Bangu F.C. assumindo que os 3 depoimentos foram verdadeiros. A prova do exemplo é completada com {tactic}`native_decide`. -:::solution ```lean -#eval Form.impliesL [depo1, depo2, depo3] A -#eval Form.impliesL [depo1, depo2, depo3] J -#eval Form.impliesL [depo1, depo2, depo3] C +def banguSolution : Form := solution!(.conjs [A, (.neg J), C]) + +example : Form.impliesL [depo1, depo2, depo3] banguSolution = true := + by solution!(native_decide) ``` ::: -:::: # Traduzindo `Form` para `Prop` -O capítulo começou distinguindo raciocinar em lógica proposicional de raciocinar sobre fórmulas dela. Temos que `p ∧ q` é uma proposição, do tipo `Prop` e `Form.conj p q` é um termo (dado) do tipo `Form`. A ligação é uma função que interpreta cada fórmula como a proposição que ela afirma, dada uma valoração. +O mapeamento de `Form` em `Prop` pode ser definido como uma função que interpreta cada fórmula como a proposição que ela afirma, dada uma valoração. ```lean def Form.denote (f : Form) (v : Valuation) : Prop := @@ -464,7 +370,7 @@ def Form.denote (f : Form) (v : Valuation) : Prop := | .disj g h => g.denote v ∨ h.denote v ``` -Repare no que cada caso faz: ele troca um construtor de `Form` pelo conectivo correspondente de `Prop`. O `conj` do dado vira o `∧` da proposição, o `neg` vira o `¬`. O teorema que fecha o capítulo diz que as duas leituras concordam: computar dá `true` exatamente quando a proposição vale. +Repare no que cada caso faz: ele troca um construtor de `Form` pelo conectivo correspondente de `Prop`. O `conj` do dado vira o `∧` da proposição, o `neg` vira o `¬`. O teorema que fecha o capítulo diz que as duas leituras concordam. Dada uma valoração, computar o valor verdade de uma fórmula resulta em `true` exatamente quando a proposição resultande da fórmula para a mesma valoração tem prova. ```lean theorem Form.eval_iff_denote (f : Form) (v : Valuation) : diff --git a/CSwL/Logic/Proof.lean b/CSwL/Logic/Proof.lean index dc14f8c..04060e6 100644 --- a/CSwL/Logic/Proof.lean +++ b/CSwL/Logic/Proof.lean @@ -9,386 +9,99 @@ open CSwLMeta set_option verso.code.warnLineLength 100 -#doc (Manual) "Prova em Lean" => +#doc (Manual) "Provas em Lean" => %%% tag := "Proof" %%% -Lógica, aqui, é duas coisas ao mesmo tempo. É o assunto — proposições, -conectivos, quantificadores, o que se segue de quê — e é a ferramenta com que -se escreve e se confere qualquer afirmação neste livro. Esta seção trata da -ferramenta: o tipo `Prop`, o que conta como prova em Lean, e as táticas com -que se constrói uma. As seções seguintes tratam do assunto, e o fazem -implementando cada lógica como um tipo de dado. +Neste capítulo vamos falar sobre o tipo `Prop` em Lean para representação de proposições lógicas em tipos dependentes. A representação de proposições e contrução de provas é o que torna Lean um assistente de prova, além de linguagem de programação. Vamos apresentar provas como termos e a construção de provas com táticas. -Supomos conhecida a lógica proposicional e a de predicados — sintaxe, -semântica, e a noção de consequência. Para uma apresentação a partir do -início, ver {citep Bib.logicandproof}[]. +Supomos conhecida a lógica proposicional e a de predicados — sintaxe, semântica, e a noção de consequência. Para uma apresentação a partir do início, ver {citep Bib.enderton2001}[]. ```lean namespace Proof - --- `square₁` and `square₂` are the two definitions of squaring from the Lean --- chapter; the examples below reuse them rather than introducing new ones. -open IntroL ``` -# O tipo Prop e Provas +# O tipo {lean}`Prop` e Provas O que diferencia Lean de outras linguagens como Python e Java é a capacidade de na mesma linguagem que usamos para 'programar' funções, escrevermos 'provas' sobre estas funções. -Nesta 'Exemplos extraídos de {citep Bib.FAA2025}[]. Uma proposição é um enunciado que pode ser verdadeiro ou falso. O enunciado `1 = 1` é verdadeiro, enquanto `square₁ 12 = 2` é falso. Toda proposição é todo tipo `Prop`. +Uma proposição é um enunciado que pode ser verdadeiro ou falso. O enunciado `1 = 1` é verdadeiro, enquanto `square₁ 12 = 2` é falso. Toda proposição é todo tipo `Prop` {citep Bib.FAA2025}[]. Podemos declarar proposições, mas não podemos _avaliar_ uma proposição. Note que perguntar pelo tipo não é o mesmo que decidir se ela é verdadeira. ```lean -#check square₁ 12 = 2 -``` +def p₁ : Prop := 1 = 1 +def p₂ : Prop := 1 = 2 +#check p₁ +#check p₂ -Podemos declarar proposições como a seguir e verificar que `1 = 1 : Prop`, mas não podemos _avaliar_ uma proposição. - -```lean -def p1 : Prop := 1 = 1 - -#check p1 +open IntroL in +#check square₁ 12 = 2 ``` -Toda proposição verdadeira tem uma prova, e uma prova é um _termo_ do tipo da proposição que testemunha a verdade da proposição. Provar `1 = 1` é exibir um termo de tipo `1 = 1`, exatamente o que o termo `Eq.refl 1` faz abaixo. Declarar um teorema é muito parecido com declarar uma função. +Em Lean, proposições são tipos e provas são termos desses tipos. Ou seja, provar algo é exatamente o mesmo ato de construir um valor em `Prop`. Por isso a forma mais direta de provar é escrever o termo à mão, do mesmo jeito que definimos qualquer função. Provar `1 = 1` é exibir um termo de tipo `1 = 1`, exatamente o que o construtor {name}`refl` do tipo {lean}`Eq` faz abaixo. Este tipo representa a relação de igualdade. Podemos notar que declarar um teorema é muito parecido com declarar uma função, como vimos em {ref "IntroL"}[IntroL]. ```lean theorem OneEqSelf : 1 = 1 := Eq.refl 1 ``` -A mesma ideia vale para dizer que duas funções são a mesma coisa — não é -analogia, é a proposição `f = g`, provável do mesmo jeito. Agora usando o -modo `tactic` iniciado com `by`. Usamos as taticas `rfl` e `intro` que iremos explicar a seguir. Com `example` não precisamos dar nomes a teoremas que não serão reusados. - -```lean -example : - ∀ (z : Nat), (λ x ↦ x * x) z = (fun y => y * y) z := by - intro n - rfl -``` - -Note que perguntar pelo tipo não é o mesmo que decidir se ela é verdadeira: - -```lean -#check (square₁ = square₂) -``` - -Provar é dar um termo cujo tipo é a proposição. Para uma igualdade em que -os dois lados reduzem ao mesmo valor, o termo é `rfl` — de _reflexividade_, -que é o princípio de que tudo é igual a si mesmo. Ver {citep Bib.love2026}[] -para uma explicação sobre `rfl`. - -```lean -theorem square₁_eq_square₂ : square₁ = square₂ := by - rfl -``` - -Escrito com `by`, `rfl` é uma _tática_: uma instrução para construir a -prova. Você pode inspecionar a definição de Lean para `Eq.refl`. - -```lean (name := c2print1) -#print square₁_eq_square₂ -``` - -```leanOutput c2print1 -theorem Proof.square₁_eq_square₂ : square₁ = square₂ := -Eq.refl square₁ -``` - -Além de `rfl`, um pequeno repertório de táticas resolve o que os capítulos -seguintes precisam — conferido nos próprios arquivos, não escolhido a -priori. A -ordem abaixo é a de {citep Bib.FAA2025}[], que apresenta as táticas nesta -sequência; `decide`, `omega`, `obtain`, `cases`, `simp` e `induction` não -vêm de lá (o curso os introduz onde a necessidade aparece) e ficam ao -final, fora da ordem do FAA2025: - -``` -rfl fecha a = b quando os dois lados calculam o mesmo valor -exact e fornece o termo que é a prova -intro h introduz uma hipótese, para provar uma implicação ou ∀ -constructor parte um ∧ ou um ↔ em dois objetivos -apply h aplica uma implicação ou lema, deixando a(s) premissa(s) - como novo(s) objetivo(s) -unfold nome desdobra uma definição, antes de continuar -rw [h] reescreve o objetivo usando a igualdade h, da esquerda para - a direita -assumption fecha o objetivo com uma hipótese já disponível -decide fecha um objetivo decidível calculando a resposta -omega resolve aritmética linear em Nat e Int -obtain ⟨_,_⟩ := h desmonta uma hipótese composta (conjunção, existencial) -cases h dado h : P ∨ Q, parte a prova em dois casos -simp [...] reescreve com um conjunto de lemas até não haver mais o que - simplificar -induction x prova por casos sobre a forma como x foi construído -funext x duas funções são iguais quando concordam em todo ponto -``` - -Duas notações de prova não são táticas: `⟨t, h⟩` monta um par (para provar -uma conjunção ou exibir a testemunha de um existencial), e `h.1`/`h.2` -desmontam um par que está numa hipótese. - -::::exercise (rating := 1) (name := "rfl-arithmetic") - -Termine a prova usando `rfl`. - -```lean -example : 7 * 6 = 42 := - solution!(rfl) -``` - -:::: - -::::exercise (rating := 1) (name := "square-unfold") - -Prove que `square₁ n = n * n`; uma variável aparece, então `rfl` não basta -sozinho — é preciso desdobrar a definição antes. - -```lean -example (n : Nat) : square₁ n = n * n := by - solution! - unfold square₁ - rfl -``` - -:::: - -::::exercise (rating := 1) (name := "identity-implication") - -Provar `P → Q` é: suponha `P`, derive `Q`. Prove `P → P`. Fonte: -{citep Bib.FAA2025}[] - -```lean -example (P : Prop) : P → P := by - solution! - intro h - exact h -``` - -:::: - -::::exercise (rating := 1) (name := "p-implies-q-implies-p") - -Complete a prova abaixo. Fonte: {citep Bib.FAA2025}[] - -```lean -example (P Q : Prop) : P → (Q → P) := by - solution! - intro h _ - exact h -``` - -:::: - -::::exercise (rating := 1) (name := "and-intro") - -Prove `P ∧ Q` a partir de `P` e de `Q`. Fonte: {citep Bib.FAA2025}[]. Dica: -`constructor` parte o objetivo `P ∧ Q` em dois; cada um se fecha com -`exact`. - -```lean (name := c2check24) -#check And.intro -``` - -```leanOutput c2check24 -And.intro {a b : Prop} (left : a) (right : b) : a ∧ b -``` - -```lean -example (P Q : Prop) (hP : P) (hQ : Q) : P ∧ Q := by - solution! - apply And.intro - · exact hP - · exact hQ -``` +Acontece que, para propriedades um pouco menos triviais, o termo para provar uma proposição pode ficar grande e pouco natural de escrever manualmente. É aí que entra a palavra `by`. Ela introduz um _modo_ chamado 'tactic mode' onde usamos uma pequena linguagem de comandos (táticas) em que descrevemos como a prova deve ser montada e deixamos o Lean construir o termo por nós. -:::: - -::::exercise (rating := 2) (name := "and-comm") - -Prove que a conjunção comuta. Fonte: {citep Bib.FAA2025}[]. Dica: um `↔` se parte em dois objetivos com -`constructor`; em cada um, `intro h` seguido de `obtain ⟨_,_⟩ := h` desmonta -a conjunção da hipótese, e `constructor` reconstrói a conjunção invertida. - -Veja também o que acontece ao avaliar `(10,20).1`. `And` em Lean é uma -`structure` com dois campos. +A tática {tactic}`rfl` prova igualdades quando os dois lados são iguais por definição, isto é, quando Lean consegue reduzi-los até a mesma expressão por computação. Essa redução inclui, por exemplo, a expansão de definições, a aplicação de funções e a avaliação de `let`. Isso é uma consequência importante da fundação de Lean em Calculus of Inductive Constructions (CiC) {citep Bib.nederpelt2014}[]: expressões de tipos e programas podem ser computadas e comparadas por redução. Assim, `rfl` é frequentemente usado para dizer que os dois lados são iguais porque são o mesmo valor depois de reduzir o código. O comando `#print double_theorem` irá mostrar que a tática {tactic}`rfl` construiu o termo {name}`Eq.refl`. ```lean -example (P Q : Prop) : P ∧ Q ↔ Q ∧ P := by - solution! - constructor - · intro h - obtain ⟨h1, h2⟩ := h - apply And.intro - · exact h2 - · exact h1 - · intro h - constructor - · exact h.2 - · exact h.1 -``` - -:::: - -::::exercise (rating := 1) (name := "implication-transitivity") - -Fonte: {citep Bib.FAA2025}[]. Dica: `intro`, depois `apply` duas vezes, -encadeando as duas hipóteses. - -```lean -example (P Q R : Prop) (h : P → Q) (h2 : Q → R) : - P → R := by - solution! - intro hp - apply h2 - apply h - exact hp -``` - -:::: - -::::exercise (rating := 1) (name := "apply-several-premises") +def double (n : Nat) := n + n -Adaptado de {citep Bib.FAA2025}[]. - -```lean -example (P Q R S : Prop) (h0 : P ∧ Q ∧ R) - (h : P → Q → R → S) : S := by - solution! - apply h - · exact h0.1 - · exact h0.2.1 - · exact h0.2.2 +theorem double_theorem : double 5 = 5 + 5 := by rfl ``` -:::: - -Nem toda prova precisa de lógica proposicional abstrata — às vezes o que -falta é desdobrar uma definição local antes de concluir. - -::::exercise (rating := 1) (name := "unfold-direct-proof") - -Fonte: {citep Bib.FAA2025}[], com `f` definida localmente igual ao arquivo. -Dica: `intro h`, `unfold f at h` (ou `rw [f] at h`), depois concluir por -`omega` ou `assumption`. +A tática {tactic}`rfl` tem limitações, embora possamos provar que duas funções são identificas a menos da sua mudança nos nomes dos parâmetros, precisamos do teorema sobre a comutatividade dos naturais para provar o segundo exemplo. ```lean -def f₁ (x y : Nat) : Prop := x = y +example (z : Nat) : (λ x ↦ 2 * x) z = (fun y => 2 * y) z := by + rfl -example (x : Nat) : f₁ x 1 → x ≠ 2 := by - solution! - intro h - unfold f₁ at h - omega +example (z : Nat) : (λ x ↦ 2 * x) z = (fun y => y * 2) z := by + exact Nat.mul_comm 2 z ``` -:::: - -::::exercise (rating := 1) (name := "unfold-conjunction") - -Fonte: {citep Bib.FAA2025}[]. +Além de {tactic}`rfl`, um pequeno repertório de táticas resolve o que os capítulos +seguintes precisam. -```lean -example (x y : Nat) : f₁ 0 x ∧ f₁ 0 y → x = y := by - solution! - intro h - obtain ⟨h1, h2⟩ := h - unfold f₁ at h1 h2 - omega -``` -:::: - -::::exercise (rating := 1) (name := "exists-witness") - -Prove que `∃ n : Nat, n + n = 10`, exibindo a testemunha com `⟨_, _⟩` ou -usando `Exists.intro`. - -```lean (name := c2check25) -#check Exists.intro -``` +::::exercise (rating := 1) (name := "rfl-arithmetic") -```leanOutput c2check25 -Exists.intro.{u} {α : Sort u} {p : α → Prop} (w : α) (h : p w) : Exists p -``` +Complete a prova abaixo usando a tática {tactic}`rfl`. Esta é a primeira prova que do [Natural Number Game](https://adam.math.hhu.de/#/g/leanprover-community/nng4/). O leitor está convidado a jogar NNG para uma boa introdução a provas no Lean. ```lean -example : ∃ n : Nat, n + n = 10 := by - solution! - apply Exists.intro 5 - rfl +example (x q : Nat) : 37 * x + q = 37 * x + q := + solution!(rfl) ``` - :::: -::::exercise (rating := 1) (name := "cases-on-or") - -Prove que `P ∨ Q → Q ∨ P`, usando `cases` sobre a hipótese, complete a -prova. - -```lean (name := c2check26) -#check Or.intro_left -``` - -```leanOutput c2check26 -Or.intro_left {a : Prop} (b : Prop) (h : a) : a ∨ b -``` +# Lógica Proposicional em Lean -```lean (name := c2check27) -#check Or.intro_right -``` +Os conectivos lógicos `∧`, `∨`, `→`, `↔` e `¬` estão disponíveis diretamente no Lean, de modo que uma fórmula proposicional pode ser representada como uma proposição em Lean. Isso nos fornece uma ponte conveniente entre a semântica da linguagem natural e o raciocínio formal. Podemos traduzir o conteúdo semântico de uma sentença para uma proposição em Lean e, em seguida, usar Lean para verificar se uma conclusão decorre de um conjunto de hipóteses. -```leanOutput c2check27 -Or.intro_right {b : Prop} (a : Prop) (h : b) : a ∨ b -``` +Vamos considerar um primeiro exemplo. Três irmãs — Ana, Maria e Cláudia — +foram a uma festa com vestidos de cores diferentes. Uma vestiu azul, a outra +branco, e a terceira, preto. Chegando à festa, o anfitrião perguntou quem era +cada uma delas. -```lean -example (P Q : Prop) : P ∨ Q → Q ∨ P := by - intro h - cases h with - | inl hp => - solution! - exact Or.inr hp - | inr hq => - solution! - exact Or.inl hq -``` +- A de azul respondeu: "Ana é a que está de branco"; +- A de branco disse: "Eu sou Maria"; +- A de preto respondeu: "Cláudia é quem está de branco". -:::: +O anfitrião foi capaz de identificar cada irmã considerando que: -# Prova por indução +- Ana sempre diz a verdade; +- Maria às vezes diz a verdade; +- Cláudia nunca diz a verdade. -A última tática da tabela, `induction`, prova algo para todo valor de um -tipo indutivo, e não para um valor de cada vez. - -::::exercise (rating := 1) (name := "add-zero-induction") - -Prove que `n + 0 = n` para todo `n`, usando `induction n`. No caso `0`, -`rfl` fecha; no caso `n + 1`, a hipótese de indução (`ih`) resolve `omega`. +Para começar, vamos introduzir variáveis do tipo `Prop`, cada uma delas representado uma proposição. São 3 pessoas e 3 cores. Vamos representar "Ana veste azul" por `Aa` e assim por diante. ```lean -example (n : Nat) : n + 0 = n := by - solution! - induction n with - | zero => rfl - | succ a ih => - -- try `apply?` - omega -``` +section PL -:::: - -Quem quiser praticar Lean provas em Lean, pode jogar o [Natural Number -Game](https://adam.math.hhu.de/#/g/leanprover-community/nng4/). - -# Lógica Proposicional em Lean - -O Lean possui `Prop`, como tipo predefinido, cujos elementos são proposições. Os conectivos lógicos `∧`, `∨`, `→`, `↔` e `¬` estão disponíveis diretamente no Lean, de modo que uma fórmula proposicional pode ser representada como uma proposição em Lean. Isso nos fornece uma ponte conveniente entre a semântica da linguagem natural e o raciocínio formal. Podemos traduzir o conteúdo semântico de uma sentença para uma proposição em Lean e, em seguida, usar Lean para verificar se uma conclusão decorre de um conjunto de hipóteses. - -Continuando a partir do quiz anterior. Para começar, vamos introduzir variáveis do tipo `Prop`, cada uma delas representado uma proposição. São 3 pessoas e 3 cores. Vamos representar "Ana veste azul" por `Aa` e assim por diante. - -```lean variable ( Aa Ab Ap Ma Mb Mp @@ -426,26 +139,26 @@ structure Premissas : Prop where hb1 : (Mb → ¬ Ab ∧ ¬ Cb) ∧ (Cb → ¬ Ab ∧ ¬ Mb) ∧ (Ab → ¬ Mb ∧ ¬ Cb) hp1 : (Mp → ¬ Ap ∧ ¬ Cp) ∧ (Cp → ¬ Ap ∧ ¬ Mp) ∧ (Ap → ¬ Mp ∧ ¬ Cp) - -- resposta 1 + -- da resposta 1 h1 : Aa → Ab h2 : Ca → ¬ Ab - -- resposta 2 + -- da resposta 2 h3 : ¬ Ab - -- resposta 3 + -- da resposta 3 h4 : Ap → Cb h5 : Cp → ¬ Cb ``` -Podemos então enunciar o problema do quiz na forma do teorema abaixo. Neste caso, +Podemos então enunciar o problema na forma do teorema abaixo. ```lean theorem vestidos (h : Premissas Aa Ab Ap Ma Mb Mp Ca Cb Cp) : Ap ∧ Cb ∧ Ma := sorry ``` -Consultar o tipo deste teorema com `#check vestidos` nos revela que ele tem o formato de uma implicação, que pode ser lido como `Γ ⊢ α` Do conjunto `Γ` de premissas em `Premissas` posso *derivar* `Ap ∧ Cb ∧ Ma`. A leitura é sintática. Podemos construir a prova de `α` a partir da aplicação de regras de dedução a partir das fórmulas de `Γ`. +Consultar o tipo deste teorema com `#check vestidos` nos revela que ele tem o formato de uma implicação, que pode ser lido como `Γ ⊢ α` Do conjunto `Γ` de premissas em `Premissas` posso *derivar* `Ap ∧ Cb ∧ Ma`. Em Lean podemos construir a prova de `α` a partir da aplicação de regras de dedução a partir das fórmulas de `Γ`. Chamamos "sistema dedutivo" um conjunto das regras de dedução. Existem vários sistemas dedutivos. A formalização de Prop em Lean corresponde a implementação do sistema chamado *dedução natural* definido por Gerhard Gentzen em 1930s. @@ -455,8 +168,7 @@ Neste sistema dedutivo, cada conectivo vem com dois tipos de regra: as de *intro variable {P Q R : Prop} ``` -A regra de introdução de `→` diz que para provar `P → Q`, supomos `P` e derivamos `Q`. A tatica `intro` move o antecedente para as hipóteses. A regra de eliminação é a chamada regra *modus ponens*. De `P → Q` e de `P`, conclua `Q`. Em Lean isso é aplicação `h hP` já é a prova de `Q`. A tática `apply` faz o mesmo de trás para frente, ela transforma o objetivo `Q` no objetivo `P`. - +A regra de introdução de `→` diz que para provar `P → Q`, supomos `P` e derivamos `Q`. A tatica `intro` move o antecedente para as hipóteses. A regra de eliminação é a chamada regra *modus ponens*. De `P → Q` e de `P`, conclua `Q`. Em Lean isso é aplicação `h hP` já é a prova de `Q`. A tática `apply` faz o mesmo de trás para frente, ela transforma o objetivo `Q` no objetivo `P`. A {tactic}`exact` fecha a prova indicando a hipótese cujo tipo corresponde ao _goal_ aberto. A {tactic}`assumption` fecha o _goal_ quando o tipo de alguma das hipóteses corresponde ao tipo do _goal_, sem precisarmos passar a hipótese nominalmente, como quando usamos {tactic}`exact`. ```lean example : P → (Q → P) := by @@ -467,7 +179,7 @@ example (h₁ : P → Q) (h₂ : Q → R) : P → R := by intro hP apply h₂ apply h₁ - exact hP + assumption example (h : P → Q) (hP : P) : Q := h hP ``` @@ -545,24 +257,7 @@ example (h : ¬¬P) : P := by exact h hn ``` -::::exercise (rating := 1) (name := "contrapositive") - -Prove a contrapositiva. Só uma das direções precisa de raciocínio clássico. - -```lean -example : (P → Q) ↔ (¬Q → ¬P) := solution!(by - constructor - · intro h hnQ hP - exact hnQ (h hP) - · intro h hP - by_contra hnQ - exact h hnQ hP) -``` - -:::: - ::::exercise (rating := 2) (name := "de-morgan") - Uma das leis de De Morgan vale construtivamente; a outra precisa do terceiro excluído. @@ -587,13 +282,28 @@ example : ¬(P ∧ Q) ↔ (¬P ∨ ¬Q) := solution!(by | inl hnP => exact hnP hand.1 | inr hnQ => exact hnQ hand.2) ``` +:::: + +::::exercise (rating := 1) (name := "contrapositive") +Prove a contrapositiva. Só uma das direções precisa de raciocínio clássico. + +```lean +example : (P → Q) ↔ (¬Q → ¬P) := solution!(by + constructor + · intro h hnQ hP + exact hnQ (h hP) + · intro h hP + by_contra hnQ + exact h hnQ hP) +``` :::: + ::::exercise (rating := 1) (name := "exchange-prop") Complete a representação do argumento abaixo em linguagem lógica. -> Se o câmbio cair, temos inflação. Se as exportações crescerem, diminuímos o déficit. O câmbio cai ou diminuímos o déficit. Logo, temos inflação ou as exportações crescem. +Se o câmbio cair, temos inflação. Se as exportações crescerem, diminuímos o déficit. O câmbio cai ou diminuímos o déficit. Logo, temos inflação ou as exportações crescem. ```lean section @@ -613,8 +323,69 @@ end ``` :::: +::::exercise (rating := 2) (name := "and-comm") +Prove que a conjunção é comutativa. + +```lean +example (P Q : Prop) : P ∧ Q ↔ Q ∧ P := by + solution!( + constructor + · intro h + obtain ⟨h1, h2⟩ := h + apply And.intro + · exact h2 + · exact h1 + · intro h + constructor + · exact h.2 + · exact h.1) +``` +:::: + +::::exercise (rating := 1) (name := "implication-transitivity") +Complete a prova abaixo. + +```lean +example (P Q R : Prop) (h : P → Q) (h2 : Q → R) : P → R := by + solution!( + intro hp + apply h2 + apply h + exact hp) +``` +:::: + +::::exercise (rating := 1) (name := "unfold-direct-proof") +Em algumas provas, podemos precisar expandir uma definição antes de qualquer outro passo de manipulação dos conectivos lógicos. Logo após introduzir o antecedente da implicaçõa como hipótese, considere `unfold E at h` para expandir a definição de `E` na hipótese recém introduzida `h`. Feche a prova com a táctica {tactic}`linarith`. + +```lean +def E (x y : Nat) : Prop := x = y + +example (x : Nat) : E x 1 → x ≠ 2 := by + solution!( + intro h + unfold E at h + linarith) +``` +:::: + +::::exercise (rating := 1) (name := "unfold-rw-conjunction") +Na prova abaixo, o antecedente da implicação precisa ser transformado em hipótese, digamos `h`. Em seguida podemos expandir a definição de `E`. Neste momento, {tactic}`linarith` já fecharia a prova. Mas sugerimos uma solução mais manual, obter duas novas hipóteses `h1` e `h2` a partir de `h` (eliminação da conjunção). A partir dai, podemos reescrever o _goal_ com as hipóteses da forma `h : α = β` com `rewrite [h]` quer irá reescrever o _goal_ trocando as ocorrências de `α` por `β`. Finalmente, fechamos a prova com {tactic}`rfl`. Experimente também a variação de `rewrite` chamada `rw`, que tenta aplicar {tactic}`rfl` logo após as reescritas. + +```lean +example (x y : Nat) : E x 0 ∧ E y 0 → x = y := by + solution!( + intro h + unfold E at h + obtain ⟨h1, h2⟩ := h + rewrite [h1,h2] + rfl) +``` +:::: + + ::::exercise (rating := 2) (name := "dresses") -Complete a prova do teorema que responde o quiz anterior. +Complete a prova do teorema, provando que o problema dos vestidos tem a solução onde Ana veste preto, Cláudia veste branco e Maria veste azul. ```lean theorem vestidos₁ (h : Premissas Aa Ab Ap Ma Mb Mp Ca Cb Cp) @@ -648,8 +419,8 @@ theorem vestidos₁ (h : Premissas Aa Ab Ap Ma Mb Mp Ca Cb Cp) ) have hMa : Ma := by - solution!( rcases ha with hMa | hAa | hCa + solution!( · exact hMa · exact absurd hAa hnAa · exact absurd hCa hnCa @@ -657,22 +428,27 @@ theorem vestidos₁ (h : Premissas Aa Ab Ap Ma Mb Mp Ca Cb Cp) exact ⟨hAp, hCb, hMa⟩ ``` - :::: +```lean +end PL +``` + # As regras dos quantificadores em Lean -O Lean se baseia em na teoria dos tipos, na qual se assume que cada variável pertence a algum tipo. Você pode pensar em um tipo como um "universo" ou um "domínio de discurso", no sentido da lógica de primeira ordem. +O mesmo tipo `Prop` em Lean não está limitado ao raciocínio proposicional. Também podemos representar lógica de primeira ordem em `Prop`. Como já falamos, o Lean se baseia em na teoria dos tipos, na qual se assume que cada variável pertence a algum tipo. Você pode pensar em um tipo como um "universo" ou um "domínio de discurso", no sentido da lógica de primeira ordem. Com a diferença importante de que em lógica de primeira ordem, entedemos o domínio da interpretação com um conjunto não vazio, e um tipo em Lean não necessariamente precisa ser _habitado_. -Seguindo a apresentação de Lógica Proposicional, quatro novas regras precisam ser explicadas, duas para cada quantificador. +A expressividade de `Prop` vai além de lógica de primeira ordem. Poderíamos ainda falar de lógicas [polissortidas](https://en.wikipedia.org/wiki/First-order_logic) onde poderíamos ter mais de um tipo usado em uma mesma expressão lógica. Por exemplo, podemos querer usar a lógica de primeira ordem para geometria, com quantificadores sobre pontos e linhas. Mas nesta seção, nos restringimos os predicados a um único universo `U`. ```lean -section +section FOL variable (U : Type) variable (P Q : U → Prop) ``` +Seguindo a apresentação de Lógica Proposicional, quatro novas regras precisam ser explicadas, duas para cada quantificador. + A introdução de `∀` diz que para provar que algo vale de todo `x`, tome um `x` arbitrário e prove que vale para ele. É a mesma `intro` agora sobre um objeto em vez de uma hipótese. A eliminação de `∀` é aplicação: de `∀ x P x` e de um objeto `d`, sai `P d`. @@ -706,9 +482,7 @@ example (h : ∃ x, P x ∧ Q x) : ∃ x, Q x := by exact ⟨d, hQ⟩ ``` -Podemos ainda considera uma lógica de múltiplos tipos, onde podemos ter múltiplos universos. Por exemplo, podemos querer usar a lógica de primeira ordem para geometria, com quantificadores sobre pontos e linhas. Mas acima restringimos os predicados a um único universo `U`. - -A demonstração abaixo não é válida se não declararmos uma variável `u : U`, mesmo que `u` não apareça no enunciado do teorema. Isso destaca uma diferença entre a lógica de primeira ordem e a lógica implementada em Lean. Na dedução natural, podemos provar `∀ x P x → ∃ x P x`, o que mostra que nosso sistema de prova assume implicitamente que o universo tem pelo menos um objeto. Em contraste, a afirmação `(∀ x : U, P x) → ∃ x : U, P x` não é demonstrável em Lean. Em outras palavras, em Lean, é possível que um tipo esteja vazio, e, portanto, a prova acima requer uma suposição explícita de que existe um elemento `u : U`. +A demonstração abaixo não é válida se não declararmos uma variável `u : U`, mesmo que `u` não apareça no enunciado do teorema. Isso destaca uma diferença entre a lógica de primeira ordem e a lógica implementada em Lean. Na dedução natural, podemos provar `∀ x P x → ∃ x P x`, o que mostra que nosso sistema de prova assume implicitamente que o universo tem pelo menos um objeto. Em contraste, em Lean, é possível que um tipo esteja vazio, e, portanto, a prova requer uma suposição explícita de que existe um elemento `u : U`. ```lean variable (u : U) @@ -717,13 +491,10 @@ example: (∀ x , P x) → ∃ x, P x := by intro h use u exact h u - -end ``` ::::exercise (rating := 2) (name := "forall-exists-swap") - -Prove o primeiro exemplo. +Prove o exemplo abaixo e reflita sobre porque não podemos substituir `→` por `↔`. ```lean example {U : Type} (R : U → U → Prop) : @@ -734,15 +505,37 @@ example {U : Type} (R : U → U → Prop) : intro x exact ⟨d, hd x⟩) ``` +:::: -Explique porque a volta da implicação não vale. - -:::solution -A volta não vale. De `∀x ∃y Rxy` cada `x` pode ter a sua testemunha, e nada obriga que seja a mesma para todos. -::: +::::exercise (rating := 1) (name := "exists-witness") +Prove que `∃ n : Nat, n + n = 10`, exibindo a testemunha. Você pode usar {name}`Exists.intro`. +```lean +example : ∃ n : Nat, n + n = 10 := by + solution!( + apply Exists.intro 5 + rfl) +``` :::: +# Prova por indução + +Outra tática de prova que podemos precisar é a {tactic}`induction`. Ela prova algo para todo valor de um tipo indutivo, e não para um valor de cada vez. + +Considere o exemplo abaixo e a esperada _prova por indução_ que faríamos no papel. Mostramos para o caso base, que em `Nat` é o `zero` e depois o passo indutivo, cuja hipótese de indução é nomeada como `ih`. + +```lean +example (n : Nat) : n + 0 = n := by + induction n with + | zero => rfl + | succ a ih => + linarith +``` + +Ao longo do texto, outras táticas poderão ser usadas como: {tactic}`decide`, {tactic}`omega`, {tactic}`simp` e {tactic}`funext`, discutiremos quando forem necessárias. + + ```lean +end FOL end Proof ``` diff --git a/DEVIATIONS.md b/DEVIATIONS.md index 06b8974..e541ba7 100644 --- a/DEVIATIONS.md +++ b/DEVIATIONS.md @@ -190,7 +190,8 @@ Two consequences of moving 2.3 here: ### 3. `Logic.lean` — CSwFP/4.4–4.7, 5.2, 5.3, 5.5 -Two files, `PL.lean` (propositional logic) and `FOL.lean` (predicate logic). +Three files: `Proof.lean` (proving in Lean), `PL.lean` (propositional logic) +and `FOL.lean` (predicate logic), included in that order. Doing logic in Lean is doing deduction — `intro` is →-introduction, `constructor` is ∧-introduction, `cases` on ∨ is ∨-elimination. CSwFP reaches deduction only in its inference engine (5.7); here it arrives with the logic itself. Deduction lives at the meta level, in `Prop` and the tactics. @@ -204,14 +205,23 @@ state. Those two carry a structure of their own, and part 1's subheadings, one per connective, are that structure — a connective's introduction and elimination rules are what the section teaches. -**The chapter's opening example is not CSwFP's.** The potassium/chlorine -example that opens `PL.lean` — `K` for "traces of potassium were observed", `C` -for "the sample contained chlorine", and the four compound sentences built from -them — has no counterpart in CSwFP, whose chapter 4 opens with Sea Battle. It -is adapted from the opening of Enderton's *A Mathematical Introduction to -Logic*, and the prose cites it as such. Recorded here because the rest of parts -2 and 3 *are* translations, and a reader of this document checking coverage -would otherwise look for a source passage that does not exist. +**The chapter's opening example was not CSwFP's, and is now removed.** +`PL.lean` used to open with a potassium/chlorine example adapted from +Enderton's *A Mathematical Introduction to Logic* — `K` for "traces of +potassium were observed", `C` for "the sample contained chlorine", four +compound sentences, and a truth table over them. It had no counterpart in +CSwFP, whose chapter 4 opens with Sea Battle. It went out with the chapter +reorganization of 2026-09-13: it taught the basics of propositional logic, +which `Proof.lean` now declares a prerequisite, and the chapter's own subject +is formalizing rather than logic itself. The `enderton2001` entry is left in +`Bib.lean`, unused. Recorded because a reader checking coverage might look for +a source passage for the old opening and find none. + +**The dresses puzzle moved to `Proof.lean`.** It was a `:::quiz` in `PL.lean`'s +introduction, and the section "Lógica Proposicional em Lean" — now in +`Proof.lean` — formalized it, opening with "Continuando a partir do quiz +anterior". The move broke that reference, so the puzzle goes with the section +that solves it, no longer as a quiz but as the worked example that opens it. Two divergences inside the translated part. CSwFP gives the semantics in two sections, "Semantics of Propositional Logic" and "Propositional Reasoning in @@ -220,14 +230,45 @@ the definitions are already Lean from the first line, so there is no later point at which implementation begins. And the discussion of what an empty conjunction should be worth does not arise, since `top` and `bot` are constructors. -`PL.lean`, in this internal order: - -1. **`Prop` and proof in Lean** — meta level. Tactics presented as the rules they are, building on the `rfl`/`intro`/`exact`/`decide` of `IntroL.lean`: `apply`; `constructor` and anonymous constructors for ∧ and ↔; `left`, `right` and `cases` for ∨; `False.elim` and `absurd` for ¬; `by_contra`, `by_cases` and `em` for classical reasoning. -2. **`Form` as syntax** — object level: BNF, `inductive Form` (4.4). Glued to it, the section that separates the two levels: `Form.conj p q` is data, `p ∧ q` is a proposition. Glued, not deferred to the end of the chapter — the confusion is born the instant the `inductive` appears. This is a cost Lean creates and Haskell does not have: there the meta level is invisible, living in the prose, so `data Form = ...` cannot be confused with it. -3. **Valuation** — 5.2 and 5.3: truth tables, consequence, `update` over valuations. -4. **The bridge** — interpreting `Form` into `Prop` and proving `eval v F = true ↔ ⟦F⟧`. Where deduction and valuation meet. CSwFP cannot have this section. - -`FOL.lean` mirrors `PL.lean`'s order: the quantifier rules first — `intro`/`apply` for ∀, `use` and `obtain` for ∃ — then 4.5, 4.6, 4.7, then 5.5. Putting the tactics last would have made the two logic chapters teach the same thing in opposite positions, for no reason. +**Proving in Lean is a section of its own, and it comes first.** The meta +level used to be presented three times: `IntroL.lean` introduced `Prop`, and +then `PL.lean` and `FOL.lean` each opened with the tactics for their own +connectives before reaching their actual subject. `Proof.lean` now holds all of +it — `Prop`, what counts as a proof, the tactics as the rules they are +(`apply`; `constructor` and anonymous constructors for ∧ and ↔; `left`, `right` +and `cases` for ∨; `False.elim` and `absurd` for ¬; `by_contra`, `by_cases` and +`em`; `intro`/`apply` for ∀, `use` and `obtain` for ∃), and proof by induction. +`IntroL.lean` is left deliberately pre-proof: it uses `example`, `theorem` and +`rfl` only as the shape an exercise's tests take, and says so. + +The consequence is that `PL.lean` and `FOL.lean` now have the same three-part +shape, and neither teaches Lean tactics: + +1. **Syntax as data** — BNF, then the `inductive` (4.4 for `Form`, 4.5–4.7 for `Formula`). Glued to it in `PL.lean`, the section that separates the two levels: `Form.conj p q` is data, `p ∧ q` is a proposition. Glued, not deferred — the confusion is born the instant the `inductive` appears. This is a cost Lean creates and Haskell does not have: there the meta level is invisible, living in the prose, so `data Form = ...` cannot be confused with it. +2. **Computable semantics** — 5.2 and 5.3 for `Form.eval` over valuations; 5.5 for `Formula.eval` over a model. +3. **The bridge** — interpreting the syntax into `Prop` (`denote`) and proving that the two readings agree. CSwFP cannot have this section. + +**`FOL.lean` gained its bridge section.** It previously had only a `Prop`-valued +`Formula.holds` and no computable semantics at all, so the chapter did not in +fact mirror `PL.lean` — there was nothing to bridge *from*. It now follows the +same shape: `Interp` returns `Bool` and `Formula.eval` computes, `Denot` returns +`Prop` and `Formula.denote` interprets, and `Formula.eval_iff_denote` relates +them. The bridge theorem needs a hypothesis `PL.lean`'s does not: `eval` decides +a quantifier by walking a list `dom`, so it agrees with the `∀` of Lean only +when `dom` lists every element of the domain. That hypothesis is the formal +counterpart of a real limitation, and the prose says so. + +**The model in `FOL.lean` is CSwFP/6's, in fragment.** Chapter 6's model +(`src/Model.hs`) is pulled forward to give 5.5 something concrete to evaluate +against: ten of the twenty-seven entities, and eight predicates +(`girl`, `boy`, `princess`, `dwarf`, `giant`, `child`, `love`, `defeat`) with +the original's extensions, restricted to the entities kept. The one place this +bites is `defeat`, which in the original is the dwarf/giant rule *plus* the +pairs `(A,W)` and `(A,V)`; the wizards `W` and `V` are outside the fragment, so +only the rule survives. The natural-language translation that chapter 6 +builds on it is not pulled forward — only the model. The previous example was a +three-element `Nat` domain with predicates named `P` and `R`, which could not +show why a *finite, listed* domain is what makes evaluation possible. **`Formula` is binary too.** Its `conj` and `disj` take two arguments, with `top` and `bot` as constructors and `Formula.conjs`/`Formula.disjs` recovering the n-ary notation — the same design as `Form`, for the same reason. A constructor holding a `List (Formula α)` would make the type a nested inductive, costing `induction` and `deriving`. The `List α` in `atom name (args : List α)` does not: `α` is a parameter, not the type being defined, so an atom may still take any number of arguments. With that, the definition of truth in 5.5 is a plain recursion, one case per constructor, instead of three mutually recursive functions. The one `mutual` block left in the chapter belongs to `Term`, where a list of terms inside `Term` is what function symbols of arbitrary arity require. diff --git a/PROVENANCE.md b/PROVENANCE.md index 33eeb8e..a663ed0 100644 --- a/PROVENANCE.md +++ b/PROVENANCE.md @@ -210,6 +210,13 @@ consequences hold, 5.19 and 5.20 the same for predicate logic. In this book loses its point. They are worth keeping only if reformulated as proofs about the definitions rather than queries against them. +For 5.19 and 5.20 this became true only with the chapter reorganization of +2026-09-13, which gave `FOL.lean` a computable `Formula.eval`; before that the +chapter had no way to evaluate a formula at all, and the two were unported for +want of a semantics rather than for having too easy an answer. They are now the +strongest candidates for reformulation as proofs, since the model to state them +against is in the chapter. + **It asks for a variant implementation.** CSwFP/5.11 asks for a check of logical equivalence, which `Form.equivalent` already is; 5.12 asks to reimplement the semantics with `[String]` instead of `[(String, Bool)]` for valuations. diff --git a/STYLE-CODE.md b/STYLE-CODE.md index 4950aeb..c4a98e2 100644 --- a/STYLE-CODE.md +++ b/STYLE-CODE.md @@ -51,13 +51,13 @@ this way is usually a mistake; see "Known gaps." | Chapter | Commands and declarations | Types and syntax | Tactics | | --- | --- | --- | --- | | `IntroCS` | `namespace`, `def` (by pattern matching), `inductive`, `deriving Repr`, `example`, `#eval` | `Nat`, function type `→`, dot-notation constructors (`.num`) | `rfl`, `induction … with`, `rw`, `rewrite`, `unfold`, `repeat` | -| `IntroL` | `#check`, `#print`, `theorem`, `structure`, `instance`, `section`, `variable` | `Type`, `Prop`, `Bool`, `List`, `Option`, `Char`, `String`, `fun`/`λ`, `match`, `if … then … else`, `⟨…⟩`, implicit `{}`, instance-implicit `[]`, `∘`, `BEq` | `funext`, `show` (solution only), `omega` (solution only), `decide` (solution only) | +| `IntroL` | `#check`, `#print`, `theorem`, `structure`, `instance`, `section`, `variable` | `Type`, `Prop`, `Bool`, `List`, `Option`, `Char`, `String`, `fun`/`λ`, `match`, `if … then … else`, `⟨…⟩`, implicit `{}`, instance-implicit `[]`, `∘`, `BEq`, `List.all`/`List.any` | `funext`, `show` (solution only), `omega` (solution only), `decide` (solution only) | | `Logic/Proof` | `open` | `¬`, `∀`, `∃`, `∧`, `∨`, `↔`, `≠` | `intro`, `exact`, `apply`, `cases … with`, `constructor`, `obtain`, `have`, `use`, `left`, `right`, `rcases`, `by_cases`, `by_contra` | | `Logic/PL` | `abbrev`, `private` | `DecidableEq`, `×` | `simp` | -| `Logic/FOL` | `mutual` | `\|>` | — | +| `Logic/FOL` | `mutual`, `deriving BEq` | `\|>`, `List.contains` | `induction … generalizing` | | `Sets` | — | `Set`, `Rel`, `Finset`, `Fintype`, `Setoid`, `∈`, `⊆`, `∪`, `∩` | `assumption`, `trivial`, `symm`, `simp_all` | | `SeaBattle` | — | `Fin` | `native_decide` | -| `Morphology` | `deriving BEq` | — | — | +| `Morphology` | *(none new)* | — | — | | `InfEngine` | — | `do`-notation | — | | `English` | *(none new)* | *(none new)* | *(none new)* | From fbbab5b4d3e8f5503dc701eb8635313bdae960e3 Mon Sep 17 00:00:00 2001 From: Alexandre Rademaker Date: Sun, 13 Sep 2026 23:16:10 -0300 Subject: [PATCH 04/14] feat(html): give each section of Logic its own page The two-level table of contents was self-imposed. `htmlSplit := .never` means "do not split here nor in any part below", so a glue chapter carrying it keeps its sections on one page however long they grow. Dropping it from `Logic.lean` lets Verso split at the depth it already defaults to (`htmlDepth := 2`), which is what fp-lean runs. Each section declares `file := "Tag"`, without which Verso builds the URL by sluggifying the Portuguese title and the accents come out mangled (`L___gica-proposicional`). _out//html/Logic/{index,Proof,PL,FOL}/ The chapter index is now a 63 KB landing page instead of carrying all three sections, and the four variants render with no unresolved cross-references. The generated Lean is untouched, verified byte-identical against a snapshot taken before the change: `walkOuter` writes one file per chapter and reads `file :=` only from chapters, while `walkSection` takes its target file as a parameter and never consults metadata. Splitting that output by section is the other half of the issue and is left for its own commit, since it has to invert `chapterImports`, which currently flattens a section's imports into its chapter on purpose. Refs #11 AI-usage disclosure: this change was developed with the assistance of Claude Opus 5 (Anthropic, September 2026, via Claude Code). The author decided to take up the issue and reviewed the result; the assistant made the edits, checked the claims the issue made against Verso's source and the extractor's, and verified the generated Lean was unchanged. The author takes full responsibility for the final content. --- CSwL/Logic.lean | 1 - CSwL/Logic/FOL.lean | 1 + CSwL/Logic/PL.lean | 4 ++++ CSwL/Logic/Proof.lean | 1 + STYLE-CODE.md | 10 ++++++++++ 5 files changed, 16 insertions(+), 1 deletion(-) diff --git a/CSwL/Logic.lean b/CSwL/Logic.lean index 6d10eeb..b52cd3d 100644 --- a/CSwL/Logic.lean +++ b/CSwL/Logic.lean @@ -15,7 +15,6 @@ open Verso Genre Manual #doc (Manual) "Lógica" => %%% tag := "Logic" -htmlSplit := .never file := "Logic" %%% diff --git a/CSwL/Logic/FOL.lean b/CSwL/Logic/FOL.lean index 3ad7e12..2ce6eb9 100644 --- a/CSwL/Logic/FOL.lean +++ b/CSwL/Logic/FOL.lean @@ -10,6 +10,7 @@ set_option verso.code.warnLineLength 100 #doc (Manual) "Lógica de predicados" => %%% tag := "FOL" +file := "FOL" %%% ```lean diff --git a/CSwL/Logic/PL.lean b/CSwL/Logic/PL.lean index dfc3cf0..19a564a 100644 --- a/CSwL/Logic/PL.lean +++ b/CSwL/Logic/PL.lean @@ -11,6 +11,7 @@ set_option verso.code.warnLineLength 100 #doc (Manual) "Lógica proposicional" => %%% tag := "PL" +file := "PL" %%% ```lean @@ -356,6 +357,9 @@ example : Form.impliesL [depo1, depo2, depo3] banguSolution = true := # Traduzindo `Form` para `Prop` +%%% +tag := "pl-to-prop" +%%% O mapeamento de `Form` em `Prop` pode ser definido como uma função que interpreta cada fórmula como a proposição que ela afirma, dada uma valoração. diff --git a/CSwL/Logic/Proof.lean b/CSwL/Logic/Proof.lean index 04060e6..620085f 100644 --- a/CSwL/Logic/Proof.lean +++ b/CSwL/Logic/Proof.lean @@ -12,6 +12,7 @@ set_option verso.code.warnLineLength 100 #doc (Manual) "Provas em Lean" => %%% tag := "Proof" +file := "Proof" %%% Neste capítulo vamos falar sobre o tipo `Prop` em Lean para representação de proposições lógicas em tipos dependentes. A representação de proposições e contrução de provas é o que torna Lean um assistente de prova, além de linguagem de programação. Vamos apresentar provas como termos e a construção de provas com táticas. diff --git a/STYLE-CODE.md b/STYLE-CODE.md index c4a98e2..11af45c 100644 --- a/STYLE-CODE.md +++ b/STYLE-CODE.md @@ -132,6 +132,16 @@ that only gathers them via `{include 1 …}` from a same-named directory. Each content file has its own `namespace`: the book redefines the same names in different chapters, deliberately. +`htmlSplit := .never` keeps a chapter on one HTML page, and also forbids the +split in every part below it. A single-file chapter should keep it. A glue +chapter should *omit* it, so each included section becomes its own page under +the chapter's directory — `Logic/PL/`, `Logic/FOL/` — which is what the +default `htmlDepth` of 2 already asks for. A section that gets its own page +needs its own `file := "Tag"`, or Verso builds the URL by sluggifying the +Portuguese title and the accents come out mangled (`L___gica-proposicional`). +None of this affects the generated Lean: the saver writes one file per +chapter, and reads `file :=` only from chapters. + ### Exercises and solutions `:::exercise (rating := N) (name := "mnemonic")` — an exercise. `rating` is From 47e435aab3131106a0d821ceb1536526e7c397a9 Mon Sep 17 00:00:00 2001 From: Alexandre Rademaker Date: Mon, 14 Sep 2026 00:20:51 -0300 Subject: [PATCH 05/14] fix(save): restore `file := "IntroL"`, which an import resolves against MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `file := "introL"` renamed the generated chapter to `CSwL/introL.lean`, and `import CSwL.IntroL` then stopped reaching the extracted project: the generated `Morphology.lean` lost it and failed with `Unknown identifier IntroL.initS`, plus every test downstream of `swedishPlural`. `chapterModules` is built from the *generated* file names, so it held `CSwL.introL` while the source header says `CSwL.IntroL`. The two no longer matched, `isSection` (`CSwLMeta/Save/Project.lean`) concluded the import named a section whose content had been merged into its chapter, and dropped it — the one disposal path that does not reach the `reportError` for an unresolvable import a few lines below. A `file :=` that does not match the module name therefore removes an import in silence. Introduced in 57a6702, found while building the generated project for #22. Refs #22 AI-usage disclosure: this change was developed with the assistance of Claude Opus 5 (Anthropic, September 2026, via Claude Code). The assistant traced the missing import to the case mismatch and confirmed it by regenerating with the name restored; the author reviewed the result. The author takes full responsibility for the final content. --- CSwL/IntroL.lean | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CSwL/IntroL.lean b/CSwL/IntroL.lean index d40f0c9..6823c19 100644 --- a/CSwL/IntroL.lean +++ b/CSwL/IntroL.lean @@ -9,7 +9,7 @@ open CSwLMeta %%% tag := "IntroL" htmlSplit := .never -file := "introL" +file := "IntroL" %%% Neste capítulo, apresentamos o essencial sobre a linguagem de programação Lean. Nosso objetivo é apresentar o suficiente para que o leitor possa acompanhar os exemplos do restante do livro. Para uma apresentação completa, sugerimos a leitura de {citep Bib.FPiL}[] e {citep Bib.LLR}[]. From 5e3fce1e41f1a3e945b791d21a3680dc8f407c0c Mon Sep 17 00:00:00 2001 From: Alexandre Rademaker Date: Mon, 14 Sep 2026 00:21:41 -0300 Subject: [PATCH 06/14] fix(seabattle): define `gapShip` outside the block expected to fail MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `gapShip` is a well-formed definition that happened to share a ```lean +error fence with `badState`, the definition the fence is there to reject. In the book both elaborate in place and nothing is wrong. In the extracted project the fence becomes an indented `sf_expect_failure` block, so `gapShip` was defined *inside* it and the three examples below, at file scope, could not see it — reported as `Expected type must not contain free variables` from the `native_decide` that needed it. It now has a fence of its own, immediately before, and the `+error` fence holds only what is meant to fail. The issue attributed this to `solution!(by …)` emitting `(by …)`. That is not the cause: removing the parentheses by hand leaves the error exactly where it was, and the parenthesised form elaborates fine. Refs #22 AI-usage disclosure: this change was developed with the assistance of Claude Opus 5 (Anthropic, September 2026, via Claude Code). The assistant tested the parenthesis hypothesis the issue proposed, found it did not hold, and located the scoping cause; the author reviewed the result. The author takes full responsibility for the final content. --- CSwL/SeaBattle.lean | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/CSwL/SeaBattle.lean b/CSwL/SeaBattle.lean index 3765bc3..b00e9c0 100644 --- a/CSwL/SeaBattle.lean +++ b/CSwL/SeaBattle.lean @@ -297,10 +297,11 @@ testar depois, é um erro do compilador. `gapShip` tem uma lacuna na coluna B, então nenhum `State` pode conter só esse navio: a prova de `shipsOK` que a `structure` exige não existe. -```lean +error -/-- Contraexemplo: mesma linha, com lacuna na coluna B. -/ +```lean def gapShip : Grid := [(.A, 0), (.C, 0)] +``` +```lean +error def badState : State := { ships := [gapShip] attacks := [] @@ -333,15 +334,12 @@ def addShip (ship : Grid) (s : State) : Option State := example : (addShip [(.A, 0), (.A, 1)] exampleState).isSome := solution!(by native_decide) -/-- `gapShip` não é um navio válido: a adição falha. -/ example : addShip gapShip exampleState = none := solution!(by native_decide) -/-- `destroyerCells` já ocupa células de `exampleState`: colide. -/ example : addShip destroyerCells exampleState = none := solution!(by native_decide) ``` - :::: A semântica de uma reação depende do estado do jogo e da posição do último ataque. Dado um estado `s`, uma posição `p` e uma reação `r`, queremos dizer quando `r` é a reação verdadeira: From b60879f46e995cbe9623e36107f1e19a0e4c5850 Mon Sep 17 00:00:00 2001 From: Alexandre Rademaker Date: Mon, 14 Sep 2026 00:24:50 -0300 Subject: [PATCH 07/14] fix(logic,sets): write `solution!` in the form each solution needs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three solutions were written in a shape that only survives while the marker is there. The `solutions` variant strips the marker in place, and what was left did not parse or did not prove. `Sets.lean`, `ex_3_12`: a structure instance opened on the same line as `solution!(`, and its remaining fields were aligned against a column that the marker created. Removing `solution!` shortens the first line by nine characters, the fields no longer line up under `refl`, and the instance fails to parse. The body now starts on the line below, indented, so the alignment is its own. `Proof.lean`, three tactic blocks: written `solution!( … )` rather than as an indented block. As a tactic the marker is replaced by `all_goals`, so the block has to be applicable to a single goal. Two were merely parenthesised and became indented. In `vestidos₁` the shape was wrong: `rcases` ran outside the marker and the three bullets inside it, so `all_goals` applied all three to each of the three goals it left open. The `rcases` moves inside, and the block opens and closes the case split itself. `STYLE-CODE.md` now states both rules, which it did not: a tactic solution takes the indented form and must suit a single goal, and a multi-line term begins on the line after `solution!(`. The emitter is not at fault and is unchanged: `solution!` is used in both forms in sf-in-lean, and a parenthesised *term* — including a `by` block — is what it is for. Refs #22 AI-usage disclosure: this change was developed with the assistance of Claude Opus 5 (Anthropic, September 2026, via Claude Code). The author judged the fault to lie in the book's use of `solution!` rather than in the macro and pointed at sf-in-lean for the idiom; the assistant located the three occurrences, found the `all_goals` constraint behind the `vestidos₁` failure, and wrote the style-guide entry. The author takes full responsibility for the final content. --- CSwL/Logic/Proof.lean | 18 +++++++++--------- CSwL/Sets.lean | 7 ++++--- STYLE-CODE.md | 18 ++++++++++++++++++ 3 files changed, 31 insertions(+), 12 deletions(-) diff --git a/CSwL/Logic/Proof.lean b/CSwL/Logic/Proof.lean index 620085f..d6642bf 100644 --- a/CSwL/Logic/Proof.lean +++ b/CSwL/Logic/Proof.lean @@ -398,10 +398,9 @@ theorem vestidos₁ (h : Premissas Aa Ab Ap Ma Mb Mp Ca Cb Cp) -- Ana não está de azul: se estivesse, por `h1` ela estaria de branco, mas Ana -- não está de branco por `h3`. have hnAa : ¬ Aa := by - solution!( + solution! intro hAa exact h3 (h1 hAa) - ) have hAp : Ap := by cases hA with @@ -421,11 +420,12 @@ theorem vestidos₁ (h : Premissas Aa Ab Ap Ma Mb Mp Ca Cb Cp) have hMa : Ma := by rcases ha with hMa | hAa | hCa - solution!( - · exact hMa - · exact absurd hAa hnAa - · exact absurd hCa hnCa - ) + · solution! + exact hMa + · solution! + exact absurd hAa hnAa + · solution! + exact absurd hCa hnCa exact ⟨hAp, hCb, hMa⟩ ``` @@ -513,9 +513,9 @@ Prove que `∃ n : Nat, n + n = 10`, exibindo a testemunha. Você pode usar {nam ```lean example : ∃ n : Nat, n + n = 10 := by - solution!( + solution! apply Exists.intro 5 - rfl) + rfl ``` :::: diff --git a/CSwL/Sets.lean b/CSwL/Sets.lean index 257da7d..6df090e 100644 --- a/CSwL/Sets.lean +++ b/CSwL/Sets.lean @@ -867,9 +867,10 @@ def kernel {α β : Type} (f : α → β) : Rel α α := theorem ex_3_12 {α β : Type} (f : α → β) : Equivalence (kernel f) := - solution!({ refl := fun _ => rfl - symm := fun h => h.symm - trans := fun h1 h2 => h1.trans h2 }) + solution!( + { refl := fun _ => rfl + symm := fun h => h.symm + trans := fun h1 h2 => h1.trans h2 }) ``` :::gradeTheorem "1" ex_3_12 diff --git a/STYLE-CODE.md b/STYLE-CODE.md index 11af45c..d0d760f 100644 --- a/STYLE-CODE.md +++ b/STYLE-CODE.md @@ -159,6 +159,24 @@ The `student` and `terse` variants replace these with `sorry`; `solutions` and `grading` keep them. This is why a feature first used inside `solution!` is invisible to the student. +Which of the two forms to use is not a matter of taste, because the `solutions` +variant strips the marker in place and what is left has to parse and elaborate +on its own: + +- **A tactic block takes the indented form**, never `solution!(…)`. As a + tactic the marker is replaced by `all_goals`, so the block must make sense + applied to a *single* goal — a proof that splits into cases has to perform + the split itself, inside the block, rather than relying on goals its caller + left open. +- **A multi-line term begins on the line after `solution!(`**, indented under + it. Written on the same line, its continuation lines are aligned against a + column that only exists while the marker is there; removing the marker + shortens the first line and the alignment breaks. + +The book's own build catches neither mistake — a solution is elaborated in +place there, marker and all. Only the generated project sees the stripped +source, which is why `solutions` is verified (see `ExtractConfig.verify`). + ### Grading `:::gradeTheorem …` marks theorems the autograder scores. From 463592863dabf529e1f30ff57c2a3372d5a3add1 Mon Sep 17 00:00:00 2001 From: Alexandre Rademaker Date: Mon, 14 Sep 2026 00:25:24 -0300 Subject: [PATCH 08/14] feat(save): build the generated `solutions` project as part of `make` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `buildProject` has been there since the saver was written, behind a `verify` flag that defaulted to `false` and that nothing ever set. The three defects fixed in this branch all had the same shape — the book compiled, the project handed to the student did not — and none of them was visible to `lake build` at the repository root, where a solution is elaborated in place, marker and all. Only the extracted source shows what the student receives. `verify := true` for `solutions` alone. It is the one variant whose proofs are all meant to be complete, so "it compiles" both means something and is now true. `student` and `terse` replace answers with `sorry` and fail by design; verifying them would report expected failures as errors. `grading` is the same Lean with the autograder's attributes over it. The two reasons recorded for keeping this off no longer hold, and the comment now says what was measured instead of what was assumed. The toolchain mismatch is gone: the generated project and `CSwL` are both on `v4.33.0`. And Mathlib is not compiled "from scratch on every `make`" — the generated project vendors its own, 7.5 GB, but once built an incremental `lake build` takes about three seconds. The cost that remains is disk, and a `make clean` that pays for it again. Checked by reintroducing the `ex_3_12` defect: `make solutions` fails, exit 2, naming the file and line inside the generated project. Closes #22 AI-usage disclosure: this change was developed with the assistance of Claude Opus 5 (Anthropic, September 2026, via Claude Code). The author decided to turn the check on; the assistant measured the two recorded objections, found both stale, limited the flag to `solutions`, and verified that a failing generated project fails `make`. The author takes full responsibility for the final content. --- CSwLMeta/Save/Project.lean | 31 ++++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/CSwLMeta/Save/Project.lean b/CSwLMeta/Save/Project.lean index be15fd9..ea3f47b 100644 --- a/CSwLMeta/Save/Project.lean +++ b/CSwLMeta/Save/Project.lean @@ -72,14 +72,23 @@ structure ExtractConfig where modPrefix : String variant : Variant /-- Run `lake build` inside the generated project, to check that it - compiles on its own. Stays `false` for two reasons, both measured and not - hypothetical: (a) chapters import Mathlib, so the generated project would - compile Mathlib from scratch on every `make`; (b) in the `grading` variant - the autograder requires the `v4.33.0` toolchain and ours is - `v4.33.0-rc2` -- the same mismatch that kept the autograder out of - `CSwL`'s own `lakefile.toml`, and which remains open for the real - chapters (`Placeholder` does not import Mathlib, so for it the generated - `grading` project is pure autograder). -/ + compiles on its own. On for `solutions` and off everywhere else. + + The book's own build says nothing about this: there a solution is + elaborated in place, so a chapter can compile while the project handed to + the student does not. Every defect fixed for issue #22 had that shape. + + Only `solutions` is verified. `student` and `terse` replace answers with + `sorry`, so their generated projects fail *by design* and verifying them + would report expected failures as errors. `grading` is the same Lean with + the autograder's attributes on top. + + The cost, measured rather than assumed: the generated project vendors its + own Mathlib, 7.5 GB per variant, and once built an incremental `lake + build` is about three seconds. It is not rebuilt on every `make`, but + `make clean` removes `_out/` and the next build pays for it again. The + toolchain objection that once kept this off is gone -- the generated + project and `CSwL` are both on `v4.33.0`. -/ verify : Bool := false /-! ## Generated Lake project template -/ @@ -443,7 +452,11 @@ def emitSavedStudent (vol : String) := /-- `ExtraStep` for the `solutions` variant: answer keys shown. -/ def emitSavedSolutions (vol : String) := - emitSavedImpl { modPrefix := vol, variant := .solutions } + -- `verify := true` here only: this is the one variant whose proofs are all + -- meant to be complete, so "the generated project compiles" is both + -- meaningful and true. `student` and `terse` carry `sorry` by design, and + -- `grading` adds autograder attributes over the same Lean. + emitSavedImpl { modPrefix := vol, variant := .solutions, verify := true } /-- `ExtraStep` for the `terse` (lecture) variant: answer keys elided and proofs marked with `workinclass!` become `sorry`, to be done live. -/ From 9fde68fb6244d44feecc87ae436a1f63a44feaac Mon Sep 17 00:00:00 2001 From: Alexandre Rademaker Date: Mon, 14 Sep 2026 00:26:28 -0300 Subject: [PATCH 09/14] docs(logic): revise the prose of FOL --- CSwL/Logic/FOL.lean | 25 +++++++------------------ 1 file changed, 7 insertions(+), 18 deletions(-) diff --git a/CSwL/Logic/FOL.lean b/CSwL/Logic/FOL.lean index 2ce6eb9..2da7bba 100644 --- a/CSwL/Logic/FOL.lean +++ b/CSwL/Logic/FOL.lean @@ -124,7 +124,7 @@ def Formula.disjs {α : Type} : List (Formula α) → Formula α | f :: fs => .disj f (Formula.disjs fs) ``` -E a instância de `Repr` para exibirmos fórmulas de forma legível. Note que ela demanda que o tipo `α` tenha também uma instância de `Repr`. +Um termo do tipo `Formula` não é muito legível, vamos implementar a instância de `Repr` para controlar a exibição destes termos. Note que ela demanda que o tipo `α` tenha também uma instância de `Repr`. ```lean def Formula.format {α} [Repr α] : Formula α → Std.Format @@ -189,7 +189,6 @@ def Formula.freeVars {α} (vars : α → List Variable) : ``` ::::exercise (rating := 2) (name := "closed-form") - Escreva uma função `closedForm : Formula Variable → Bool` que verifica se uma fórmula é fechada. Aqui cada termo é uma variável, então extrair as variáveis de um termo é devolvê-lo numa lista de um @@ -203,11 +202,9 @@ def freeVarsInFormula (f : Formula Variable) : def closedForm (f : Formula Variable) : Bool := solution!((freeVarsInFormula f).isEmpty) ``` - :::: ::::exercise (rating := 1) (name := "implication-as-abbrev") - Implicações e equivalências podem ser vistas como abreviações, pois se definem a partir de negação, conjunção e disjunção — as mesmas equivalências usadas na lógica proposicional. Escreva uma função @@ -237,11 +234,9 @@ def withoutIDs (frm : Formula Variable) : | .forall_ v f => .forall_ v (withoutIDs f) | .exists_ v f => .exists_ v (withoutIDs f)) ``` - :::: ::::exercise (rating := 2) (name := "negation-normal-form") - Toda fórmula de lógica de predicados pode ser transformada em uma equivalente na *forma normal da negação* (NNF, "negation normal form"), onde negações só ocorrem diante de átomos. A receita é "empurrar" as negações através dos quantificadores por `¬ ∀x F ≡ ∃x ¬F` e `¬ ∃x F ≡ ∀x ¬F`, e através de disjunções e conjunções pelas leis de De Morgan: `¬(F1 ∧ F2) ≡ ¬F1 ∨ ¬F2` e `¬(F1 ∨ F2) ≡ ¬F1 ∧ ¬F2`. Finalmente, `¬¬F ≡ F` elimina dupla negação. Complete o código da função `nnf`. Dica: a receita acima diz o que fazer com `¬` diante de alguma subfórmula. Isso sugere duas funções, uma para cada situação em que uma subfórmula pode aparecer. As duas se chamam mutuamente, e por isso vão num bloco `mutual`. @@ -291,16 +286,10 @@ end def Formula.nnf (f : Formula Variable) : Formula Variable := solution!(nnfPos f) - -#eval Formula.neg formula2 -#eval (Formula.neg formula2).nnf ``` - :::: -# Símbolos de função - -Termos denotam objetos do domínio, e diferentes termos podem denotar um mesmo objeto como o termo `(5 + 3) × 4`, `8 × 4` e `32`. Para representar termos mais complexos que apenas variáveis, a solução é introduzir símbolos funcionais para as operações entre termos. Da mesma forma como escolhemos representar relações binárias quaisquer, ao invés de fixar símbolos específicos para relações como "menor que". +Termos denotam objetos do domínio, e diferentes termos podem denotar um mesmo objeto como os termos `(5 + 3) × 4`, `8 × 4` e `32`. Para representar termos mais complexos que apenas variáveis, a solução é introduzir símbolos funcionais para as operações entre termos. ```lean inductive Term where @@ -374,11 +363,9 @@ def Formula.varsInForm (frm : Formula Term) : List Variable := | .exists_ v f => v :: f.varsInForm tmp.eraseDups) ``` - :::: ::::exercise (rating := 2) (name := "free-vars-in-formula") - Implemente `freeVarsInForm : Formula Term → List Variable`, que dá a lista de variáveis com ocorrências livres numa fórmula. @@ -386,21 +373,23 @@ lista de variáveis com ocorrências livres numa fórmula. def Formula.freeVarsInForm (f : Formula Term) : List Variable := solution!(f.freeVars varsInTerm) ``` - :::: -::::exercise (rating := 2) (name := "open-form") +::::exercise (rating := 2) (name := "open-form") Usando a função `freeVarsInForm`, complete a função `openForm`, que verifica se uma fórmula é aberta. Reaproveite as funções anteriores. ```lean def openForm (f : Formula Term) : Bool := solution!(!f.freeVarsInForm.isEmpty) ``` - :::: + # Semântica da lógica de predicados +%%% +tag := "fol-semantics" +%%% Por conveniência, nos limitamos a um fragmento de língua com apenas três letras de predicado: `P` (unário), `R` (binário), e `S` (ternário). From 633a8153967f26c32e8220e7803c892f52dc46f5 Mon Sep 17 00:00:00 2001 From: Alexandre Rademaker Date: Mon, 14 Sep 2026 00:57:42 -0300 Subject: [PATCH 10/14] feat(save): give a section with `file :=` its own generated module MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The extracted project mirrored the book's chapters but not its files: three section files of 441, 599 and 772 lines arrived merged into one 1819-line `CSwL/Logic.lean`, while their sources sit in `CSwL/Logic/`. The output now mirrors the source — a `PL.lean` in the book is a `PL.lean` in the project — and the chapter becomes a glue module importing its sections. The criterion is `file :=`. It cannot be read off the document: after `{include 1 …}` a section that came from its own file and one written inline in the chapter are the same `Part`. The key is already what names the HTML page, and only the three sections that should become files carry it, so `isOwnModule` asks for it and the inline sections (`pl-syntax`, `pl-semantics`, …) stay merged as before. Nothing changes for a chapter whose sections set no `file :=` — `Morphology` is untouched until someone adds the key. `chapterImports` did not need inverting, only documenting: it keys on `chapterModules`, which is built from the generated file names, so a section that now has a module of its own is no longer taken for a merged one and its `import` survives as written. Each section keeps the imports its own header declares — `Mathlib.Tactic.ByContra` lands in `PL.lean`, `Mathlib.Tactic.Use` in `FOL.lean` — which is what 0b137cb had to work around while the output was flat. The silent drop that 0b137cb worked around and that 47e435a ran into is now an error. An `import` that resolves to a chapter the project emits under another name — `CSwL.IntroL` against a chapter emitted as `introL.lean` — used to be taken for a merged section and dissolved, and the failure surfaced three chapters away as `Unknown identifier IntroL.initS`. Extraction now stops and names the file and the cause. Verified: the split content is identical to the previous single file except that each section's headings rise one level, as a file's own headings should; all four variants emit the same eleven modules; and the generated `solutions` project still builds, which is the check #22 turned on. Refs #11 AI-usage disclosure: this change was developed with the assistance of Claude Opus 5 (Anthropic, September 2026, via Claude Code). The author set the principle that the Lean output should mirror the source's file organisation; the assistant chose `file :=` as the criterion, wrote the change, and verified the split against the previous output. The author takes full responsibility for the final content. --- CSwLMeta/Save/Extract.lean | 63 ++++++++++++++++++++++++++++++++------ CSwLMeta/Save/Project.lean | 53 +++++++++++++++++++++++++------- STYLE-CODE.md | 11 +++++-- 3 files changed, 105 insertions(+), 22 deletions(-) diff --git a/CSwLMeta/Save/Extract.lean b/CSwLMeta/Save/Extract.lean index 8e7ab5d..40654b8 100644 --- a/CSwLMeta/Save/Extract.lean +++ b/CSwLMeta/Save/Extract.lean @@ -310,17 +310,37 @@ def chapterFileBase (p : Part Manual) : String := (meta?.bind (·.file)).getD titleStr.sluggify.toString -/-- Generated Lean file path for a chapter Part. -/ -def chapterPath (vol : String) (p : Part Manual) : String := - vol ++ "/" ++ chapterFileBase p ++ ".lean" +/-- Generated Lean file path for a chapter Part. `dir` is the directory the +Part's file goes in: `CSwL` for a chapter, `CSwL/Logic` for a section of the +`Logic` chapter. -/ +def chapterPath (dir : String) (p : Part Manual) : String := + dir ++ "/" ++ chapterFileBase p ++ ".lean" /-- Generated Lean module name for a chapter Part. Uses the raw `file :=` identifier when it is a plain alphanumeric/underscore name; falls back to -French-quote brackets for slugs that contain hyphens or other punctuation. -/ -def chapterModule (vol : String) (p : Part Manual) : String := +French-quote brackets for slugs that contain hyphens or other punctuation. +`dir` is as in `chapterPath`, with `/` standing for the module separator. -/ +def chapterModule (dir : String) (p : Part Manual) : String := + let prefix' := dir.replace "/" "." let base := chapterFileBase p - if base.all (fun c => c.isAlphanum || c == '_') then vol ++ "." ++ base - else vol ++ ".«" ++ base ++ "»" + if base.all (fun c => c.isAlphanum || c == '_') then prefix' ++ "." ++ base + else prefix' ++ ".«" ++ base ++ "»" + +/-- Whether a Part inside a chapter becomes a module of its own rather than +being merged into its chapter's file. + +The criterion is the `file := …` key, which is also what names the module. It +mirrors the book's own source: a section long enough to live in its own file +(`CSwL/Logic/PL.lean`) sets `file :=` so that Verso gives it its own HTML +page, and the same key now gives it its own generated `.lean`. A section +written inline in its chapter sets only `tag :=`, and stays merged. + +The distinction cannot be recovered from the document alone: after +`{include 1 …}` a section that came from its own file and one written inline +are the same `Part`. -/ +def isOwnModule (p : Part Manual) : Bool := + let .mk _ _ meta? _ _ := p + (meta?.bind (·.file)).isSome end @@ -485,8 +505,33 @@ def walkOuter (width : Nat) (vol : String) (text : Part Manual) (buf : SaveBuffe buf := buf.appendAll rootFile s!"import {chapterModule vol p}\n" for p in subParts do let chapterFile := chapterPath vol p - buf := buf.appendOnly chapterFile .grading s!"import AutograderLib\n\n" - buf := walkSection width 1 chapterFile p buf + let .mk titleInlines _ _ intro chapterParts := p + -- The sections of this chapter that carry `file :=` and so become + -- modules of their own. A chapter with none of them (the common case) is + -- emitted exactly as before, in one piece. + let ownModules := chapterParts.filter isOwnModule + if ownModules.isEmpty then + buf := buf.appendOnly chapterFile .grading s!"import AutograderLib\n\n" + buf := walkSection width 1 chapterFile p buf + else + -- A glue chapter: its own prose, then the sections in their own files + -- under `{vol}/{chapter}/`. The `import` of each section is already in + -- the chapter's source header (`import CSwL.Logic.PL`), and + -- `chapterImports` now keeps it rather than dissolving it, so nothing + -- is emitted here. + let dir := vol ++ "/" ++ chapterFileBase p + buf := buf.appendOnly chapterFile .grading s!"import AutograderLib\n\n" + if !hasSuppressHeaderMarker intro then + buf := buf.appendAll chapterFile + (asModuleDoc s!"# {Text.inlinesToText titleInlines}") + buf := walkBlocks width chapterFile intro buf + for s in chapterParts do + if isOwnModule s then + let sectionFile := chapterPath dir s + buf := buf.appendOnly sectionFile .grading s!"import AutograderLib\n\n" + buf := walkSection width 1 sectionFile s buf + else + buf := walkSection width 1 chapterFile s buf return buf end CSwLMeta.Save diff --git a/CSwLMeta/Save/Project.lean b/CSwLMeta/Save/Project.lean index ea3f47b..f26a746 100644 --- a/CSwLMeta/Save/Project.lean +++ b/CSwLMeta/Save/Project.lean @@ -292,18 +292,26 @@ private def headerImports (src : String) : Array String := Id.run do /-- Every `import` a chapter needs in the extracted project, including those declared only in the header of a section it merges. -The extracted project has one file per chapter: a section brought in with -`{include 1 ...}` has its content merged into the chapter's buffer and gets no -module of its own. An `import` written only in that section's header would -therefore be dropped, and the generated file would use what it never imported — -which shows up as an "unknown identifier" inside the extracted project's build, -far from the file that caused it. So a section's header is read too, and its +A section that sets `file :=` gets a module of its own in the extracted +project (see `isOwnModule`), so it keeps its own header and the `import` of it +in the chapter's header stays as it is — the module it names exists. + +A section without `file :=` is merged into its chapter's buffer and gets no +module. An `import` written only in such a section's header would therefore be +dropped, and the generated file would use what it never imported — which shows +up as an "unknown identifier" inside the extracted project's build, far from +the file that caused it. So a merged section's header is read too, and its imports are merged into the chapter's. -`sf-in-lean` does not need this: there a section stays a module of its own, and -`bundleLoop` copies it with its header intact. -/ +Note that both branches turn on `chapterModules`, which is built from the +*generated* file names. An `import` whose spelling does not match the module +name it refers to — `CSwL.IntroL` against a chapter emitted as `introL.lean` — +looks like a merged section here, and its imports would be silently fetched +from a file that does not exist. That is `reportError`ed below rather than +passed over. -/ private partial def chapterImports (modPrefix : String) - (chapterModules : List String) (file : String) : IO (List String) := do + (chapterModules : List String) (chapterSources : List String) + (file : String) : IO (List String) := do let src ← (IO.FS.readFile file).toBaseIO >>= fun | .ok s => pure s | .error _ => pure "" @@ -313,7 +321,24 @@ private partial def chapterImports (modPrefix : String) let mut acc := raw.filter keepImport |>.filter (! isSection ·) for sec in raw.filter isSection do let path := (sec.replace "." "/") ++ ".lean" - for i in ← chapterImports modPrefix chapterModules path do + -- A merged section's source is a file that no chapter is generated from: + -- its content went into its chapter's buffer. If this path *is* a source + -- some chapter was generated from, the import names a real chapter whose + -- `file :=` is spelled differently from the module — `CSwL.IntroL` + -- against a chapter emitted as `introL.lean` — and dissolving it here + -- would drop it in silence. + if chapterSources.any (·.toLower == path.toLower) then + throw <| IO.userError <| + s!"`import {sec}` in {file} names a chapter that the extracted " ++ + s!"project emits under a different module name. Its `file :=` does " ++ + s!"not match the module the source imports, so the import would be " ++ + s!"dropped and the generated project would use what it never " ++ + s!"imported. Make `file :=` match, or fix the import." + if ! (← System.FilePath.pathExists path) then + throw <| IO.userError <| + s!"`import {sec}` in {file} names neither a generated module nor a " ++ + s!"section file on disk ({path})." + for i in ← chapterImports modPrefix chapterModules chapterSources path do if ! acc.contains i then acc := acc ++ [i] return acc @@ -358,6 +383,11 @@ private def emitSavedImpl (config : ExtractConfig) : -- `CSwL/Morphology/Phonemes.lean`). let chapterModules := entries.map (·.1) |>.filter (·.any (· == '/')) |>.map fun k => ((k.dropEnd 5).toString).replace "/" "." + -- The source paths some generated file was produced from. A buffer key is + -- the source's path in the repository, so an `import` resolving to one of + -- these names a real chapter or section — never a merged one, whose + -- content has no file of its own in the extracted project. + let chapterSources := entries.map (·.1) |>.filter (·.any (· == '/')) -- Picks each file's variant and prefixes the chapter's `import` header, -- already stripped of the infrastructure ones. let mut files : Array (String × String) := #[] @@ -380,7 +410,8 @@ private def emitSavedImpl (config : ExtractConfig) : -- (flattened, one file per chapter), so the line itself is dropped -- -- but the section's *own* imports are picked up in its place, by -- `chapterImports`. - let imps ← chapterImports config.modPrefix chapterModules file + let imps ← chapterImports config.modPrefix chapterModules + chapterSources file for i in imps do let top := modTop i if pkgPrefixes.contains top then diff --git a/STYLE-CODE.md b/STYLE-CODE.md index d0d760f..7da7ae3 100644 --- a/STYLE-CODE.md +++ b/STYLE-CODE.md @@ -139,8 +139,15 @@ the chapter's directory — `Logic/PL/`, `Logic/FOL/` — which is what the default `htmlDepth` of 2 already asks for. A section that gets its own page needs its own `file := "Tag"`, or Verso builds the URL by sluggifying the Portuguese title and the accents come out mangled (`L___gica-proposicional`). -None of this affects the generated Lean: the saver writes one file per -chapter, and reads `file :=` only from chapters. + +`file :=` also names the generated Lean. A section that sets it becomes its +own module in the extracted project — `CSwL/Logic/PL.lean` in the source +becomes `CSwL/Logic/PL.lean` in the output — and the chapter becomes a glue +module importing them, mirroring the source. A section without `file :=` is +merged into its chapter's file, as every chapter's inline sections are. This +is why the key must match the module name exactly: the extractor resolves a +chapter's `import` against the *generated* names, and a mismatch is now an +error rather than a dropped import. ### Exercises and solutions From 0a3ab2f70841bddc0cf1f1e46fd68637177d0229 Mon Sep 17 00:00:00 2001 From: Alexandre Rademaker Date: Mon, 14 Sep 2026 01:09:03 -0300 Subject: [PATCH 11/14] fix(logic): drop the `CSwLCompat` import from two files that do not use it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `CSwLCompat` backs the `sf_experiment` and `sf_expect_failure` macros that a ```lean +error fence becomes in the extracted project. Only `SeaBattle.lean` and `Logic/PL.lean` have such a fence; `Logic.lean` and `Logic/Proof.lean` imported the module without using it, and the extracted project carried the import into the student's files. The module itself is still needed, in every variant including `student`: the two fences that remain are pedagogical — `badState`, which must fail to typecheck, and `doesNotWork`, which shows that a proof of a `Prop` cannot be pattern-matched — so the blocks belong in the student's project and the project does not build without the macro that carries them. AI-usage disclosure: this change was developed with the assistance of Claude Opus 5 (Anthropic, September 2026, via Claude Code). The author asked whether the module was needed in the student output; the assistant established that it is, found the two imports that are not, and removed them. The author takes full responsibility for the final content. --- CSwL/Logic.lean | 1 - CSwL/Logic/Proof.lean | 1 - 2 files changed, 2 deletions(-) diff --git a/CSwL/Logic.lean b/CSwL/Logic.lean index b52cd3d..e2d6b4f 100644 --- a/CSwL/Logic.lean +++ b/CSwL/Logic.lean @@ -3,7 +3,6 @@ import Bib import CSwL.Logic.Proof import CSwL.Logic.PL import CSwL.Logic.FOL -import CSwLCompat import VersoManual diff --git a/CSwL/Logic/Proof.lean b/CSwL/Logic/Proof.lean index d6642bf..9a05d9d 100644 --- a/CSwL/Logic/Proof.lean +++ b/CSwL/Logic/Proof.lean @@ -1,7 +1,6 @@ import CSwLMeta import Bib import Mathlib.Tactic -import CSwLCompat import CSwL.IntroL open Verso.Genre Manual From cc160313c314fd2c7ab3b57efa7e421c5b3932a8 Mon Sep 17 00:00:00 2001 From: Alexandre Rademaker Date: Mon, 14 Sep 2026 01:09:32 -0300 Subject: [PATCH 12/14] docs(style): regenerate the first-use ledger against the new chapter order MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The ledger is indexed by book order, and the reorganisation moved nearly every row. It had been patched by hand as chapters moved; rescanning the sources found five rows that no longer said where a feature is first used. - `native_decide` — `Logic/PL`, not `SeaBattle`. It is named in the prose of three exercises there (`:282`, `:308`, `:348`), two chapters before the row claimed. This is the worst of the five: the tactic trusts the compiler rather than the kernel, and "Known gaps" now records that `Logic/PL` is where the explanation it still lacks belongs. - `assumption` — `Logic/Proof`, not `Sets`. It is presented there in prose (`:172`) and used at `:182`, so this row was not a gap, only misplaced. - `DecidableEq` — `IntroL`, not `Logic/PL`. Presented twice, at `:327` with `deriving` and at `:862` alongside `BEq`. - `trivial` — stays in `Sets`, but it is a term there (`:517`), not a tactic, and moves to the types column. - `funext` — solution only, like the `show`/`omega`/`decide` beside it: its only use is inside the `twice` exercise's answer. Stale line numbers in "Known gaps" corrected (`Sets.lean:507` → `:517`, the `twice` exercise's `:1154-1158` → `:1136-1141`). "How to read the table" gains the scanning method, since the naive one is what let these drift: a search that ignores ```` ```lean ```` fences reports Portuguese as Lean — "trivialmente" matches `trivial` — and a tactic named in a sentence is a mention, not a use. Uses are found inside fences with comments stripped, presentations in the prose, and the rule holds only when a presentation precedes the first use in book order. Closes #10 AI-usage disclosure: this change was developed with the assistance of Claude Opus 5 (Anthropic, September 2026, via Claude Code). The assistant wrote the fence-aware scan, checked each disagreement between it and the table against the sources, and distinguished the real errors from matches on Portuguese prose; the author reviewed the result. The author takes full responsibility for the final content. --- STYLE-CODE.md | 55 ++++++++++++++++++++++++++++++--------------------- 1 file changed, 33 insertions(+), 22 deletions(-) diff --git a/STYLE-CODE.md b/STYLE-CODE.md index 7da7ae3..b0b5b31 100644 --- a/STYLE-CODE.md +++ b/STYLE-CODE.md @@ -40,6 +40,13 @@ presents fourteen tactics in a plain code fence — one line each, `rfl` through `funext` — and a scan that reads only ```` ```lean ```` blocks misses it. Check the prose before recording a gap. +The scan has to be fence-aware in the other direction too, or it reports +Portuguese words as Lean: "trivialmente" matches `trivial`, and a tactic named +in a sentence is a mention, not a use. Search inside ```` ```lean ```` blocks +with `--` comments stripped to find *uses*, then search the prose separately to +find *presentations*; the rule is satisfied only when a presentation precedes +the first use in book order. + A feature marked **(solution only)** first appears inside a `solution!(…)` block. Those rows are a distinct case: the feature is invisible in the `student` and `terse` variants and visible in `solutions` and `grading`, so a @@ -51,12 +58,12 @@ this way is usually a mistake; see "Known gaps." | Chapter | Commands and declarations | Types and syntax | Tactics | | --- | --- | --- | --- | | `IntroCS` | `namespace`, `def` (by pattern matching), `inductive`, `deriving Repr`, `example`, `#eval` | `Nat`, function type `→`, dot-notation constructors (`.num`) | `rfl`, `induction … with`, `rw`, `rewrite`, `unfold`, `repeat` | -| `IntroL` | `#check`, `#print`, `theorem`, `structure`, `instance`, `section`, `variable` | `Type`, `Prop`, `Bool`, `List`, `Option`, `Char`, `String`, `fun`/`λ`, `match`, `if … then … else`, `⟨…⟩`, implicit `{}`, instance-implicit `[]`, `∘`, `BEq`, `List.all`/`List.any` | `funext`, `show` (solution only), `omega` (solution only), `decide` (solution only) | -| `Logic/Proof` | `open` | `¬`, `∀`, `∃`, `∧`, `∨`, `↔`, `≠` | `intro`, `exact`, `apply`, `cases … with`, `constructor`, `obtain`, `have`, `use`, `left`, `right`, `rcases`, `by_cases`, `by_contra` | -| `Logic/PL` | `abbrev`, `private` | `DecidableEq`, `×` | `simp` | +| `IntroL` | `#check`, `#print`, `theorem`, `structure`, `instance`, `section`, `variable` | `Type`, `Prop`, `Bool`, `List`, `Option`, `Char`, `String`, `fun`/`λ`, `match`, `if … then … else`, `⟨…⟩`, implicit `{}`, instance-implicit `[]`, `∘`, `BEq`, `DecidableEq`, `List.all`/`List.any` | `funext` (solution only), `show` (solution only), `omega` (solution only), `decide` (solution only) | +| `Logic/Proof` | `open` | `¬`, `∀`, `∃`, `∧`, `∨`, `↔`, `≠` | `intro`, `exact`, `apply`, `cases … with`, `constructor`, `obtain`, `have`, `use`, `left`, `right`, `rcases`, `by_cases`, `by_contra`, `assumption` | +| `Logic/PL` | `abbrev`, `private` | `×` | `simp`, `native_decide` | | `Logic/FOL` | `mutual`, `deriving BEq` | `\|>`, `List.contains` | `induction … generalizing` | -| `Sets` | — | `Set`, `Rel`, `Finset`, `Fintype`, `Setoid`, `∈`, `⊆`, `∪`, `∩` | `assumption`, `trivial`, `symm`, `simp_all` | -| `SeaBattle` | — | `Fin` | `native_decide` | +| `Sets` | — | `Set`, `Rel`, `Finset`, `Fintype`, `Setoid`, `∈`, `⊆`, `∪`, `∩`, `trivial` (term) | `symm`, `simp_all` | +| `SeaBattle` | — | `Fin` | — | | `Morphology` | *(none new)* | — | — | | `InfEngine` | — | `do`-notation | — | | `English` | *(none new)* | *(none new)* | *(none new)* | @@ -74,27 +81,31 @@ maintains. Open questions about the table, recorded so they are not lost. Each needs an author decision, not a mechanical fix. -- **`native_decide`** is used eleven times in `SeaBattle.lean` (`:290`, `:291`, - `:307`, `:308`, `:334`, `:338`, `:342`, `:395`, `:396`, `:399`, `:402`) and - twice in `Morphology/SwedishPlural.lean` (`:69`, `:72`), and is presented - nowhere. It closes a goal by compiling and running it, trusting the compiler - rather than the kernel — a materially different promise from `decide`, and a - reader who meets it without being told will draw the wrong conclusion about - what a Lean proof is worth. Only the `SwedishPlural` uses carry an - explanation, in a Portuguese comment inside a solution (`:63-66`), which - reaches neither the student nor the English code-comment rule. The - `SeaBattle` uses are in ordinary code, not solutions, so the student does - see them. -- **`trivial`** — one term-level use in `Sets.lean:507`, not presented - anywhere. Give it a line or replace it when that chapter is revised. -- **`show`, `omega`, `decide` in `IntroL`** — all three appear only inside the - `twice` exercise's `solution!(…)` blocks (`:1154`, `:1155`, `:1158`), so the - student and `terse` variants never show them, but the `solutions` and +- **`native_decide`** is named in the prose of three `Logic/PL` exercises + (`:282`, `:308`, `:348`) and used in their solutions, then used eleven times + in `SeaBattle.lean` (from `:290`) and twice in + `Morphology/SwedishPlural.lean` (`:69`, `:72`). It is told to the reader but + never *presented*: it closes a goal by compiling and running it, trusting + the compiler rather than the kernel — a materially different promise from + `decide`, and a reader who meets it without being told will draw the wrong + conclusion about what a Lean proof is worth. The only explanation anywhere + is a Portuguese comment inside a `SwedishPlural` solution (`:63-66`), which + reaches neither the student nor the English code-comment rule. `Logic/PL` is + where it is first met and so where the explanation belongs. +- **`trivial`** — two term-level uses in `Sets.lean:517`, not presented + anywhere. It is listed in the table under types rather than tactics, since + that is what it is here. Give it a line or replace it when that chapter is + revised. +- **`funext`, `show`, `omega`, `decide` in `IntroL`** — all four appear only + inside the `twice` exercise's `solution!(…)` blocks (`:1136-1138`, `:1141`), + so the student and `terse` variants never show them, but the `solutions` and `grading` variants do, and nothing presents them before `Logic/Proof`. This is the cost of moving every proof tactic out of `IntroL`: the chapter is now deliberately pre-proof, so presenting them here would undo that. Either move the exercise's tests to `Logic/Proof`, or weaken them to what `rfl` closes. - `decide` → `rfl` is known to work for `twice_test2`. + `decide` → `rfl` is known to work for `twice_test2`. (`funext` is discussed + in the prose at `:807`, but as the principle of function extensionality, not + as a tactic the reader is being handed.) `IntroCS` is the constraint's one accepted exception: it uses Lean that `IntroL` only presents later, deliberately, and the chapter says so where its From 2812deaaea1c855d510ec1023b2b8b17d4adcffa Mon Sep 17 00:00:00 2001 From: Alexandre Rademaker Date: Mon, 14 Sep 2026 01:38:32 -0300 Subject: [PATCH 13/14] docs(FOL, English): revise the prose --- CSwL/English.lean | 44 +++++--------------------------------------- CSwL/Logic/FOL.lean | 13 ++++--------- 2 files changed, 9 insertions(+), 48 deletions(-) diff --git a/CSwL/English.lean b/CSwL/English.lean index 0f10ef1..d9b2fbf 100644 --- a/CSwL/English.lean +++ b/CSwL/English.lean @@ -46,32 +46,20 @@ A abreviações são como `def` mas são `unfold` automaticamente. ```lean abbrev Sentences := List Sentence -``` -```lean (name := c2eval37) #eval Subject.Chomsky -``` -```leanOutput c2eval37 -English.Subject.Chomsky -``` - -```lean (name := c2eval38) #eval Sentence.S Subject.Chomsky (Predicate.Wrote "Syntactic Structures") ``` -```leanOutput c2eval38 -English.Sentence.S (English.Subject.Chomsky) (English.Predicate.Wrote "Syntactic Structures") -``` - A última saida acima lembra uma árvore, o que iremos chamar de _árvore sintática_. -``` - Sentence - / \ - Subject Predicate +```display + Sentence + |- Subject + |- Predicate ``` O passo inverso é a _geração_, serializar uma estrutura que representa @@ -82,19 +70,6 @@ instâncias para `ToString` de nossos tipos. Criar uma instância de uma classe é implementar os campos que a classe demanda e classes são `structure`. Algumas variações de sintaxe na declaração das instâncias. -```lean (name := c2print7) -#print ToString -``` - -```leanOutput c2print7 -class ToString.{u} (α : Type u) : Type u -number of parameters: 1 -fields: - ToString.toString : α → String -constructor: - ToString.mk.{u} {α : Type u} (toString : α → String) : ToString α -``` - ```lean instance : ToString Subject where toString @@ -121,10 +96,6 @@ def makeS (s : Subject) (p : Predicate) : Sentence := .S s p makeS .Chomsky (makeP "Syntactic Structures") ``` -```leanOutput c2eval39 -Chomsky wrote "Syntactic Structures" -``` - ## Por que isso serve à semântica @@ -201,15 +172,10 @@ E a sentença, com o sujeito no lugar. ```lean def dorothyLikesToto : t := likesToto dorothy -``` -```lean (name := c3check12) #check dorothyLikesToto ``` -```leanOutput c3check12 -English.dorothyLikesToto : t -``` A derivação da sentença é uma sequência de duas aplicações, e cada passo é conferido pelos tipos. Uma combinação mal formada não chega a @@ -299,6 +265,7 @@ entidade: `#check likes "Toto"` não compila, e o erro aponta o argumento — uma `String` onde se esperava um `e`. É a versão tipada de dizer que a combinação não é bem formada. + # Um fragmento do inglês Em seguida, vamos implementar um fragmento um pouco mais realistico do inglês. Ela é deliberadamente básica e grosseira, queremos capturar a sintaxe de sentenças como: @@ -789,7 +756,6 @@ O problema é que `rcn4`/`rcn5` não são recursivas: cada uma coordena exatamen :::: - ```lean end English ``` diff --git a/CSwL/Logic/FOL.lean b/CSwL/Logic/FOL.lean index 2da7bba..3bab90a 100644 --- a/CSwL/Logic/FOL.lean +++ b/CSwL/Logic/FOL.lean @@ -433,10 +433,11 @@ def dwarf : Entity → Bool := ([Entity.B, .R].contains ·) def giant : Entity → Bool := ([Entity.T].contains ·) def child : Entity → Bool := fun x => girl x || boy x -def love : Entity → Entity → Bool := fun x y => - [(Entity.Y, Entity.E), (.B, .S), (.R, .S)].contains (x, y) +def love (x y : Entity) : Bool := + [(.Y, .E), (.B, .S), (.R, .S)].contains (x, y) -def defeat : Entity → Entity → Bool := fun x y => dwarf x && giant y +def defeat (x y : Entity) : Bool := + dwarf x && giant y ``` A função de interpretação amarra os nomes de predicado ao modelo. Nomes fora @@ -533,18 +534,12 @@ def everyDwarfLovesAPrincess : Formula Variable := .forall_ x (.impl (.atom "Dwarf" [x]) (.exists_ y (.conj (.atom "Princess" [y]) (.atom "Love" [x, y])))) -``` -```lean (name := folEval1) #eval (Formula.eval entities int0 g0 someDwarfDefeatsSomeGiant, Formula.eval entities int0 g0 everyChildIsGirlOrBoy, Formula.eval entities int0 g0 everyDwarfLovesAPrincess) ``` -```leanOutput folEval1 -(true, true, false) -``` - A terceira é falsa no modelo: os anões `B` e `R` amam `S`, que é Branca de Neve, e Branca de Neve não é a princesa. Quem ama a princesa é `Y`, que não é anão. From e2a21c007a82a38c317d684d0b8f351408672d53 Mon Sep 17 00:00:00 2001 From: Alexandre Rademaker Date: Mon, 14 Sep 2026 17:27:26 -0300 Subject: [PATCH 14/14] refactor(logic, IntroL): slim the chapters, restructure the material MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `IntroL.lean` loses roughly 80% of its lines. Three sections leave it: "As duas leituras de uma função" and "Tipos na gramática e na computação" move on, and "Tipos como disciplina" is dropped as the book's thesis rather than the chapter's content. The `leanOutput` blocks and `#reduce` go with them. What remains is the language the rest of the book uses, and the chapter is left deliberately pre-proof. `Proof.lean` gains the extensional/intensional discussion as a new section, "Extensionalidade de Funções", which is where `funext` belongs now that it is presented as a tactic. Its sections gain tags, "Prova por indução" becomes "Provas por Indução", and a new exercise `implication-as-disj` is added while `and-comm` drops to one star. `PL.lean` and `FOL.lean` get prose revisions. `FOL.lean` also renders its quantifiers as `∀`/`∃` rather than `A`/`E`, and its scope-ambiguity passage is cut. Cross-references that pointed at an exercise now point at a section instead: exercises cannot be `{ref}`-ed, tracked in #24. `DEVIATIONS.md` catches up with all of this. CSwFP/2.5 — the type BNF and the three typing rules — moves from `IntroL.lean` to `English.lean`, which already carried the rest of 2.5 and already names `e` and `t`. The paragraph claiming `Prop` is presented in `IntroL.lean` is rewritten: the argument for it was that the student would meet `Prop` unintroduced, and that fell when `Proof.lean` became part of the third chapter, ahead of every chapter that displays it. Two stale claims are corrected along the way — the `restful` example is gone with 2.5, and the instances declared downstream are three `Repr` in `FOL.lean`, not `ToString` in `PL.lean`. --- CSwL/IntroL.lean | 971 ++++++------------------------------------ CSwL/Logic/FOL.lean | 48 +-- CSwL/Logic/PL.lean | 128 ++++-- CSwL/Logic/Proof.lean | 273 +++++++----- DEVIATIONS.md | 140 +++--- 5 files changed, 469 insertions(+), 1091 deletions(-) diff --git a/CSwL/IntroL.lean b/CSwL/IntroL.lean index 6823c19..64c9669 100644 --- a/CSwL/IntroL.lean +++ b/CSwL/IntroL.lean @@ -1,10 +1,13 @@ import CSwLMeta import Bib import Mathlib.Tactic +import CSwLCompat open Verso.Genre Manual open CSwLMeta +set_option verso.code.warnLineLength 100 + #doc (Manual) "Programação Funcional no Lean" => %%% tag := "IntroL" @@ -33,12 +36,7 @@ Ao abrir um arquivo Lean, podemos além de escrever declarações, podemos inter #eval "Olá, " ++ "mundo" ``` -Uma `def`inição introduz um nome no ambiente. Os dois-pontos anunciam o tipo, -e o `:=` dá o valor. `100` é um termo do tipo `Nat`, e `"Chomsky"` é um termo -do tipo `String`. Em alguns contextos, o tipo não precisa ser declarado quando -Lean consegue descobri-lo sozinho. Escrever `def n := 100` funciona, porque -Lean irá interpretar `100 : ℕ` e logo estabelecer que a constante `n : ℕ` — -mas escrever o tipo é conveniente e ajuda a tornar o código mais legível. O comando `#check` pergunta ou confirma o tipo, sem calcular nada. +Uma `def`inição introduz um nome no ambiente. Os dois-pontos anunciam o tipo, e o `:=` dá o valor. `100` é um termo do tipo `Nat`, e `"Chomsky"` é um termo do tipo `String`. Em alguns contextos, o tipo não precisa ser declarado quando Lean consegue descobri-lo sozinho. Escrever `def n := 100` funciona, porque Lean irá interpretar `100 : ℕ` e logo estabelecer que a constante `n : ℕ`, mas escrever o tipo é conveniente e ajuda a tornar o código mais legível. O comando `#check` pergunta ou confirma o tipo, sem calcular nada. ```lean def author : String := "Chomsky" @@ -48,11 +46,7 @@ def author : String := "Chomsky" #check (author : String) ``` -Tipos também são termos, e portanto têm tipo. O tipo de `true` é `Bool`, o -tipo de `Bool` é `Type`, e o de `Type` é `Type 1`. Esta hierarquia de -universos existe para que não exista um tipo de todos os tipos, o que -produziria um paradoxo. Para nós, em geral, basta saber que a pergunta "qual o -tipo disto?" tem sempre resposta. +Tipos também são termos, e portanto têm tipo. O tipo de `true` é `Bool`, o tipo de `Bool` é `Type`, e o de `Type` é `Type 1`. Esta hierarquia de universos existe para que não exista um tipo de todos os tipos, o que produziria um paradoxo. Para nós, em geral, basta saber que a pergunta "qual o tipo disto?" tem sempre resposta. ```lean #check true @@ -63,7 +57,7 @@ tipo disto?" tem sempre resposta. # Funções -O tipo `Nat → Nat` representa todas as funções que recebem um número natual e devolvem um número natural. O termo `fun x => x * x : Nat → Nat` é uma particular função deste tipo. Ao aplicar o termo `12 : Nat`, temos o `144 : Nat` como resposta. Ao invés de `fun` podemos usar `λ` e ao invés de `=>` podemos usar `↦`, em Lean podemos usar os caracteres unicode. +O tipo {lean}`Nat → Nat` representa todas as funções que recebem um número natual e devolvem um número natural. O termo {lean}`fun x : Nat => x * x` é uma particular função deste tipo. Ao aplicar o termo `12 : Nat`, temos o `144 : Nat` como resposta. Ao invés de `fun` podemos usar `λ` e ao invés de `=>` podemos usar `↦`, em Lean podemos usar os caracteres unicode. ```lean #check (λ x ↦ x * x) 12 @@ -75,15 +69,12 @@ Mas podemos nomear abstrações, principalmente quando queremos que elas possam ```lean def square₁ : Nat → Nat := fun x => x * x - -def square₂ : ℕ → ℕ := - λ x ↦ x * x ``` Normalmente pode ser conveniente nomear os parâmetros de uma função. A seguir, parâmetros de mesmo tipo podem ser agrupados. ```lean -def square₃ (x : ℕ) : ℕ := x * x +def square₂ (x : ℕ) : ℕ := x * x def agePlusNameSize (age : ℕ) (name : String) : ℕ := age * name.length @@ -94,19 +85,20 @@ def maximum (n k : Nat) : Nat := else n ``` -Nomes são definidos em `namespaces`. As definições deste capítulo estarão no -namespace `IntroL`. A notação `name.length` acima infere pelo tipo de `name` que -estamos falando da função `length` definida no namespace `String` mesmo nome -do tipo `String`. Ver {citep Bib.FPiL}[]. +O nome do argumento não importa, as duas funções abaixo são iguais, como podemos comprovar pela prova abaixo usando {tactic}`rfl`. -Perguntado sobre um nome que foi definido, o `#check` responde com a -assinatura, e não com o tipo seta. Envolver o nome em parênteses força a -segunda forma. Mas as duas dizem o mesmo. As três versões de `square` tem o -mesmo tipo e como veremos, podemos provar que são iguais. +```lean +example : + (λ (x : Nat) => x * x) = (λ (z : Nat) => z * z) := rfl +``` + +Nomes são definidos em `namespaces`. As definições deste capítulo estarão no namespace `IntroL`. A notação `name.length` acima infere pelo tipo de `name` que estamos falando da função `length` definida no namespace `String` mesmo nome do tipo `String`. Ver {citep Bib.FPiL}[]. + +Perguntado sobre um nome que foi definido, o `#check` responde com a assinatura, e não com o tipo seta. Envolver o nome em parênteses força a segunda forma. Mas as duas dizem o mesmo. As três versões de `square` tem o mesmo tipo e como veremos, podemos provar que são iguais. ```lean -#check square₃ -#check (square₃) +#check square₂ +#check (square₂) ``` As vezes podemos querer introduzir uma constante ou tipo sem especificar seu comportamento o valor. Para isso usamos `opaque`, um símbolo com o tipo mas sem implementação. Exemplos de {citep Bib.love2026}[]. @@ -124,20 +116,10 @@ Conferir tipo não demanda computação, logo o comando `#check` funciona retorn #check g a ``` -Os exercícios deste capítulo vêm com *testes*, e os testes são escritos na -mesma linguagem. Um `example` enuncia uma afirmação sem lhe dar nome; o que -vem depois do `:=` é a justificativa. A tática `rfl` fecha uma igualdade -quando os dois lados *calculam* o mesmo valor — é o que confere se a -definição pedida faz o que se pediu. Um `theorem` é um `example` com nome, -e o nome serve para que o enunciado possa ser citado depois. - -Aqui esses três recursos aparecem só como ferramenta de teste. O que -significa provar em Lean, e como se constrói uma prova que `rfl` não fecha -sozinha, é o assunto de {ref "Proof"}[Prova em Lean]. +Os exercícios deste capítulo vêm com *testes* escritos como teoremas simples. Um `example` enuncia uma afirmação sem lhe dar nome; o que vem depois do `:=` é a justificativa. A tática `rfl` fecha uma igualdade quando os dois lados *calculam* o mesmo valor — é o que confere se a definição pedida faz o que se pediu. Um `theorem` é um `example` com nome, e o nome serve para que o enunciado possa ser referenciado depois, possivelmente na prova de outro teorema. Aqui esses três recursos aparecem só como ferramenta de teste. O que significa provar em Lean, e como se constrói uma prova que {tactic}`rfl` não fecha sozinha, é o assunto de {ref "Proof"}[Prova em Lean]. ::::exercise (rating := 1) (name := "sum-of-squares") - -Defina `sumOfSquares` que recebe dois naturais e devolve `m² + n²`. +Defina `sumOfSquares` que recebe dois naturais e devolve `m² + n²`. Para fechar o exemplo, use `rfl`. ```lean def sumOfSquares (m n : Nat) : Nat := @@ -145,13 +127,11 @@ def sumOfSquares (m n : Nat) : Nat := (square₁ m) + (square₁ n) ) -example : sumOfSquares 3 4 = 25 := by - solution! - rfl +example : sumOfSquares 3 4 = 25 := solution!(rfl) ``` :::: -Lean é uma linguagem muito extensiva, na verdade, boa parte de Lean é escrita em Lean, usando os recursos de _meta programação_. Os operadores `+` ou `*` entre outros são símbolos sintáticos associados a definições. Lean tem um mecanismo de `classes` para definir operadores polimorficos como o `+` para os naturais (interpretado como a função `Nat.add`) ou para números de ponto flutuante. +Lean é uma linguagem muito extensiva, boa parte de Lean é escrita em Lean, usando os recursos de _meta programação_. Os operadores `+` ou `*` entre outros são símbolos sintáticos associados a definições. Lean tem um mecanismo de `classes` para definir operadores polimorficos como o `+` para os naturais (interpretado como a função {lean}`Nat.add`) ou para números de ponto flutuante. ```lean #eval Nat.add 2 2 @@ -166,27 +146,28 @@ Podemos forçar o tipo do primeiro argumento, definimos qual multiplicação est #eval (1 : Float) * 10 ``` -No comando abaixo, o tipo de `x` é algo como `?m.7`. Isto significa que Lean sem dizer o tipo de `x`, Lean não tem como saber qual o `*` desejado, `Nat`, `Int`, ou qualquer outro tipo com multiplicação. O `?m.7` é uma _metavariável_: um buraco que Lean deixa em aberto à espera de informação que decida a questão. +No comando abaixo, o tipo de `x` é algo como `?m.7`. Isto significa que Lean sem dizer o tipo de `x`, Lean não tem como saber qual o `*` desejado, {lean}`Nat`, {lean}`Int`, ou qualquer outro tipo com multiplicação. O `?m.7` é uma _metavariável_: um buraco que Lean deixa em aberto à espera de informação que decida a questão. ```lean #check fun x => x * x ``` -Anotar o argumento resolve, e a resposta passa a ser o tipo esperado. O -contexto também resolve. Aplicada a `4`, a função agora é sobre `Nat` assumida a interpretação padrão de números como `Nat`. +Anotar o argumento resolve, e a resposta passa a ser o tipo esperado. O contexto também resolve. Aplicada a `4`, a função agora é sobre `Nat` assumida a interpretação padrão de números como `Nat`. ```lean #check fun (x : Nat) => x * x #check (fun x => x * x) 4 ``` -Se função é valor, então nada impede que ela seja _argumento_ de outra -função. `h` recebe uma função de `Nat → Nat` e um valor, e é isso que o -torna uma função de ordem superior. +Funções podem ser passadas como _argumento_ para outras funções. `tranformWord` recebe uma função, e é isso que o torna uma função de ordem superior. ```lean -def h (f : Nat → Nat) (x : Nat) : Nat := f x -#eval h (λ x => x + 1) 10 +def pluralize (w : String) : String := w ++ "s" + +def transformWord (f : String → String) (w : String) : String := + f w + +#eval transformWord pluralize "dragon" ``` Uma função também pode ser produzida como resultado. O que é equivalente a uma avaliação parcial. Abaixo, a função `h₁` recebe dois naturais para produzir a saída. A função `h₂` recebe um natural, para então devolver a função que ao receber um natural irá produzir como saída a soma dos dois valores recebidos. O interessante que não preciso escrever `h₁` como `h₂`, é perfeitamente aceitável passar apenas um dos argumentos para `h₁` e ver que o tipo da expressão resultante. @@ -201,39 +182,10 @@ def h₂ (x : Nat) : (Nat → Nat) := #check h₁ 1 ``` -::::exercise (rating := 1) (name := "building-terms") - -Adaptado de {citep Bib.love2026}[]. Cada `def` declara `{α β γ : Type}`, são funções parametrizadas por tipo. Para as quatro funções abaixo, cujo tipo foi definido, pede-se fornecer o termo para o tipo correspondente. Dica, use `_` para identificar no _InfoView_ qual tipo o termo na posição deverá ter. - -O `section` permite criar uma seção, onde definições podem compartilhar, por exemplo, a declaração de variáveis. Veja o tipo de `projFst`. - -```lean -section -variable {α β γ : Type} - -def I : α → α := - fun x => x - -def K : α → β → α := - fun a _b ↦ a - -def C : (α → β → γ) → β → α → γ := - solution!(λ f => λ b => λ a => f a b) - -def projFst : α → α → α := - solution!(fun a _b => a) - -def projSnd : α → α → α := - solution!(fun _a b => b) - -def someNonsense : (α → β → γ) → α → (α → γ) → β → γ := - solution!(fun f a _g b => f a b) - -end -``` -:::: - # Expressões +%%% +tag := "expressions" +%%% Uma _expressão_ é uma construção sintática da linguagem. Toda expressão é um termo. Um _termo canônico_ é um termo que já está na forma final de sua computação, não podendo ser reduzido. Construções sintáticas que normalmente não tem valor em linguagens imperativas, também são termos em Lean. @@ -254,10 +206,11 @@ O `if-then-else` também é expressão. Os dois ramos têm de ter o mesmo tipo. ``` # Estruturas +%%% +tag := "structures" +%%% -Uma `structure` agrupa vários valores num só, dando nome a cada campo. -`Point` tem dois campos, `x` e `y`, ambos `Float`. E a estrutura introduz um -novo tipo chamado `Point` e um `namespace` de mesmo nome. +Uma `structure` agrupa vários valores num só, dando nome a cada campo. `Point` tem dois campos, `x` e `y`, ambos `Float`. E a estrutura introduz um novo tipo chamado `Point` e um `namespace` de mesmo nome. ```lean structure Point where @@ -282,15 +235,13 @@ Cada campo tem uma função de projeção. No exemplo, `Point.x` e `Point.y`. To #eval origin₁.x ``` -A notação `⟨_, _⟩` é a *notação de anônima* para o construtor: serve quando o tipo -esperado já deixa claro qual construtor usar. +A notação `⟨_, _⟩` é a *notação de anônima* para o construtor: serve quando o tipo esperado já deixa claro qual construtor usar. ```lean def origin₃ : Point := ⟨0.0, 0.0⟩ ``` -Uma função sobre `Point` também pode desmontar o argumento com `⟨_, _⟩`, em -vez de projetar campo a campo: +Uma função sobre `Point` também pode desmontar o argumento com `⟨_, _⟩`, em vez de projetar campo a campo: ```lean def addPoints (p1 p2 : Point) : Point := @@ -308,26 +259,16 @@ def scaleX (p : Point) (factor : Float) : Point := #eval scaleX ⟨2.0, 3.0⟩ 10.0 ``` -# Tipos indutivos - -Tipos indutivos vêm antes da recursão porque, em Lean, uma função -recursiva se escreve casando padrão sobre as formas de um tipo indutivo: -sem o tipo declarado, não há sobre o que recursar. - -`inductive` declara um tipo listando as formas que seus valores podem ter. -Quando nenhuma forma carrega argumento, o tipo é uma enumeração; quando -carrega, é um registro variante; quando a forma se refere ao próprio tipo -sendo definido, é uma árvore. As três coisas são o mesmo mecanismo. +# Tipos Indutivos +%%% +tag := "tipos-indutivos" +%%% -Essa é a construção mais importante do curso. Em {ref "SeaBattle"}[Batalha Naval] -veremos que uma gramática escrita na notação usual — a Forma de -Backus-Naur — é literalmente um tipo `inductive`, e daí em diante todo -fragmento da língua é declarado assim. +Tipos indutivos vêm antes da recursão porque, em Lean, uma função recursiva se escreve casando padrão sobre as formas de um tipo indutivo: sem o tipo declarado, não há sobre o que recursar. -A enumeração é o caso mais simples. `deriving Repr, DecidableEq` pede que a -exibição e o teste de igualdade sejam gerados em vez de escritos à mão. +A palavra-chave `inductive` declara um tipo listando as formas que seus valores podem ter. Quando nenhuma forma carrega argumento, o tipo é uma enumeração; quando carrega, é um registro variante; quando a forma se refere ao próprio tipo sendo definido, é uma árvore. As três coisas são o mesmo mecanismo. -Os dias da semana, nada mais são dias da semana. +Essa é a construção mais importante do curso. Em {ref "SeaBattle"}[Batalha Naval] veremos que uma gramática escrita na notação usual — a Forma de Backus-Naur — é literalmente um tipo `inductive`, e daí em diante todo fragmento da língua é declarado assim. A enumeração é o caso mais simples. `deriving Repr, DecidableEq` pede que a exibição e o teste de igualdade sejam gerados em vez de escritos à mão. Os dias da semana, nada mais são dias da semana. ```lean inductive Day where @@ -342,67 +283,19 @@ deriving Repr ``` ::::exercise (rating := 1) (name := "is-weekend") - Complete `isWeekend`, que responde se o dia é sábado ou domingo. ```lean def isWeekend (d : Day) : Bool := - solution!(match d with - | .saturday => true - | .sunday => true - | _ => false) + solution!( + match d with + | .saturday => true + | .sunday => true + | _ => false) ``` - :::: -`Bool` é a enumeração de duas formas; `Nat` é o caso em que uma das formas -se refere ao próprio tipo que está sendo definido. E `#print` mostra a -declaração. - -```lean (name := c2print2) -#print Bool -``` - -```leanOutput c2print2 -inductive Bool : Type -number of parameters: 0 -constructors: -Bool.false : Bool -Bool.true : Bool -``` - -```lean (name := c2print3) -#print Day -``` - -```leanOutput c2print3 -inductive IntroL.Day : Type -number of parameters: 0 -constructors: -IntroL.Day.monday : Day -IntroL.Day.tuesday : Day -IntroL.Day.wednesday : Day -IntroL.Day.thursday : Day -IntroL.Day.friday : Day -IntroL.Day.saturday : Day -IntroL.Day.sunday : Day -``` - -```lean (name := c2print4) -#print Nat -``` - -```leanOutput c2print4 -inductive Nat : Type -number of parameters: 0 -constructors: -Nat.zero : ℕ -Nat.succ : ℕ → ℕ -``` - -Ou seja: um natural é `Nat.zero`, ou é `Nat.succ n` para algum natural `n`, -e nada mais. O `2` que se escreve é notação para `Nat.succ (Nat.succ -Nat.zero)`. +O tipo {lean}`Bool` é a enumeração de duas formas, dois construtores. O tipo {lean}`Nat` é o caso em que uma das formas se refere ao próprio tipo que está sendo definido. Podemos usar `#print Nat` para mostrar a declaração do tipo {lean}`Nat`. ```lean example : 2 = Nat.succ (Nat.succ Nat.zero) := rfl @@ -410,19 +303,11 @@ example : 2 = Nat.succ (Nat.succ Nat.zero) := rfl # Recursão -Uma definição recursiva precisa de duas coisas: ter caso base, e chegar -nele. O segundo não é uma recomendação — é uma exigência que o compilador -verifica, e a definição é rejeitada se ele não conseguir ver que a -recursão termina. +Uma definição recursiva precisa de duas coisas: ter caso base, e chegar nele. O segundo não é uma recomendação — é uma exigência que o compilador verifica, e a definição é rejeitada se ele não conseguir ver que a recursão termina. -Em `Nat`, os dois casos do tipo dão as duas coisas de uma vez. Casar por -`0` (`Nat.zero`) e `n + 1` (`Nat.succ n`). O caso base é `0`, e a chamada -recursiva recebe o `n` que estava dentro do `succ`, necessariamente menor. -Não há um terceiro caso a esquecer, e não há argumento para o qual a -função não responda. +Em `Nat`, os dois casos do tipo dão as duas coisas de uma vez. Casar por `0` (`Nat.zero`) e `n + 1` (`Nat.succ n`). O caso base é `0`, e a chamada recursiva recebe o `n` que estava dentro do `succ`, necessariamente menor. Não há um terceiro caso a esquecer, e não há argumento para o qual a função não responda. -O fatorial é o exemplo mínimo dessa forma: um caso base e um caso que -chama a si mesmo com um argumento menor. +O fatorial é o exemplo mínimo dessa forma: um caso base e um caso que chama a si mesmo com um argumento menor. ```lean def factorial : Nat → Nat @@ -430,28 +315,7 @@ def factorial : Nat → Nat | n + 1 => (n + 1) * factorial n ``` -```lean (name := c2eval12) -#eval factorial 5 -``` - -```leanOutput c2eval12 -120 -``` - -```lean (name := c2eval13) -#eval factorial 0 -``` - -```leanOutput c2eval13 -1 -``` - -A mesma função sem casar padrão, decidindo o caso base com um `if`. -Funciona, e serve de contraste: aqui o argumento da chamada recursiva é `x -- 1`, e que ele seja menor que `x` é um fato a ser verificado, não algo que -a forma da definição já garanta. Neste caso Lean verifica sozinho; em -definições menos óbvias, não — e aí a prova de terminação passa a ser -trabalho do programador. +A mesma função sem casar padrão, decidindo o caso base com um `if`. Funciona, e serve de contraste: aqui o argumento da chamada recursiva é `x - 1`, e que ele seja menor que `x` é um fato a ser verificado, não algo que a forma da definição já garanta. Neste caso Lean verifica sozinho; em definições menos óbvias, não — e aí a prova de terminação passa a ser trabalho do programador. ```lean def factorial' (x : Nat) : Nat := @@ -459,11 +323,7 @@ def factorial' (x : Nat) : Nat := else x * factorial' (x - 1) ``` -O casamento de padrão de `factorial` é um _açucar sintático_, na verdade a -expressão `match` está oculta na definição. A seguir, usamos de forma -explicita. - -Como exemplo, vamos implementar em Lean um gerador recursivo de sentença. +O casamento de padrão de `factorial` é um _açucar sintático_, na verdade a expressão `match` está oculta na definição. A seguir, usamos de forma explicita. Como exemplo, vamos implementar em Lean um gerador recursivo de sentença. ```lean def gen (x : Nat) : String := @@ -474,15 +334,7 @@ def gen (x : Nat) : String := def genS (n : Nat) : String := gen n ++ "." ``` -```lean (name := c2eval14) -#eval genS 3 -``` - -```leanOutput c2eval14 -"Sentences can go on and on and on and on." -``` - -A função de story a seguir fornece outro exemplo de recursão. +A função `story` a seguir fornece outro exemplo de recursão. ```lean def story : Nat → String @@ -498,29 +350,14 @@ def story : Nat → String story k ++ "'" ``` -podemos usar `#eval story 2` direto, mas as quebras de linha não seriam -interpretadas. o símbolo `<|` faz com que a expressão `story 2` seja -interpretada antes de passada para a função `IO.println` que efetivamente -imprime uma linha na saída. +podemos usar `#eval story 2` direto, mas as quebras de linha não seriam interpretadas. o símbolo `<|` faz com que a expressão `story 2` seja executada antes de passada para a função `IO.println` que efetivamente interpreta as quebras de linha e outros caracteres especiais que possam estar contidos em uma string. ```lean (name := c2eval15) #eval IO.println <| story 2 ``` -```leanOutput c2eval15 -The night was pitch dark, mysterious and deep. -Ten cannibals were seated around a boiling cauldron. -Their leader got up and addressed them like this: -'The night was pitch dark, mysterious and deep. -Ten cannibals were seated around a boiling cauldron. -Their leader got up and addressed them like this: -'Let's cook and eat that final missionary, and off to bed.'' -``` - ::::exercise (rating := 1) (name := "sum-to") - -Implemente `sumTo n` para devolver `0 + 1 + ... + n` e termine a prova de -que a função está correta para a entrada `4`. +Implemente `sumTo n` para devolver `0 + 1 + ... + n` e termine a prova de que a função está correta para a entrada `4`. ```lean def sumTo : Nat → Nat := @@ -530,41 +367,19 @@ def sumTo : Nat → Nat := theorem sumTo_test : sumTo 4 = 10 := solution!(by rfl) ``` - :::gradeTheorem "1" sumTo_test ::: :::: -# Listas e polimorfismo +# Listas e Polimorfismo -`List α` é o tipo das listas de elementos do tipo `α`, e é um tipo indutivo -como os da seção anterior: uma lista é vazia, `[]` (`List.nil`), ou é um -elemento seguido de uma lista, `x :: xs` (`List.cons`). Nada mais é uma -lista. +`List α` é o tipo das listas de elementos do tipo `α`, e é um tipo indutivo como os da seção anterior: uma lista é vazia, `[]` (`List.nil`), ou é um elemento seguido de uma lista, `x :: xs` (`List.cons`). Nada mais é uma lista. ```lean (name := c2print5) #print List ``` -```leanOutput c2print5 -inductive List.{u} : Type u → Type u -number of parameters: 1 -constructors: -List.nil : {α : Type u} → List α -List.cons : {α : Type u} → α → List α → List α -``` - -É por isso que a recursão sobre lista tem exatamente a forma da recursão -sobre `Nat` — dois casos, e o segundo dá acesso a algo estritamente menor, -aqui a cauda. - -O `α` em `List α` é um parâmetro: `List Nat` e `List String` são tipos -diferentes, produzidos pelo mesmo `List`. Uma função que não olha para -dentro dos elementos não tem por que se comprometer com um deles. - -Como já falamos, `{α : Type}` declara o parâmetro entre chaves, o que o -torna _implícito_. Lean o descobre a partir do argumento, e quem chama não -escreve. +É por isso que a recursão sobre lista tem exatamente a forma da recursão sobre `Nat`. Dois casos, e o segundo dá acesso a algo estritamente menor, aqui a cauda. O `α` em `List α` é um parâmetro: {lean}`List Nat` e {lean}`List String` são tipos diferentes, produzidos pelo mesmo `List`. Uma função que não olha para dentro dos elementos não tem por que se comprometer com um deles. Como já falamos, `{α : Type}` declara o parâmetro entre chaves, o que o torna _implícito_. Lean o descobre a partir do argumento, e quem chama não escreve. ```lean def size {α : Type} : List α → Nat @@ -572,34 +387,16 @@ def size {α : Type} : List α → Nat | _ :: xs => 1 + size xs ``` -```lean (name := c2eval16) -#eval size [10, 20, 30] -``` - -```leanOutput c2eval16 -3 -``` - -```lean (name := c2eval17) -#eval size ["Chomsky", "Montague"] -``` - -```leanOutput c2eval17 -2 -``` - ::::exercise (rating := 1) (name := "sum-list") - -`sumList` soma os elementos de uma lista. Complete e termine a prova. +Complete `sumList` que soma os elementos de uma lista. ```lean -def sumList : List Nat → Nat := - solution!(fun +def sumList (ns : List Nat) : Nat := + solution!(match ns with | [] => 0 | x :: xs => x + sumList xs) -theorem sumList_test : sumList [1, 2, 3, 4] = 10 := - solution!(by rfl) +theorem sumList_test : sumList [1, 2, 3, 4] = 10 := solution!(by rfl) ``` :::gradeTheorem "1" sumList_test @@ -607,12 +404,11 @@ theorem sumList_test : sumList [1, 2, 3, 4] = 10 := :::: ::::exercise (rating := 1) (name := "count-zeros") - -`countZeros` conta quantos zeros a lista tem. Idem. +Termina a implementação de `countZeros`, que conta quantos zeros temos na lista passada. ```lean -def countZeros : List Nat → Nat := - solution!(fun +def countZeros (ns : List Nat) : Nat := + solution!(match ns with | [] => 0 | x :: xs => if x == 0 then 1 + countZeros xs else countZeros xs) @@ -620,247 +416,92 @@ def countZeros : List Nat → Nat := theorem countZeros_test : countZeros [0, 1, 0, 2, 0] = 3 := solution!(by rfl) ``` - :::gradeTheorem "1" countZeros_test ::: :::: -# O tipo Option +# O tipo {lean}`Option` -Uma função de tipo `List α → α` promete devolver um elemento para qualquer -lista que receba. Para a lista vazia não existe elemento nenhum, e a -promessa é impossível. Não por falta de cuidado do programador, mas porque -o tipo afirma algo falso. +Uma função de tipo `List α → α` promete devolver um elemento para qualquer lista que receba. Para a lista vazia não existe elemento nenhum, e a promessa é impossível. A correção é no tipo, não no corpo: `List α → Option α` promete devolver _ou_ um elemento (`some x`) _ou_ nada (`none`). Quem chama fica obrigado a tratar os dois casos. O ganho é que o caso sem resposta deixa de ser invisível: ele está na assinatura, não pode ser ignorado. -A correção é no tipo, não no corpo: `List α → Option α` promete devolver -_ou_ um elemento (`some x`) _ou_ nada (`none`). Quem chama fica obrigado a -tratar os dois casos. O ganho é que o caso sem resposta deixa de ser -invisível: ele está na assinatura, e não há como esquecê-lo. - -```lean (name := c2print6) -#print Option -``` - -```leanOutput c2print6 -inductive Option.{u} : Type u → Type u -number of parameters: 1 -constructors: -Option.none : {α : Type u} → Option α -Option.some : {α : Type u} → α → Option α -``` ```lean def myLast {α : Type} : List α → Option α | [] => none | [x] => some x | _ :: xs => myLast xs -``` - -```lean (name := c2eval18) -#eval myLast [1,2,3] -``` - -```leanOutput c2eval18 -some 3 -``` -```lean (name := c2eval19) -#eval myLast ([] : List Nat) -``` - -```leanOutput c2eval19 -none -``` - -```lean def average (xs : List Int) : Option Rat := if xs.isEmpty then none else some ((xs.sum : Rat) / (xs.length : Rat)) ``` -```lean (name := c2eval20) -#eval average [1,2,3,4] -``` - -```leanOutput c2eval20 -some (5 / 2) -``` - -```lean (name := c2eval21) -#eval average [] -``` - -```leanOutput c2eval21 -none -``` - -Algumas funções devolvem um valor default no caso ruim, em vez de -`Option`. `String.back` é uma delas, e vale conhecer as que são assim. - -```lean (name := c2eval22) -#eval "rad".back -``` - -```leanOutput c2eval22 -'d' -``` +Como alternativa ao retorno de um {lean}`Option`, algumas funções como {lean}`String.back` retornam o valor _default_ do tipo que retornam. O tipo `Char` tem como valor default {lean}`'A'`. -```lean (name := c2eval23) +```lean +#eval (default : Char) #eval "".back ``` -```leanOutput c2eval23 -'A' -``` - -# Processamento de listas e composição de funções +# Processamento de Listas +%%% +tag := "processamento-listas" +%%% -Algumas perações cobrem quase todo uso de lista no curso. Todas se -escreveriam por recursão, como `size` acima, mas estas função de ordem -superior simplificam nosso trabalho. +Algumas operações cobrem quase todo uso de lista no curso. Todas se escreveriam por recursão, como `size` acima, mas estas função de ordem superior simplificam nosso trabalho. -`map` aplica uma função a cada elemento; `filter` filtra a lista com os -que satisfazem uma condição. A `foldl` (e também temos a `foldr`) reduzem -a lista a um valor final a partir do processamento sucesso de uma função. +A função {lean}`List.map` aplica uma função a cada elemento; {lean}`List.filter` filtra a lista com os que satisfazem uma condição. A {lean}`List.foldl` (e também temos a {lean}`List.foldr`) reduzem a lista a um valor final a partir do processamento sucesso de uma função. ```lean def entities : List String := ["Dorothy", "Toto", "Aunt Em", "Scarecrow"] -``` -```lean (name := c2eval24) #eval entities.map String.length -``` - -```leanOutput c2eval24 -[7, 4, 7, 9] -``` - -```lean (name := c2eval25) #eval entities.filter (fun x => x.length > 4) -``` - -```leanOutput c2eval25 -["Dorothy", "Aunt Em", "Scarecrow"] -``` - -```lean (name := c2eval26) #eval entities.foldl (fun s a => a.length + s) 0 ``` -```leanOutput c2eval26 -27 -``` - -`all` e `any` perguntam se _todos_ os elementos satisfazem uma condição, -ou se _algum_ satisfaz, ambas devolvem `Bool`. +As funções {lean}`List.all` e {lean}`List.any` perguntam se _todos_ os elementos satisfazem uma condição, ou se _algum_ satisfaz, ambas devolvem `Bool`. ```lean (name := c2eval27) #eval entities.all (fun e => e.length > 2) -``` - -```leanOutput c2eval27 -true -``` - -```lean (name := c2eval28) #eval entities.any (fun e => e.startsWith "T") ``` -```leanOutput c2eval28 -true -``` +# Composição de Funções +%%% +tag := "composicao-funcoes" +%%% -E a composição: `f ∘ g` é a função que aplica `g` e depois `f`, de modo que -`(f ∘ g) x` é `f (g x)`. Ela produz função nova sem nomear argumento -nenhum — `double ∘ double` é quadruplicar. +E a composição: `f ∘ g` é a função que aplica `g` e depois `f`, de modo que `(f ∘ g) x` é `f (g x)`. Ela produz função nova sem nomear argumento nenhum — `double ∘ double` é quadruplicar. ```lean (name := c2eval29) #eval (square₁ ∘ square₂) 5 -``` - -```leanOutput c2eval29 -625 -``` - -```lean (name := c2eval30) #eval entities.map (size ∘ String.toList) ``` -```leanOutput c2eval30 -[7, 4, 7, 9] -``` - -# As duas leituras de uma função - -Uma função admite duas leituras, e as duas importam: - -* *extensional* — a função como tabela: o conjunto de pares - entrada/saída. Uma conversão de Celsius para Fahrenheit é a tabela - `{(0, 32), (100, 212), …}`, ponto. -* *intensional* — a função como instrução de cálculo. A mesma conversão - é `x ↦ x * 9 / 5 + 32`, uma receita que produz a tabela sem precisar - listá-la. - -Em Lean, `def` escreve sempre a versão intensional — a instrução —, mas -duas instruções diferentes podem ser a mesma função, no sentido -extensional, se produzem a mesma tabela. É isso que `funext` verifica: -duas funções são iguais quando concordam em todo ponto do domínio. +Podemos compor duas conversões, de Kelvin para Celsius, depois de Celsius para Fahrenheit. O símbolo `∘` é expandido para `Function.comp`, e `(f ∘ g) x = f (g x)`. primeiro `g`, depois `f`, na ordem em que a leitura da notação sugere o contrário. ```lean def celsiusToFahrenheit (c : Int) : Int := c * 9 / 5 + 32 -``` - -```lean (name := c3eval6) -#eval celsiusToFahrenheit 0 -``` - -```leanOutput c3eval6 -32 -``` - -```lean (name := c3eval7) -#eval celsiusToFahrenheit 100 -``` - -```leanOutput c3eval7 -212 -``` - -## Composição -Componhamos duas conversões: de Kelvin para Celsius, depois de Celsius -para Fahrenheit. `∘` é `Function.comp`, e `(f ∘ g) x = f (g x)` — -primeiro `g`, depois `f`, na ordem em que a leitura da notação sugere o -contrário. - -```lean def kelvinToCelsius (k : Int) : Int := k - 273 def kelvinToFahrenheit : Int → Int := celsiusToFahrenheit ∘ kelvinToCelsius ``` -```lean (name := c3eval8) -#eval kelvinToFahrenheit 373 -``` - -```leanOutput c3eval8 -212 -``` - -# Classes de tipos +# Classes de Tipos +%%% +tag := "classes" +%%% -Nós já vimos isso lá no começo, mas `count` conta ocorrências em qualquer -lista cujos elementos se possam comparar. Essa exigência entra na -assinatura entre colchetes, `[BEq α]`: uma instância de igualdade para -`α`, que Lean encontra sozinho no ponto de uso. +Vamos definir uma função para contar as ocorrências de um valor de um tipo `α`, em qualquer lista de valores do tipo `α`. Para esta função, nossa única exigência é garantir que poderemos comparar valores do tipo `α`. Essa exigência entra na assinatura entre colchetes, `[BEq α]`, uma instância de igualdade para `α`, que Lean encontra sozinho no ponto de uso. Duas noções de igualdade convivem, e vale separá-las desde já: * `BEq α` devolve `Bool` e se escreve `==`. -* `DecidableEq α` devolve uma _prova_ de igualdade ou de desigualdade. - Permite usar `=` num `if` e usar o resultado numa demonstração. +* `DecidableEq α` devolve uma _prova_ de igualdade ou de desigualdade. Permite usar `=` num `if` e usar o resultado numa demonstração. Tente remover `[BEq α]` na definição abaixo. @@ -870,443 +511,103 @@ def count {α : Type} [BEq α] (x : α) : List α → Nat | y :: ys => if x == y then count x ys + 1 else count x ys ``` -```lean (name := c2eval31) -#eval count 2 [1, 2, 2, 3] -``` +Até aqui só usamos a classe {lean}`BEq`, tanto {lean}`Nat` quanto {lean}`String` tem instâncias para esta classe e por isso {name}`count` funcionará para estes tipos. -```leanOutput c2eval31 -2 -``` - -```lean (name := c2eval32) -#eval count "thou" ["thou","art","thou"] -``` - -```leanOutput c2eval32 -2 -``` - -Até aqui só *usamos* classes: `[BEq α]` pede uma instância que o Lean -encontra sozinho. Falta o outro lado — declarar uma. +Na declaração do tipo {lean}`Day`, a instrução `deriving Repr` pediu para que uma instância padrão para a classe `Repr` fosse gerada. E podemos também pedir para que seja gerada uma instância para {lean}`BEq`. -Na verdade já declaramos várias, sem escrever nenhuma. Toda vez que um -tipo termina com `deriving Repr`, o Lean escreve por nós a instância de -`Repr` que o `#eval` usa para exibir valores daquele tipo. É o que -`Day` faz: - -```lean (name := c2evalDayRepr) -#eval Day.saturday -``` +```lean +deriving instance BEq for Day -```leanOutput c2evalDayRepr -IntroL.Day.saturday +#eval count Day.friday [.friday, .sunday, .friday, .monday] ``` -O que sai é o nome do construtor, porque é isso que uma instância -derivada sabe fazer. Para escolher a forma de exibição, a instância -tem de ser escrita à mão, com a palavra-chave `instance`. A classe -para isso é `ToString`, que dá sentido a `toString`: +A seguir, vamos customizar a instância de {name}`Day` para a classe {name}`Repr`. Usamos a palavra-chave `instance`. Não precisamos dar nome a instâncias, mas neste caso usamos `insReprDay`. ```lean -instance : ToString Day where - toString - | .monday => "segunda" - | .tuesday => "terça" - | .wednesday => "quarta" - | .thursday => "quinta" - | .friday => "sexta" - | .saturday => "sábado" - | .sunday => "domingo" -``` +instance insReprDay : Repr Day where + reprPrec := fun d _n => + match d with + | .monday => f!"segunda" + | .tuesday => f!"terça" + | .wednesday => f!"quarta" + | .thursday => f!"quinta" + | .friday => f!"sexta" + | .saturday => f!"sábado" + | .sunday => f!"domingo" -```lean (name := c2evalDayToString) -#eval toString Day.saturday +#eval Day.friday ``` -```leanOutput c2evalDayToString -"sábado" -``` - -A instância não tem nome: quem a procura é o Lean, pelo tipo, e não -nós pelo nome. Declarar uma instância é dizer "este tipo pertence a -esta classe, e eis como" — implementar os campos que a classe exige, -aqui só o `toString`. - -`Repr` e `ToString` convivem porque servem a coisas diferentes: `Repr` -exibe para quem está programando e tende a mostrar a estrutura; -`ToString` produz o texto que se quer mostrar a quem lê. Nos capítulos -seguintes, quase toda instância escrita à mão será de `ToString` — para -que uma árvore sintática se imprima como a sentença que ela representa. - -# Cadeias e textos - -`String` é uma sequência UTF-8 empacotada, não uma lista de caracteres. -Isso a torna eficiente para guardar texto e inadequada para percorrer a -cadeia. Não há padrão `c :: cs` para casar diretamente numa `String`. +# Cadeias de Textos +%%% +tag := "strings" +%%% -Mas podemos converter uma `String` em uma lista de caracteres e uma lista -de caracteres em uma `String`. +`String` é uma sequência UTF-8 empacotada, não uma lista de caracteres. Isso a torna eficiente para guardar texto e inadequada para percorrer a cadeia. Não há padrão `c :: cs` para casar diretamente numa `String`. Mas podemos converter uma `String` em uma lista de caracteres e uma lista de caracteres em uma `String`. ```lean def hword : List Char → Bool | [] => false | c :: cs => c == 'h' || hword cs -``` -```lean (name := c2eval33) #eval hword "shrimptoast".toList -``` - -```leanOutput c2eval33 -true -``` - -```lean (name := c2eval34) #eval hword "antiquing".toList -``` - -```leanOutput c2eval34 -false -``` -```lean def reversal : List Char → List Char | [] => [] | c :: t => reversal t ++ [c] -``` -```lean (name := c2eval35) #eval String.ofList (reversal "Chomsky".toList) -``` - -```leanOutput c2eval35 -"yksmohC" -``` -Remove o último caractere. - -```lean def initS (s : String) : String := String.ofList s.toList.dropLast -``` -```lean (name := c2eval36) -#eval initS "flicka" +#eval initS "Brasil" ``` -```leanOutput c2eval36 -"flick" -``` +# Lean e o Cálculo Lambda +%%% +tag := "lambda" +%%% -# Cálculo lambda - -A notação `fun x => e` não é invenção de linguagem de programação. Ela -resolve uma ambiguidade real, e vale ver qual. - -A expressão `x² + y` não determina uma função. Ela pode ser lida como -função de `x`, com `y` fixo; como função de `y`, com `x` fixo; ou como -função dos dois. O que falta é dizer qual variável é o parâmetro — e o -operador lambda é exatamente o marcador que diz isso. Em `λx ↦ x² + y`, -o `x` está *ligado* e o `y` está *livre*. - -O nome da variável ligada não importa: `λz ↦ z² + y` é a mesma função. E -isso não é convenção — em Lean as duas são o mesmo termo, e o `rfl` -prova: - -```lean -example : - (fun (x : Nat) => x * x) = - (fun (z : Nat) => z * z) := rfl -``` - -## A gramática dos termos - -O cálculo lambda tem três formas de construir expressão, e nada mais. -Escritas na notação usual para gramáticas — a Forma de Backus-Naur, ou -BNF: +No [cálculo lambda](https://en.wikipedia.org/wiki/Lambda_calculus) (LC) temos três formas de construir expressões. Usando a Forma de Backus-Naur (BNF) para representar a linguagem de LC. ```bnf E ::= _v | "(" E E ")" | "(" "λ" _v "↦" E ")" ; ``` -Leia: uma expressão é uma variável, ou a justaposição de duas expressões -(aplicação), ou um lambda seguido de variável e expressão (abstração). A -última cláusula é implícita e importante: *nada além disso é -expressão*. - -Aqui está o ponto. Uma gramática BNF é uma definição indutiva, e uma -definição indutiva é um tipo `inductive` — o mesmo mecanismo com que -{ref "Morphology"}[Morfologia] declara as classes de declinação do sueco -e os traços fonológicos. As duas coisas são a mesma, escritas em notações -diferentes: - -A gramática acima, como tipo. Cada cláusula da BNF virou um construtor. +Uma expressão é uma variável, ou a justaposição de duas expressões (aplicação), ou um lambda seguido de variável e expressão (abstração). E nada além disso é expressão. A gramática acima pode ser implementada como um tipo indutivo. Cada cláusula da BNF corresponde a um construtor. ```lean inductive Lam where | var (name : String) | app (fn arg : Lam) - | lam (binder : String) (body : Lam) -``` - -Essa correspondência é o motor do curso. Daqui em diante, cada -fragmento da língua vai ser dado por uma gramática, e a gramática vai -ser um tipo `inductive` — o que torna "esta expressão é bem formada" a -mesma coisa que "este termo tem esse tipo". - -Aqui, `Lam` fica como ilustração e não será usado: o cálculo lambda que -interessa é o próprio Lean, não uma cópia dele dentro de Lean. - -## Redução - -O que se faz com uma aplicação é substituir. A regra é uma só: - -``` -(λx ↦ E) A → E\[x := A\] -``` - -onde `E\[x := A\]` é `E` com toda ocorrência livre de `x` trocada por -`A`. Isso é a β-redução, e é o único mecanismo de cálculo do cálculo -lambda inteiro. - -Em Lean essa redução é o que o `#eval` executa e o que o `rfl` verifica: - -```lean -example : (fun (x : Nat) => x + 42) 5 = 5 + 42 := rfl + | abs (binder : String) (body : Lam) ``` -## Captura de variável +Essa correspondência é o motor do curso. Cada fragmento de linguagem, expresso como uma gramática, pode ser formalizado como um tipo `inductive`. O que torna "esta expressão é bem formada" a mesma coisa que "este termo tem esse tipo". O tipo `Lam` não será usado. Lean é baseado no Cálculo de Construtores Indutivos (CiC), uma extensão de LC com tipos. Mas `Lam` serve apenas para ilustrar a idéia de como uma gramática para uma linguagem pode ser implementada como tipo indutivo. Veremos outros exemplos no decorrer do texto. -Substituir ingenuamente dá errado, e o exemplo clássico merece atenção -porque o erro é silencioso. Considere aplicar `λyλx ↦ x + y` ao -argumento `x`. - -Trocando `y` por `x` sem cuidado, obtém-se `λx ↦ x + x` — a função que -soma um número a si mesmo. Mas o resultado correto é a função que soma -`x` a um número dado: o `x` que veio de fora foi *capturado* pelo `λx` -que já estava lá. Que o resultado é outro se vê renomeando antes: `λyλz -↦ z + y` aplicado a `x` dá `λz ↦ z + x`, que é o certo. - -A saída é renomear a variável ligada quando houver risco de captura. -Lean faz isso sozinho — internamente as variáveis ligadas não têm nome, -e o problema não existe: +A redução de um termo lambda significa simplificar o termo até um formato que não adimite maiores simplficações. Quando aplicamos um termo a outro termo, temos uma β-redução. O parâmetro da função é substituido pelo termo passado como valor para a abstração no corpo da abstração. Em Lean essa redução é o que o `#eval` executa e o que o `rfl` verifica: ```lean -example (x : Nat) : - (fun y => fun z => z + y) x = (fun z => z + x) := rfl +#eval (λ x => x + 42) 5 = 5 + 42 +example : (fun x => x + 42) 5 = 47 := rfl ``` -## Funções são dados - -Abstração e aplicação, como definidas, não distinguem dados de funções. -Se tudo é expressão, então uma função pode receber função, devolver -função, e ser aplicada a si mesma. Não há duas categorias de coisas. +A substituição de termos por termos não é trivial, considere a aplicação de `x` em `(λ y λ x ↦ x + y) x`. Trocando `y` por `x`, obtém-se `λ x ↦ x + x`. Mas o resultado correto é uma função que soma dois valores distintos. Em Lean este tipo de erro não ocorre. -É isso que permite escrever uma função que aplica outra a um argumento -fixo: - -```lean -def applyToDragon (f : String → String) : String := - f "dragon" +Um aspecto do cálculo lambda é que reduções podem não terminar. Observe o comportamento de redução de `(λ x ↦ x x) (λ x ↦ x x)`. Esta expressão não é bem formada em Lean. Substituindo `x` por `(λx ↦ x x)` no corpo `x x`, obtém-se `(λx ↦ x x) (λx ↦ x x)`, o mesmo termo de partida. A redução é portanto um laço, qualquer número de passos devolve o termo original, e a normalização nunca termina. Lean acusa dois erros na declaração a seguir. A auto-aplicação `x x` exige que `x` seja função de algum tipo `?m → ?n`, mas o argumento é o +próprio `x`, que teria então de ter simultaneamente o tipo `?m`. Como não há atribuição de tipos possível, o termo não pode nem ser _escrito_ em Lean. -def pluralize (w : String) : String := w ++ "s" -``` - -```lean (name := c3eval9) -#eval applyToDragon pluralize +```lean +error +def omega := (fun x => x x) (fun x => x x) ``` -```leanOutput c3eval9 -"dragons" -``` +No _cálculo lambda tipado_ (LCT) no qual Lean é baseado todo termo bem tipado tem forma normal, e a redução sempre termina. A contrapositiva é o que se observa aqui: um termo cuja redução não termina não pode ser bem tipado. É por isso que Lean pode ser ao mesmo tempo uma linguagem de programação e uma lógica consistente: a terminação é garantida pelos tipos. O preço é que Lean pode não conseguir determinar sozinho que uma função sempre termina, e nestes casos teremos que ajudar Lean fornecendo a prova de terminação. -::::exercise (rating := 1) (name := "twice") - -Outro exemplo de função de ordem superior é `λf λx ↦ f (f x)`, que -aplica uma função duas vezes a uma entrada dada. Ponha-a para trabalhar -reduzindo: `(λf λx ↦ f (f x)) (λy ↦ 1 + y)`. - -```lean -def twice {α : Type} (f : α → α) : α → α := - solution!(fun x => f (f x)) - -theorem twice_test1 : - twice (fun y => 1 + y) = fun x => 2 + x := solution!(by - funext x - show 1 + (1 + x) = 2 + x - omega) - -theorem twice_test2 : twice (fun y => 1 + y) 0 = 2 := - solution!(by decide) -``` - -:::gradeTheorem "1" twice_test1 twice_test2 -::: -:::: - -Um aspecto do cálculo lambda é que reduções podem não terminar. Observe -o comportamento de redução de `(λx ↦ x x) (λx ↦ x x)`, e depois de `(λx -↦ x x x) (λx ↦ x x x)`. - -Este exercício não se enuncia em Lean, e a razão é o assunto da questão: - -*1. Um passo de redução.* Substituindo `x` por `(λx ↦ x x)` no corpo -`x x`, obtém-se `(λx ↦ x x) (λx ↦ x x)` — o mesmo termo de partida. A -redução é portanto um laço: qualquer número de passos devolve o termo -original, e a normalização nunca termina. Este termo é o combinador -tradicionalmente chamado `Ω`. Já `(λx ↦ x x x) (λx ↦ x x x)` reduz a -`(λx ↦ x x x) (λx ↦ x x x) (λx ↦ x x x)`: além de não terminar, cada -passo produz um termo _maior_ que o anterior, então nem mesmo o tamanho -fica estável. - -*2. A mensagem do Lean.* Descomentando `def omega := (fun x => x x) -(fun x => x x)` abaixo, o Lean acusa dois erros: a auto-aplicação `x x` -exige que `x` seja função de algum tipo `?m → ?n`, mas o argumento é o -próprio `x`, que teria então de ter simultaneamente o tipo `?m`. O -elaborador precisa resolver `?m = ?m → ?n`, e não existe tipo que -satisfaça isso (falha o _occurs check_: `?m` ocorreria dentro de si -mesmo). Como não há atribuição de tipos possível, o termo não pode nem -ser _escrito_ em Lean. - -*3. Relação entre não terminar e não ter tipo.* O cálculo lambda -_tipado_ (simplesmente tipado, e também o de Lean) é fortemente -normalizante: todo termo bem tipado tem forma normal, e a redução sempre -termina. A contrapositiva é o que se observa aqui: um termo cuja -redução não termina não pode ser bem tipado. Os dois fenômenos têm a -mesma raiz — a auto-aplicação `x x` — e o sistema de tipos funciona -como um filtro que rejeita exatamente esses termos. É por isso que Lean -pode ser ao mesmo tempo uma linguagem de programação e uma lógica -consistente: a terminação é garantida pelos tipos, não pela boa vontade -do programador. (O preço é que Lean também rejeita programas que -terminam, mas cuja terminação ele não sabe verificar; daí a necessidade -de provar terminação em definições recursivas.) - -``` --- def omega := (fun x => x x) (fun x => x x) -``` - -# Tipos na gramática e na computação - -No cálculo lambda como está, toda expressão se aplica a toda expressão. -Nada impede escrever o número `4` aplicado a uma função, e o resultado -não é falso — é sem sentido. Tipos existem para excluir isso. - -A gramática dos tipos também é uma BNF, com duas cláusulas: - -```bnf -τ ::= _b | "(" τ "→" τ ")" ; -``` - -Há tipos básicos, e há tipos de função construídos a partir deles. Na -semântica, os dois básicos costumam ser `e`, das entidades, e `t`, dos -valores de verdade — a notação de Montague, que o capítulo sobre o -fragmento de inglês retoma. Em Lean, `t` é `Prop`. - -E a atribuição de tipos a expressões se dá por três regras: - -* *variáveis* — para cada tipo há variáveis daquele tipo; -* *abstração* — se `x : δ` e `E : τ`, então `(λx ↦ E) : δ → τ`; -* *aplicação* — se `E₁ : δ → τ` e `E₂ : δ`, então `(E₁ E₂) : τ`. - -Não há mais nada. O `#check` do Lean é essas três regras rodando: - -`restful` é uma propriedade de dias: aplicada a um, dá uma afirmação. -`opaque` declara o nome com o tipo e sem corpo — aqui o assunto são os -tipos, e qualquer definição serviria. - -```lean -opaque restful : Day → Prop - -section -variable (d : Day) -``` - -regra da aplicação: `restful : Day → Prop` e `d : Day`, logo `restful -d : Prop` - -```lean (name := c2check10) -#check restful d -``` - -```leanOutput c2check10 -restful d : Prop -``` - -regra da abstração: `y : Day` e `restful y : Prop`, logo o lambda é -`Day → Prop` - -```lean (name := c2check11) -#check fun (y : Day) => restful y -``` - -```leanOutput c2check11 -fun y => restful y : Day → Prop -``` - -```lean -end -``` +Para além dos tipos simples, em Lean, temos também os *tipos indutivos* (como apresentado), *tipos dependentes* (um tipo pode depender de um valor, como {lean}`Vector`), *proposições como tipos*, o tipo `Prop` como veremos em {ref "Proof"}[Proof], e os *universos* de tipos {lean}`Type`, {lean}`Type 1`, e assim por diante, o que evita os paradoxos que apareceriam se tivéssemos um tipo de todos os tipos. -## Lean como cálculo lambda - -O que se descreveu acima é o cálculo lambda com tipos simples, e Lean o -contém. Abstração, aplicação, β-redução, tipos de função: tudo o que -foi dito vale literalmente, e os `#check` acima são as regras de -tipagem sendo aplicadas. - -Lean vai além disso em pontos que o curso vai usar: - -* *tipos indutivos* — os deste capítulo, que aqui se revelam ser - gramáticas: uma BNF é um tipo, com casamento de padrão e recursão - garantidamente terminante; -* *tipos dependentes* — um tipo pode depender de um valor, o que - permite exigir na assinatura condições que aqui teriam de ser - verificadas à parte; -* *proposições como tipos* — `Prop` não é um tipo básico opaco: uma - prova de `P` é um termo de tipo `P`, e é por isso que o mesmo - verificador serve para checar programas e demonstrações; -* *universos* — `Type`, `Type 1`, e assim por diante, o que evita os - paradoxos que apareceriam se houvesse um tipo de todos os tipos. - -Para o que vem pela frente, a leitura útil é essa: o aparato da -semântica de Montague é um fragmento do que Lean oferece, e o excedente -é o que vai permitir demonstrar coisas sobre os significados, e não -apenas calculá-los. - -E o termo `(λx ↦ x x) (λx ↦ x x)` do exercício anterior? Você consegue -achar um tipo para ele? - -*Não.* Nenhuma atribuição de tipos funciona, e a maneira de mostrar -isso é tentar construí-la e ver onde ela quebra. - -Suponha que `λx ↦ x x` tenha tipo. Chame de `σ` o tipo de `x`. No corpo -`x x`, o `x` da esquerda está em posição de função aplicada a um -argumento, logo `σ` tem de ser um tipo de função: `σ = σ₁ → τ` para -algum `σ₁` e `τ`. O `x` da direita é o argumento dessa aplicação, então -seu tipo tem de ser o domínio: `σ = σ₁`. Combinando as duas exigências, -`σ = σ → τ`. Não há tipo simples que satisfaça essa equação: qualquer -solução teria de ser um tipo estritamente maior que si mesmo (a árvore -de `σ → τ` contém a de `σ` como subárvore própria), e não existe tipo -finito assim. É precisamente o _occurs check_ que o unificador do Lean -reporta ao dizer que `x` tem tipo `?m → ?n` mas se espera `?m`. - -Portanto `λx ↦ x x` já é intipável, e a fortiori a aplicação dele a si -mesmo também. Vale notar que a impossibilidade não é um defeito do -Lean: ela é consequência de o sistema ser fortemente normalizante. -Sistemas que admitem tipos recursivos (`σ ≅ σ → τ`, via `μ`-tipos) -conseguem tipar esse termo, mas ao preço de perder a garantia de -terminação — e, se usados como lógica, a consistência. - -# Tipos como disciplina - -O tipo de uma função diz o que ela aceita e o que devolve, e Lean -recusa a aplicação que não respeite isso — ao escrever, antes de rodar. - -Essa recusa é o instrumento central do texto. As árvores sintáticas das -gramáticas que vêm a seguir serão tipos, e os significados também; daí em -diante, "esta combinação de palavras não é bem formada" e "este -programa não tipa" passam a ser a mesma frase. ```lean end IntroL diff --git a/CSwL/Logic/FOL.lean b/CSwL/Logic/FOL.lean index 3bab90a..5ebd443 100644 --- a/CSwL/Logic/FOL.lean +++ b/CSwL/Logic/FOL.lean @@ -22,19 +22,22 @@ namespace FOL tag := "fol-intro" %%% -Se usarmos lógica proposicional para formalizar a frase "Toda maça é vermelha", teremos uma letra proposicional, um átomo indivisível que não nos permitiria capturar a idéia do quantificador e da dependencia declarada entre as _coisas_ que são maças e a cor destas mesmas _coisas_. Lógica de predicados acrescenta três ingredientes: +Se usarmos lógica proposicional para formalizar a frase "Toda maça é vermelha", teremos uma letra proposicional, um átomo indivisível que não nos permitiria capturar a idéia do quantificador e da dependencia declarada entre as _coisas_ que são maças e a cor destas mesmas _coisas_. Lógica de predicados acrescenta os seguintes ingredientes a sintaxe de Lógica Proposicional: * termos para representar indivíduos de um domínio. Os termos poderão ser variáveis ou funções aplicadas sobre termos; * proposições básicas serão predicados `n`-ários sobre termos; * fórmulas universalmente quantificadas, `∀` seguido de variável e fórmula; * fórmulas existencialmente quantificadas, `∃` seguido de variável e fórmula. + # Sintaxe de Lógica de Primeira Ordem %%% tag := "fol-syntax" %%% -Também chamada de "lógica de primeira ordem" (FOL, "first order logic"). Vamos assumir que predicados terão aridade de 1 até 3 (relações unárias, binárias e ternárias). Relações com mais de três argumentos quase nunca são necessárias para capturar a semântica de linguagem natural. A BNF completa segue abaixo e gera fórmulas como `¬P x`, `∀ x R x x` e `∀ x ∃ y R x y`. +Também chamada de "lógica de primeira ordem" (FOL, "first order logic"). Vamos assumir que predicados terão aridade de 1 até 3 (relações unárias, binárias e ternárias). Relações com mais de três argumentos quase nunca são necessárias para capturar a semântica de linguagem natural. + +A BNF completa segue abaixo e gera fórmulas como `¬P x`, `∀ x R x x` e `∀ x ∃ y R x y`. Note que não podemos aidna construir fórmulas com termos complexos como `∃ x P (f x)`, onde temos a função `f` recebendo uma variável e este termo passado como argumento para o predicado `P`. Nossos termos são apenas variáveis. ```bnf v ::= "x" | "y" | "z" | v "'" ; @@ -51,19 +54,6 @@ F ::= atom | "∃" v F ("quantificação existencial") ; ``` -Em uma fórmula `∀x F` (ou `∃x F`), o quantificador liga toda ocorrência de -`x` em `F` que não esteja já ligada por um `∀x`/`∃x` interno a `F`. Uma fórmula é *aberta* se tem ao menos uma ocorrência livre de variável, e *fechada* (também chamada *sentença*) caso contrário. Por exemplo, `(P x ∧ ∃x, R x x)` é aberta, o `x` de `P x` está fora do escopo do `∃x`. Mas `∃x (P x ∧ ∃x R x x)` é uma sentença. - -Essa distinção é o que motiva a ambiguidade de escopo de "Todo príncipe viu uma dama". Duas leituras possíveis, "para cada príncipe existe uma dama (talvez diferente) que ele viu" contra "existe uma dama que todo príncipe viu", formalizadas respectivamente como: - -``` -∀x (Prince x → ∃y (Lady y ∧ Saw x y)) -∃y (Lady y ∧ ∀x (Prince x → Saw x y)) -``` - -Repare que a leitura universal usa `→` como conectivo principal, e -a existencial usa `∧`. Já "Algum príncipe viu uma dama bonita" admite apenas uma formalização, `∃x∃y (Prince x ∧ Lady y ∧ Beautiful y ∧ Saw x y)`. - :::dev "Alexandre (arademaker)" Em Lean, indexar por aridade é mais natural do que empilhar primos: um `structure PredSymbol` com campos `name : String` e `arity : Nat` já @@ -93,7 +83,7 @@ def y : Variable := ⟨"y", []⟩ def z : Variable := ⟨"z", []⟩ ``` -`Formula α` é parametrizado no tipo dos termos que preenchem os predicados — por ora nossos termos são apenas `Variable`. +`Formula α` é parametrizado no tipo dos termos que preenchem os predicados. Por ora nossos termos são apenas `Variable`. ```lean inductive Formula (α : Type) where @@ -144,17 +134,15 @@ def Formula.format {α} [Repr α] : Formula α → Std.Format f!"({f1.format} & {f2.format})" | .disj f1 f2 => f!"({f1.format} | {f2.format})" - | .forall_ v f => f!"A {repr v} {f.format}" - | .exists_ v f => f!"E {repr v} {f.format}" + | .forall_ v f => f!"∀ {repr v} {f.format}" + | .exists_ v f => f!"∃ {repr v} {f.format}" instance {α} [Repr α] : Repr (Formula α) := ⟨fun f _ => f.format⟩ ``` -`Repr` é a classe que o `#eval` procura primeiro, e é por isso que basta escrever `#eval formula0`. Ela devolve um `Std.Format`, e não uma `String`. O segundo argumento que a instância ignora é a precedência. - -A seguir, `formula1` expressa que o predicado `R` é reflexivo enquanto `formula2` expressa que ele é simétrico. +A seguir, `formula1` expressa que o predicado `R` é reflexivo enquanto `formula2` expressa que ele é simétrico. Quando escrevermos `#eval formula1`, Lean irá procurar por esta instância de `Repr` para o tipo `Formula` declarada acima. O {name}`Std.Format` não é uma `String`, é um tipo que representa um documento com quebras de linha e identação. Nossa implementação está bastante simplificada. ```lean def formula1 : Formula Variable := @@ -165,12 +153,12 @@ def formula2 : Formula Variable := (.impl (.atom "R" [x, y]) (.atom "R" [y, x]))) ``` -Coletar as variáveis livres de uma fórmula é uma operação que faremos -mais de uma vez, com termos de tipos diferentes. Definimos uma só vez, -deixando como parâmetro a função que extrai as variáveis de um termo — -o que muda de um caso para outro é apenas ela. Nos quantificadores, -`filter` remove a variável ligada, e remove *todas* as suas -ocorrências. +Em uma fórmula `∀x F` (ou `∃x F`), o quantificador liga toda ocorrência de +`x` em `F` que não esteja já ligada por um `∀x`/`∃x` interno a `F`. Uma fórmula é *aberta* se tem ao menos uma ocorrência livre de variável, e *fechada* (também chamada *sentença*) caso contrário. Por exemplo, `(P x ∧ ∃x, R x x)` é aberta, o `x` de `P x` está fora do escopo do `∃x`. Mas `∃x (P x ∧ ∃x R x x)` é uma sentença. + +Essa distinção é o que motiva a ambiguidade de escopo de "Todo príncipe viu uma dama". Existem duas leituras possíveis. A primeira seria "para cada príncipe existe uma dama (talvez diferente) que ele viu" que podemos formalizar como `∀x (Prince x → ∃y (Lady y ∧ Saw x y))`. A segunda leitura seria "existe uma dama que todo príncipe viu" formalizada como `∃y (Lady y ∧ ∀x (Prince x → Saw x y))`. Repare que a leitura universal usa `→` como conectivo principal dentro da subfórmula, e a existencial usa `∧`. Já "Algum príncipe viu uma dama bonita" admite apenas uma formalização, `∃x∃y (Prince x ∧ Lady y ∧ Beautiful y ∧ Saw x y)`. + +Coletar as variáveis livres de uma fórmula é uma operação recorrente. Definimos uma só vez, deixando como parâmetro a função que extrai as variáveis de um termo — o que muda de um caso para outro é apenas ela. Nos quantificadores, `filter` remove a variável ligada, e remove *todas* as suas ocorrências. ```lean def Formula.freeVars {α} (vars : α → List Variable) : @@ -195,12 +183,8 @@ extrair as variáveis de um termo é devolvê-lo numa lista de um elemento. As fórmulas fechadas são as que têm a lista de livres vazia. ```lean -def freeVarsInFormula (f : Formula Variable) : - List Variable := - solution!(f.freeVars ([·])) - def closedForm (f : Formula Variable) : Bool := - solution!((freeVarsInFormula f).isEmpty) + solution!((f.freeVars (fun x => [x])).isEmpty) ``` :::: diff --git a/CSwL/Logic/PL.lean b/CSwL/Logic/PL.lean index 19a564a..23657d7 100644 --- a/CSwL/Logic/PL.lean +++ b/CSwL/Logic/PL.lean @@ -23,7 +23,7 @@ namespace PL tag := "pl-intro" %%% -Em {ref "Proof"}["Proof"] as fórmulas proposicionais foram escritas diretamente como termos do tipo `Prop`, e usando táticas construimos provas de proposições `α` a partir de um conjunto de hipóteses `Γ`. Isto é, em Lean mostramos como derivar `α` a partir de `Γ`, isto é `Γ ⊢ α`, de forma sintática. +Em {ref "Proof"}["Proof"] as fórmulas proposicionais foram escritas diretamente como termos do tipo `Prop`, e usando táticas construimos provas de proposições `α` a partir de um conjunto de hipóteses `Γ`. Isto é, mostramos como derivar `α` a partir de `Γ`, isto é `Γ ⊢ α`. Mas em Lean, `Prop` é um tipo e proposições particulares também são tipos. A variável `h` abaixo pode ser entendida como um identificador para uma "prova qualquer" da proposição `p ∧ q`. E Lean adota o princípio da "irrelevância da prova", ou seja, Lean não distingue diferentes provas de uma proposição. Como consequência, o tipo `Prop` não é computável, não é um "dado" que pode ser manipulado. Por exemplo, não conseguimos extrair os componentes de uma conjunção `a ∧ b`. Lean sabe que todas as provas de `a ∧ b` são irrelevantes e iguais, então ele não permite que você use uma prova para tomar decisões no mundo dos dados programáveis (`Type`). Em outras palavras, não podemos realizar casamento de padrões em `h` abaixo. @@ -52,21 +52,9 @@ Em um problema com um número finito de proposições, e os números costumam se tag := "pl-syntax" %%% -Formalmente, a sintaxe da LP é definida pela BNF abaixo. As variáveis proposicionais (ou símbolos sentenciais) são os `atom`. O uso do sufixo `'` no não-terminal `atom` é uma forma conveniente de expressar que podemos gerar quantos átomos forem necessários. +Para construir fórmulas como dados, não poderemos mais usar a notação de Lean disponível para os termos do tipo `Prop`. Quando escrevemos `p ∧ q`, o símbolo `∧` é um operador infixado (aparece no meio dos argumentos) e representa o construtor {lean}`And.intro` do tipo {lean}`And`. Os operadores, para serem usados de forma infixada, precisam ter um mecanismo de precedência para permitir que possamos escrever termos ambiguos como `p ∧ q ∧ r` que terão sua leitura associada a `p ∧ (q ∧ r)` e não `(p ∧ q) ∧ r`. Nada disso estará ao nosso dispor. -```bnf -atom ::= "p" | "q" | "r" | atom"'" ; -F ::= atom - | "¬" F ("negação") - | "(" F "∧" F ")" ("conjunção") - | "(" F "∨" F ")" ("disjunção") - | "(" F "→" F ")" ("implicação") - | "(" F "↔" F ")" ("se-somente-se") ; -``` - -Com esta gramática, podemos gerar fórmulas como `¬¬¬p'''`, `((p ∨ p') ∧ p')`, `(p ∧ (p' ∧ p'''))`. Sem parênteses a gramática pode gerar strings ambíguas: `p ∧ p′ ∨ p″` lê-se tanto como `(p ∧ p′) ∨ p″` quanto como `p ∧ (p′ ∨ p″)`, e a ambiguidade estrutural afeta o significado, como na sentença "era jovem e bonita ou triste". Nem todos os conectivos precisam ser definidos como "primitivos". O conectivo `→` poderia ser definido como uma abreviação para `p → q ≃ ¬ p ∨ q`. - -A gramática acima será representada pelo tipo indutivo `Form`. Um átomo é identificado por um nome, e o nome é uma `String`. Isso dá o inventário ilimitado que a gramática pede sem precisar enumerar símbolo por símbolo. +Nossas fórmulas serão representadas por termos do tipo indutivo `Form`. Um átomo é identificado por um nome, e o nome é uma `String`. Isso dá o inventário ilimitado que a gramática pede sem precisar enumerar símbolo por símbolo. ```lean inductive Form where @@ -76,10 +64,38 @@ inductive Form where | neg (f : Form) | conj (f g : Form) | disj (f g : Form) - deriving DecidableEq + deriving DecidableEq, Repr +``` + +Com este tipo, podemos representar fórmulas arbitrariamente complexas. + +```lean +#eval + let p : Form := .atom "p" + let q : Form := .atom "q" + let f₁ : Form := .neg (.neg p) + let f₂ : Form := .disj (.neg p) q + Form.conj f₁ f₂ +``` + +Como não temos símbolos infixados, não temos ambiguidade. As duas possíveis interpretações para a sentença ambigua em português "Maira é jovem e bonita ou triste" seriam: + +```lean +namespace Maria + +def j : Form := .atom "MJ" +def b : Form := .atom "MB" +def t : Form := .atom "MT" + +#eval Form.conj j (.disj b t) +#eval Form.disj (.conj j b) t + +end Maria ``` -Vale observar que a biblioteca `cslib` define o tipo `Cslib.Logic.PL.Proposition` que poderia ser usado nesta seção, mas isto introduziria uma complexidade desnecessária. Acima escolhemos não declarar os símbolos `→` e `↔` como construtores do tipo, eles serão funções que criam `Form` a partir de `Form`. +Vale observar que a biblioteca `cslib` define o tipo `Cslib.Logic.PL.Proposition` que poderia ser usado nesta seção, mas isto introduziria uma complexidade desnecessária. + +Nem todos os conectivos precisam ser definidos como "primitivos". Como vimos na seção {ref "pl-lean"}[pl-lean] a implicação pode ser definida como uma dijunção. E a dupla implicação como uma conjunção de implicações. ```lean def Form.impl (f g : Form) : Form := .disj (.neg f) g @@ -111,6 +127,8 @@ Três pessoas são suspeitas de torcer pelo Bangu F.C. Aparecido entrevistou os Termine a formalização dos depoimentos construindo uma expressão no tipo `Form`. ```lean +namespace Bangu + def A : Form := Form.atom "Auro" def J : Form := Form.atom "Joaquim" def C : Form := Form.atom "Claudia" @@ -118,10 +136,11 @@ def C : Form := Form.atom "Claudia" def depo1 : Form := solution!(.conj (.neg J) C) def depo2 : Form := solution!(.impl (.neg A) (.neg C)) def depo3 : Form := solution!(.conj C (.disj (.neg A) (.neg J))) + +end Bangu ``` ::: - :::exercise (rating := 1) (name := "exclusive-or") A expressão `p ∨ q` é verdadeira mesmo quando `p` e `q` são ambos verdadeiros. Em português, "ou" costuma ser exclusivo, como em "Você pode ficar com o sorvete ou com o algodão-doce, mas não com os dois." Defina um conectivo `xor` para "ou exclusivo", usando os conectivos já definidos. @@ -138,9 +157,12 @@ def form1 : Form := .conj (.atom "p") (.neg (.atom "p")) def form2 : Form := - Form.disjs [.atom "p1", .atom "p2", .atom "p3", .atom "p4"] + .disjs [.atom "p1", .atom "p2", .atom "p3", .atom "p4"] -#eval form2 +def form3 : Form := + let p : Form := .atom "p" + let q : Form := .atom "q" + .equi (.impl p q ) (.disj (.neg p) q) ``` :::exercise (rating := 1) (name := "count-operators") @@ -178,20 +200,22 @@ example : form2.depth = 3 := by decide ::: :::exercise (rating := 2) (name := "collect-atoms") -Implemente `propNames` para coletar a lista de nomes de átomos proposicionais que ocorrem numa fórmula. A lista resultante deve estar ordenada e sem repetições. +Implemente `propNames` para coletar a lista de nomes de átomos proposicionais que ocorrem numa fórmula. A lista resultante deve estar ordenada e sem repetições. O exemplo pode ser provado com {tactic}`native_decide`. ```lean -private def Form.propNamesRaw : Form → List String := - solution!(fun - | .atom name => [name] - | .top => [] - | .bot => [] - | .neg f => f.propNamesRaw - | .conj f g => f.propNamesRaw ++ g.propNamesRaw - | .disj f g => f.propNamesRaw ++ g.propNamesRaw) +def Form.propNamesRaw (f : Form) : List String := solution!( + match f with + | .atom name => [name] + | .top => [] + | .bot => [] + | .neg f => f.propNamesRaw + | .conj f g => f.propNamesRaw ++ g.propNamesRaw + | .disj f g => f.propNamesRaw ++ g.propNamesRaw) def Form.propNames (f : Form) : List String := solution!(f.propNamesRaw.eraseDups.mergeSort (· ≤ ·)) + +example : form1.propNames == ["p"] := solution!(by native_decide) ``` ::: @@ -230,11 +254,15 @@ Chamamos as fórmulas que são sempre verdade para qualquer valoração de suas Construa as valorações `vs1` e `vs2` de tal forma que os exemplos possam ser provados com a tática {tactic}`decide`. ```lean -def form3 : Form := - .disj (.atom "p") (.conj (.atom "q") (.atom "r")) +namespace TestVals -def form4 : Form := - .neg (.conj (.atom "p") (.neg (.atom "q"))) +def p : Form := .atom "p" +def q : Form := .atom "p" +def r : Form := .atom "r" + +def form3 : Form := .disj p (.conj q r) + +def form4 : Form := .neg (.conj p (.neg q)) def form5 : Form := .conj (.atom "a") (.impl (.neg (.atom "b")) (.atom "c")) @@ -242,13 +270,15 @@ def form5 : Form := def vs1 : List (String × Bool) := solution!([("p", true),("q", true)]) def vs2 : List (String × Bool) := solution!([("a", true),("b", true)]) -example : form3.eval vs1 = true := by solution!(decide) -example : form4.eval vs1 = true := by solution!(decide) -example : form5.eval vs2 = true := by solution!(decide) +example : form3.eval vs1 = true := solution!(by decide) +example : form4.eval vs1 = true := solution!(by decide) +example : form5.eval vs2 = true := solution!(by decide) + +end TestVals ``` ::: -A função a seguir gera a lista de todas as valorações sobre o conjunto dos nomes de átomos presentes em um termo do tipo `Form`. Com estas funções, podemos construir a tabela verdade de uma fórmula. +A função a seguir gera a lista de todas as valorações sobre o conjunto dos nomes de átomos presentes em um termo do tipo `Form`. ```lean def genVals : List String → List Valuation @@ -259,7 +289,11 @@ def genVals : List String → List Valuation def Form.allVals (f : Form) : List Valuation := genVals f.propNames +``` +Com estas funções, podemos construir a tabela verdade de uma fórmula. + +```lean #eval List.zip form1.allVals (form2.allVals.map (form2.eval ·)) ``` @@ -273,17 +307,23 @@ def Form.satisfiable (f : Form) : Bool := f.allVals.any (fun v => f.eval v) def Form.contradiction (f : Form) : Bool := - ¬ f.satisfiable + !f.satisfiable #eval (form1.contradiction, (Form.neg form1).tautology, form1.satisfiable) ``` +E como já sabemos da seção {ref "pl-lean"}[pl-lean], podemos mostrar que {name}`form3` é uma tautologia. + +```lean +#eval form3.tautology +``` + :::exercise (rating := 1) (name := "def-contingente") Complete a definição de fórmula contingente. Para provar o exemplo, use {tactic}`native_decide`. ```lean def Form.contingent (f : Form) : Bool := - solution!(f.satisfiable ∧ ¬ f.tautology) + solution!(f.satisfiable && !f.tautology) example : (Form.atom "q").satisfiable = true := by solution!(native_decide) @@ -314,8 +354,8 @@ def q : Form := Form.atom "q" def Feq1 : Form := Form.neg (.equi p q) def Feq2 : Form := solution!(.disj (.conj (.neg p) q) (.conj (.neg q) p)) -example : Feq1.equivalent Feq2 := by - solution!(native_decide) +example : Feq1.equivalent Feq2 = true := + solution!(by native_decide) ``` ::: @@ -348,10 +388,14 @@ def Form.impliesL (ps : List Form) (c : Form) : Bool := Complete a definição de `banguSolution` para que a fórmula represente a solução do problema dos torcedores do Bangu F.C. assumindo que os 3 depoimentos foram verdadeiros. A prova do exemplo é completada com {tactic}`native_decide`. ```lean +namespace Bangu + def banguSolution : Form := solution!(.conjs [A, (.neg J), C]) example : Form.impliesL [depo1, depo2, depo3] banguSolution = true := - by solution!(native_decide) + solution!(by native_decide) + +end Bangu ``` ::: diff --git a/CSwL/Logic/Proof.lean b/CSwL/Logic/Proof.lean index 9a05d9d..69b01fb 100644 --- a/CSwL/Logic/Proof.lean +++ b/CSwL/Logic/Proof.lean @@ -14,7 +14,7 @@ tag := "Proof" file := "Proof" %%% -Neste capítulo vamos falar sobre o tipo `Prop` em Lean para representação de proposições lógicas em tipos dependentes. A representação de proposições e contrução de provas é o que torna Lean um assistente de prova, além de linguagem de programação. Vamos apresentar provas como termos e a construção de provas com táticas. +Neste capítulo vamos falar sobre o tipo `Prop` em Lean para representação de proposições lógicas em tipos dependentes. A representação de proposições e construção de provas é o que torna Lean um assistente de prova, além de linguagem de programação. Vamos apresentar provas como termos e a construção de provas com táticas. Supomos conhecida a lógica proposicional e a de predicados — sintaxe, semântica, e a noção de consequência. Para uma apresentação a partir do início, ver {citep Bib.enderton2001}[]. @@ -79,90 +79,19 @@ example (x q : Nat) : 37 * x + q = 37 * x + q := :::: # Lógica Proposicional em Lean - -Os conectivos lógicos `∧`, `∨`, `→`, `↔` e `¬` estão disponíveis diretamente no Lean, de modo que uma fórmula proposicional pode ser representada como uma proposição em Lean. Isso nos fornece uma ponte conveniente entre a semântica da linguagem natural e o raciocínio formal. Podemos traduzir o conteúdo semântico de uma sentença para uma proposição em Lean e, em seguida, usar Lean para verificar se uma conclusão decorre de um conjunto de hipóteses. - -Vamos considerar um primeiro exemplo. Três irmãs — Ana, Maria e Cláudia — -foram a uma festa com vestidos de cores diferentes. Uma vestiu azul, a outra -branco, e a terceira, preto. Chegando à festa, o anfitrião perguntou quem era -cada uma delas. - -- A de azul respondeu: "Ana é a que está de branco"; -- A de branco disse: "Eu sou Maria"; -- A de preto respondeu: "Cláudia é quem está de branco". - -O anfitrião foi capaz de identificar cada irmã considerando que: - -- Ana sempre diz a verdade; -- Maria às vezes diz a verdade; -- Cláudia nunca diz a verdade. - -Para começar, vamos introduzir variáveis do tipo `Prop`, cada uma delas representado uma proposição. São 3 pessoas e 3 cores. Vamos representar "Ana veste azul" por `Aa` e assim por diante. - -```lean -section PL - -variable ( - Aa Ab Ap - Ma Mb Mp - Ca Cb Cp : Prop) -``` -A ideia é que as condições do problema sejam traduzidas em fórmulas proposicionais. Por exemplo, podemos formalizar a sentença "Ana veste azul, branco ou preto" com a fórmula em LP. - -```lean -#check Aa ∨ Ab ∨ Ap -``` - -Aqui cabe a observação de que a formalização em LP não foi obtida diretamente a partir da construção linguística original, uma oração coordenando seus constituintes no predicado. Intuitivamente, a sentença foi antes interpretada como três orações coordenadas (proposições completas), "Ana veste azul ou Ana veste branco ou Ana veste preto". - -A formalização completa do problema deve levar em consideração não apenas o que foi dito explicitamente mas algumas condições implicitamente assumidas. Definimos a estrutura `Premissas` por conveniência, ao invés de uma variável por premissa. - -```lean -structure Premissas : Prop where - -- cada pessoa veste algum vestido - hA : Aa ∨ Ab ∨ Ap - hM : Ma ∨ Mb ∨ Mp - hC : Ca ∨ Cb ∨ Cp - - -- cada vestido é de alguma pessoa - ha : Ma ∨ Aa ∨ Ca - hb : Ab ∨ Mb ∨ Cb - hp : Ap ∨ Mp ∨ Cp - - -- uma pessoa veste apenas um vestido - hA1 : (Aa → ¬ Ab ∧ ¬ Ap) ∧ (Ab → ¬ Aa ∧ ¬ Ap) ∧ (Ap → ¬ Aa ∧ ¬ Ab) - hM1 : (Ma → ¬ Mb ∧ ¬ Mp) ∧ (Mb → ¬ Ma ∧ ¬ Mp) ∧ (Mp → ¬ Ma ∧ ¬ Mb) - hC1 : (Ca → ¬ Cb ∧ ¬ Cp) ∧ (Cb → ¬ Ca ∧ ¬ Cp) ∧ (Cp → ¬ Ca ∧ ¬ Cb) - - -- cada vestido é de apenas uma pessoa - ha1 : (Ma → ¬ Aa ∧ ¬ Ca) ∧ (Ca → ¬ Aa ∧ ¬ Ma) ∧ (Aa → ¬ Ma ∧ ¬ Ca) - hb1 : (Mb → ¬ Ab ∧ ¬ Cb) ∧ (Cb → ¬ Ab ∧ ¬ Mb) ∧ (Ab → ¬ Mb ∧ ¬ Cb) - hp1 : (Mp → ¬ Ap ∧ ¬ Cp) ∧ (Cp → ¬ Ap ∧ ¬ Mp) ∧ (Ap → ¬ Mp ∧ ¬ Cp) - - -- da resposta 1 - h1 : Aa → Ab - h2 : Ca → ¬ Ab - - -- da resposta 2 - h3 : ¬ Ab - - -- da resposta 3 - h4 : Ap → Cb - h5 : Cp → ¬ Cb -``` - -Podemos então enunciar o problema na forma do teorema abaixo. +%%% +tag := "pl-lean" +%%% ```lean -theorem vestidos (h : Premissas Aa Ab Ap Ma Mb Mp Ca Cb Cp) - : Ap ∧ Cb ∧ Ma := sorry +namespace PL ``` -Consultar o tipo deste teorema com `#check vestidos` nos revela que ele tem o formato de uma implicação, que pode ser lido como `Γ ⊢ α` Do conjunto `Γ` de premissas em `Premissas` posso *derivar* `Ap ∧ Cb ∧ Ma`. Em Lean podemos construir a prova de `α` a partir da aplicação de regras de dedução a partir das fórmulas de `Γ`. +Os conectivos lógicos `∧`, `∨`, `→`, `↔` e `¬` estão disponíveis diretamente no Lean, de modo que uma fórmula proposicional pode ser representada como uma proposição em Lean. Isso nos fornece uma ponte conveniente entre a semântica da linguagem natural e o raciocínio formal. Podemos traduzir o conteúdo semântico de uma sentença para uma proposição em Lean e, em seguida, usar Lean para verificar se uma conclusão decorre de um conjunto de hipóteses. -Chamamos "sistema dedutivo" um conjunto das regras de dedução. Existem vários sistemas dedutivos. A formalização de Prop em Lean corresponde a implementação do sistema chamado *dedução natural* definido por Gerhard Gentzen em 1930s. +Chamamos "sistema dedutivo" um conjunto das regras de dedução. Existem vários sistemas dedutivos. A formalização de Prop em Lean corresponde a implementação do sistema chamado *dedução natural* definido por Gerhard Gentzen em 1930s. Usando as regras de dedução natural, podemos provar que uma fórmula `α` pode ser derivada a partir de um conjunto de fórmulas `Γ`, dizemos que `Γ ⊢ α`. Dizemos que `⊢ α` quando a fórmula `α` é válida, uma tautologia. -Neste sistema dedutivo, cada conectivo vem com dois tipos de regra: as de *introdução*, que dizem como construir uma prova cuja conclusão usa o conectivo, e as de *eliminação*, que dizem como usar uma prova cuja hipótese o usa. +Neste sistema dedutivo, cada conectivo vem com dois tipos de regra. As de *introdução*, que dizem como construir uma prova cuja conclusão usa o conectivo, e as de *eliminação*, que dizem como usar uma prova cuja hipótese o usa. ```lean variable {P Q R : Prop} @@ -323,12 +252,29 @@ end ``` :::: -::::exercise (rating := 2) (name := "and-comm") + +::::exercise (rating := 2) (name := "implication-as-disj") +Complete a prova abaixo. Note que esta prova precisa do fragmento clássico, tente usar {tactic}`by_cases`. + +```lean +example (P Q : Prop) : (P → Q) → ¬ P ∨ Q := by + solution! + intro h + by_cases hP : P + · right + exact h hP + · left + exact hP +``` +:::: + + +::::exercise (rating := 1) (name := "and-comm") Prove que a conjunção é comutativa. ```lean example (P Q : Prop) : P ∧ Q ↔ Q ∧ P := by - solution!( + solution! constructor · intro h obtain ⟨h1, h2⟩ := h @@ -338,7 +284,7 @@ example (P Q : Prop) : P ∧ Q ↔ Q ∧ P := by · intro h constructor · exact h.2 - · exact h.1) + · exact h.1 ``` :::: @@ -347,11 +293,11 @@ Complete a prova abaixo. ```lean example (P Q R : Prop) (h : P → Q) (h2 : Q → R) : P → R := by - solution!( + solution! intro hp apply h2 apply h - exact hp) + exact hp ``` :::: @@ -362,10 +308,10 @@ Em algumas provas, podemos precisar expandir uma definição antes de qualquer o def E (x y : Nat) : Prop := x = y example (x : Nat) : E x 1 → x ≠ 2 := by - solution!( + solution! intro h unfold E at h - linarith) + linarith ``` :::: @@ -374,28 +320,91 @@ Na prova abaixo, o antecedente da implicação precisa ser transformado em hipó ```lean example (x y : Nat) : E x 0 ∧ E y 0 → x = y := by - solution!( + solution! intro h unfold E at h obtain ⟨h1, h2⟩ := h rewrite [h1,h2] - rfl) + rfl ``` :::: ::::exercise (rating := 2) (name := "dresses") -Complete a prova do teorema, provando que o problema dos vestidos tem a solução onde Ana veste preto, Cláudia veste branco e Maria veste azul. +Três irmãs — Ana, Maria e Cláudia — foram a uma festa com vestidos de cores diferentes. Uma vestiu azul, a outra branco, e a terceira, preto. Chegando à festa, o anfitrião perguntou quem era cada uma delas. + +- A de azul respondeu: "Ana é a que está de branco"; +- A de branco disse: "Eu sou Maria"; +- A de preto respondeu: "Cláudia é quem está de branco". + +O anfitrião foi capaz de identificar cada irmã considerando que: + +- Ana sempre diz a verdade; +- Maria às vezes diz a verdade; +- Cláudia nunca diz a verdade. + +Para começar, vamos introduzir variáveis do tipo `Prop`, cada uma delas representado uma proposição. São 3 pessoas e 3 cores. Vamos representar "Ana veste azul" por `Aa` e assim por diante. + +```lean +namespace Dresses + +variable (Aa Ab Ap Ma Mb Mp Ca Cb Cp : Prop) +``` + +A ideia é que as condições do problema sejam traduzidas em fórmulas proposicionais. Por exemplo, podemos formalizar a sentença "Ana veste azul, branco ou preto" como {lean}`Aa ∨ Ab ∨ Ap`. Note que a fórmula não foi obtida diretamente a partir da construção linguística original, uma oração coordenando seus constituintes no predicado. Intuitivamente, a sentença foi antes interpretada como três orações coordenadas, "Ana veste azul ou Ana veste branco ou Ana veste preto". + +A formalização completa do problema deve levar em consideração não apenas o que foi dito explicitamente mas algumas condições implicitamente assumidas. Primeiro que cada irmã veste uma das cores. + +```lean +variable (hA : Aa ∨ Ab ∨ Ap) +variable (hM : Ma ∨ Mb ∨ Mp) +variable (hC : Ca ∨ Cb ∨ Cp) +``` + +Em seguida, que cada vestido é de alguma das irmãs. + +```lean +variable (ha : Ma ∨ Aa ∨ Ca) +variable (hb : Ab ∨ Mb ∨ Cb) +variable (hp : Ap ∨ Mp ∨ Cp) +``` + +Também precisaremos formalizar que uma irmã veste apenas um vestido e que um vestido é vestido por apenas uma irmã. + +```lean +variable (hA1 : (Aa → ¬ Ab ∧ ¬ Ap) ∧ (Ab → ¬ Aa ∧ ¬ Ap) ∧ (Ap → ¬ Aa ∧ ¬ Ab)) +variable (hM1 : (Ma → ¬ Mb ∧ ¬ Mp) ∧ (Mb → ¬ Ma ∧ ¬ Mp) ∧ (Mp → ¬ Ma ∧ ¬ Mb)) +variable (hC1 : (Ca → ¬ Cb ∧ ¬ Cp) ∧ (Cb → ¬ Ca ∧ ¬ Cp) ∧ (Cp → ¬ Ca ∧ ¬ Cb)) + +variable (ha1 : (Ma → ¬ Aa ∧ ¬ Ca) ∧ (Ca → ¬ Aa ∧ ¬ Ma) ∧ (Aa → ¬ Ma ∧ ¬ Ca)) +variable (hb1 : (Mb → ¬ Ab ∧ ¬ Cb) ∧ (Cb → ¬ Ab ∧ ¬ Mb) ∧ (Ab → ¬ Mb ∧ ¬ Cb)) +variable (hp1 : (Mp → ¬ Ap ∧ ¬ Cp) ∧ (Cp → ¬ Ap ∧ ¬ Mp) ∧ (Ap → ¬ Mp ∧ ¬ Cp)) +``` + +Finalmente, a partir das perguntas feitas para as irmãs, podemos extrair as seguintes proposições. Da primeira pergunta, extraímos `h1` e `h2`. Na segunda pergunta extraímos `h3` e da terceira pergunta, `h4` e `h5`. O leitor pode conferir como estas proposições foram extraídas considerando cada possível irmã respondendo a cada pergunta. + +```lean +variable (h1 : Aa → Ab) +variable (h2 : Ca → ¬ Ab) + +variable (h3 : ¬ Ab) + +variable (h4 : Ap → Cb) +variable (h5 : Cp → ¬ Cb) +``` + +Complete a prova do teorema, provando que o problema dos vestidos tem a solução onde Ana veste preto, Cláudia veste branco e Maria veste azul. A declaração `include ... in` irá incluir todas as variáveis declaradas anteriormente como parâmetros para o teorema seguinte. ```lean -theorem vestidos₁ (h : Premissas Aa Ab Ap Ma Mb Mp Ca Cb Cp) - : Ap ∧ Cb ∧ Ma := by - obtain - ⟨hA, hM, hC, ha, hb, hp, hA1, hM1, - hC1, ha1, hb1, hp1, h1, h2, h3, h4, h5⟩ := h +include + hA hM hC + ha hb hp + hA1 hM1 hC1 + ha1 hb1 hp1 + h1 h2 h3 h4 h5 in + +theorem vestidos : Ap ∧ Cb ∧ Ma := by - -- Ana não está de azul: se estivesse, por `h1` ela estaria de branco, mas Ana - -- não está de branco por `h3`. have hnAa : ¬ Aa := by solution! intro hAa @@ -405,28 +414,29 @@ theorem vestidos₁ (h : Premissas Aa Ab Ap Ma Mb Mp Ca Cb Cp) cases hA with | inl hAa => exact absurd hAa hnAa | inr hx => - cases hx with - | inl hAb => exact absurd hAb h3 - | inr hAp => exact hAp + solution! + cases hx with + | inl hAb => exact absurd hAb h3 + | inr hAp => exact hAp - have hCb : Cb := solution!( - h4 hAp - ) + have hCb : Cb := by + solution! + exact h4 hAp - have hnCa : ¬ Ca := solution!( - (hC1.2.1 hCb).1 - ) + have hnCa : ¬ Ca := by + solution! + exact (hC1.2.1 hCb).1 have hMa : Ma := by - rcases ha with hMa | hAa | hCa - · solution! - exact hMa - · solution! - exact absurd hAa hnAa - · solution! - exact absurd hCa hnCa + solution! + rcases ha with hMa | hAa | hCa + · exact hMa + · exact absurd hAa hnAa + · exact absurd hCa hnCa exact ⟨hAp, hCb, hMa⟩ + +end Dresses ``` :::: @@ -435,6 +445,9 @@ end PL ``` # As regras dos quantificadores em Lean +%%% +tag := "quantificadores-lean" +%%% O mesmo tipo `Prop` em Lean não está limitado ao raciocínio proposicional. Também podemos representar lógica de primeira ordem em `Prop`. Como já falamos, o Lean se baseia em na teoria dos tipos, na qual se assume que cada variável pertence a algum tipo. Você pode pensar em um tipo como um "universo" ou um "domínio de discurso", no sentido da lógica de primeira ordem. Com a diferença importante de que em lógica de primeira ordem, entedemos o domínio da interpretação com um conjunto não vazio, e um tipo em Lean não necessariamente precisa ser _habitado_. @@ -498,12 +511,12 @@ Prove o exemplo abaixo e reflita sobre porque não podemos substituir `→` por ```lean example {U : Type} (R : U → U → Prop) : - (∃ y, ∀ x, R x y) → (∀ x, ∃ y, R x y) := - solution!(by + (∃ y, ∀ x, R x y) → (∀ x, ∃ y, R x y) := by + solution! intro h obtain ⟨d, hd⟩ := h intro x - exact ⟨d, hd x⟩) + exact ⟨d, hd x⟩ ``` :::: @@ -518,7 +531,14 @@ example : ∃ n : Nat, n + n = 10 := by ``` :::: -# Prova por indução +```lean +end FOL +``` + +# Provas por Indução +%%% +tag := "induction" +%%% Outra tática de prova que podemos precisar é a {tactic}`induction`. Ela prova algo para todo valor de um tipo indutivo, e não para um valor de cada vez. @@ -532,10 +552,27 @@ example (n : Nat) : n + 0 = n := by linarith ``` -Ao longo do texto, outras táticas poderão ser usadas como: {tactic}`decide`, {tactic}`omega`, {tactic}`simp` e {tactic}`funext`, discutiremos quando forem necessárias. +Ao longo do texto, outras táticas poderão ser usadas como: {tactic}`decide`, {tactic}`omega`, +{tactic}`simp` e {tactic}`funext`, discutiremos quando forem necessárias. + + +# Extensionalidade de Funções +%%% +tag := "funext" +%%% +Uma função admite duas leituras. Na leitura extensional, a função é uma tabela: o conjunto de pares entrada e saída. Uma conversão de Celsius para Fahrenheit é a tabela `[(0, 32), (100, 212),...]`. Na leitura intensional, a função indica como a saída é obtida a partir da entrada `λ x ↦ x * 9 / 5 + 32`. Uma receita que produz a tabela sem precisar listá-la. Em Lean, `def` escreve sempre a versão intensional, mas duas instruções diferentes podem ser a mesma função, no sentido extensional, se produzem a mesma tabela. É isso que `funext` verifica: duas funções são iguais quando concordam em todo ponto do domínio. + +```lean +def double₁ (x : Nat) := 2 * x +def double₂ (x : Nat) := x + x + +example : double₁ = double₂ := by + funext n + rw [double₁, double₂] + exact (Nat.two_mul n) +``` ```lean -end FOL end Proof ``` diff --git a/DEVIATIONS.md b/DEVIATIONS.md index e541ba7..382636b 100644 --- a/DEVIATIONS.md +++ b/DEVIATIONS.md @@ -1,75 +1,73 @@ # How CSwL uses CSwFP -`CSwL` is not a section-by-section translation of CSwFP. It reorders the material so that the presentation is natural in Lean, under one hard constraint: +`CSwL` is not a section-by-section translation of CSwFP. It reorders the material so that the +presentation is natural in Lean, under one hard constraint: > **Nothing is used before it is presented.** -Names — of files, of chapter and section tags, of exercises — are English -mnemonics, never numbers. The prose is in Portuguese; the identifiers are not, -and they carry no number because material moves between chapters and a number -would be wrong the moment it did. - -The prose never explains the book by contrast with anything else. A paragraph -saying why this text does something differently — from the source it adapts, -from a library, from an alternative it considered — is about the book, not -about its subject, and the reader has no use for it. Those reasons belong in -this document. The rule is not about naming CSwFP: a passage can break it -without mentioning any source at all, and two did. - -The line to hold is not "never mention an alternative". It is: - -- a **technical consequence in Lean** is content, because the reader will meet - it. "A constructor holding a `List Form` makes the type a nested inductive, - and a nested inductive has no `induction` tactic" states a fact about Lean - that the chapter then depends on; -- an **editorial preference** is meta. "We preferred to keep `Game` as it is", - "the columns could have been modelled like the rows, but we chose the usual - convention" — these report what the authors decided, which is this - document's subject and not the book's. - -The test: would the sentence still be worth writing if this book had no source -and no alternatives? A fact about Lean survives that; a preference does not. - -A chapter that translates keeps the original's structure — its section -boundaries, its order, its sentence boundaries and its punctuation. Subheadings -that the source does not have are not added, and headings the source has are not -merged, unless something in this document says so and says why. - -Most chapters, though, are mixed: part translated, part written here because -Lean makes something sayable that the source could not say. In a mixed chapter -the rule applies section by section. A translated section follows the original; -a new section has a structure of its own, chosen for what it teaches. Which -sections are which is recorded per chapter below — a reader of this document +Names of files, chapter, section tags, and exercises are English mnemonics. The prose is in +Portuguese; the identifiers inside Lean blocks are in English. + +The prose never explains the CLwL material by contrast with CSwFP. Those reasons belong in this +document. The line to hold is not "never mention an alternative". It is: + +- a **technical consequence in Lean** is content, because the reader will meet it. "A constructor + holding a `List Form` makes the type a nested inductive, and a nested inductive has no `induction` + tactic" states a fact about Lean that the chapter then depends on; +- an **editorial preference** is meta. "We preferred to keep `Game` as it is", "the columns could + have been modelled like the rows, but we chose the usual convention" — these report what the + authors decided, which is this document's subject and not the book's. + +The test: would the sentence still be worth writing if this book had no source and no alternatives? +A fact about Lean survives that; a preference does not. + +A chapter that translates keeps the original's structure — its section boundaries, its order, its +sentence boundaries and its punctuation. Subheadings that the source does not have are not added, +and headings the source has are not merged, unless something in this document says so and says why. + +Most chapters, though, are mixed: part translated, part written here because Lean makes something +sayable that the source could not say. In a mixed chapter the rule applies section by section. A +translated section follows the original; a new section has a structure of its own, chosen for what +it teaches. Which sections are which is recorded per chapter below — a reader of this document should never have to guess whether a heading came from the source or from us. -Any divergence from this plan is stated when it is made. A silent divergence is -worse than a wrong one: a wrong decision can be argued with, an unrecorded one -cannot. +Any divergence from this plan is stated when it is made. A silent divergence is worse than a wrong +one: a wrong decision can be argued with, an unrecorded one cannot. "Presented" has one deliberate loosening and one exception. -The loosening: Lean's own basic types may be introduced *where they are first -needed*, in a sentence or two, with a citation of the Lean Language Reference -(`{citep Bib.LLR}[]`), instead of being pushed back into `IntroL.lean`. `Fin` -in `SeaBattle.lean` is the case. `IntroL.lean` presents what the -book builds on repeatedly; a type used in one chapter is better introduced -there, next to its use. The loosening covers types the language already gives -us — never a construct this book defines, and never one that needs more than a -short paragraph. +The loosening: Lean's own basic types may be introduced *where they are first needed*, in a sentence +or two, with a citation of the Lean Language Reference (`{citep Bib.LLR}[]`), instead of being +pushed back into `IntroL.lean`. `Fin` in `SeaBattle.lean` is the case. `IntroL.lean` presents what +the book builds on repeatedly; a type used in one chapter is better introduced there, next to its +use. The loosening covers types the language already gives us — never a construct this book defines, +and never one that needs more than a short paragraph. -The exception: `IntroCS.lean`, described in that chapter's section below. -Everywhere else, a construct in a code block is one an earlier chapter has -presented. +The exception: `IntroCS.lean`, described in that chapter's section below. Everywhere else, a +construct in a code block is one an earlier chapter has presented. -This document is the migration plan: which CSwFP sections each `CSwL` chapter consumes, in which order, and what each chapter presupposes. The order below is the book's order; the dependency columns are what justify it. Everything through CSwFP/6 is settled — where a section records a decision, that decision is made, not proposed. What remains is execution, tracked in [GitHub issues](https://github.com/cslib-community/CSwL/issues). Exercise-level correspondence with CSwFP is in `PROVENANCE.md`. +This document is the migration plan: which CSwFP sections each `CSwL` chapter consumes, in which +order, and what each chapter presupposes. The order below is the book's order; the dependency +columns are what justify it. Everything through CSwFP/6 is settled — where a section records a +decision, that decision is made, not proposed. What remains is execution, tracked in [GitHub +issues](https://github.com/cslib-community/CSwL/issues). Exercise-level correspondence with CSwFP is +in `PROVENANCE.md`. The current state of CSwL need major review to fulfill all decisions from this document. ## Why the order changes at all -CSwFP runs 1, 2 (sets, relations, lambda, types), 3 (Haskell), 4 (syntax), 5 (semantics), 6 (model checking). That works in Haskell because CSwFP/2 is *pure prose*: nothing in it is mechanised, so it owes nothing to the chapter that introduces the language. In Lean the same material is mechanisable, which inverts the dependency and forces the reordering below. +CSwFP runs 1, 2 (sets, relations, lambda, types), 3 (Haskell), 4 (syntax), 5 (semantics), 6 (model +checking). That works in Haskell because CSwFP/2 is *pure prose*: nothing in it is mechanised, so it +owes nothing to the chapter that introduces the language. In Lean the same material is mechanisable, +which inverts the dependency and forces the reordering below. -CSwFP/2 also cannot be split cleanly, because its sections form a definitional chain: 2.3 opens "Functions are relations with the following special property", so it needs 2.2, which needs 2.1; 2.4 opens "We already talked about functions informally", so it needs 2.3; 2.5 builds on the terms of 2.4. The chapter is therefore *dissolved* rather than moved — 2.3, 2.4 and 2.5 into `IntroL.lean`, 2.1 and 2.2 into `Sets.lean`, and the natural-language examples scattered through 2.4 and 2.5 into `English.lean`. +CSwFP/2 also cannot be split cleanly, because its sections form a definitional chain: 2.3 opens +"Functions are relations with the following special property", so it needs 2.2, which needs 2.1; 2.4 +opens "We already talked about functions informally", so it needs 2.3; 2.5 builds on the terms of +2.4. The chapter is therefore *dissolved* rather than moved — 2.3, 2.4 and 2.5 into `IntroL.lean`, +2.1 and 2.2 into `Sets.lean`, and the natural-language examples scattered through 2.4 and 2.5 into +`English.lean`. ## Chapter order @@ -78,13 +76,13 @@ CSwFP/2 also cannot be split cleanly, because its sections form a definitional c | # | `CSwL` chapter | CSwFP sections | Requires | Best after | |---|----------------------|---------------------------------------------------|----------|------------| | 1 | `IntroCS.lean` | 1.1–1.6 | — | — | -| 2 | `IntroL.lean` | 3.1–3.10, 3.13; 2.3, 2.4, 2.5 | 1 | — | +| 2 | `IntroL.lean` | 3.1–3.10, 3.13; 2.3, 2.4 | 1 | — | | 3 | `Logic.lean` | 4.4, 4.5, 4.6, 4.7, 5.2, 5.3, 5.5 | 2 | — | | 4 | `Sets.lean` | 2.1, 2.2 | 2, 3 | — | | 5 | `SeaBattle.lean` | 4.1, 5.1 | 2, 3 | 4 | | 6 | `Morphology.lean` | 3.11, 3.14 | 2 | 3 | | 7 | `InfEngine.lean` | 4.3, 5.7 | 2, 3 | 4 | -| 8 | `English.lean` | 4.2, 5.6 | 2, 3, 4 | — | +| 8 | `English.lean` | 4.2, 5.6; 2.5 | 2, 3, 4 | — | | 9 | `ModelChecking.lean` | 6.1–6.5 | 2, 3, 8 | 4 | `Sets.lean` requires `Logic.lean` because its exercises are proofs, and the tactics they need — quantifiers, `cases`, `by_contra` — arrive there. `SeaBattle.lean` requires `Logic.lean` for the same reason: it proves theorems about `WellFormed` by induction on an inductive predicate, which no earlier chapter has the machinery for. `English.lean` requires `Sets.lean` because its categorial section interprets a transitive verb over `Sets.Entity` and `Sets.likesR`, the domain and relation that chapter introduces. @@ -162,27 +160,39 @@ The exception is this chapter only, and only for code that presents nothing. It *Migration*: this chapter is to be rewritten as a faithful translation of CSwFP/1, section by section; the current file departs from the original's presentation and organisation. The code stays for now — it is illustrative, and the formalisations in it are slight — but it is not what the chapter is for, and the rewrite decides how much of it survives. Two things are needed either way: the framing sentence, which is not in the prose yet, and the replacement of the chapter's numeric cross-references. -### 2. `IntroL.lean` — CSwFP/3, plus 2.3, 2.4, 2.5 +### 2. `IntroL.lean` — CSwFP/3, plus 2.3, 2.4 Presents Lean as a functional programming language. Translated aggressively, adapting to Lean style and primitives. -scope and order of presentations: terms, types, lambda and function definition, function composition, polymorphism, inductive types, `List`, `Nat`, recursion, `structure`, type classes, list processing (`map`, `foldl`, `foldr`, `filter`), strings, chars and slices. Only what the rest of the book actually uses. +Scope and order of presentation: terms and types, functions, expressions (`let`, `if-then-else` as terms), `structure`, inductive types, recursion, `List` and polymorphism, `Option`, list processing (`map`, `filter`, `foldl`, `all`, `any`), function composition, type classes, strings, and a closing section on Lean and the lambda calculus. Only what the rest of the book actually uses. - 3.1 and 3.2 merge into a single section about Lean. - 3.12 (Identifiers in Haskell) and 3.15 (Further Reading) are omitted. - 3.13 contributes inductive types and pattern matching; its `Subject`/ `Predicate` example is dropped here, because a fragment of natural language introduced this early collides with the fragments presented later. It is absorbed into `English.lean`. -**2.3, 2.4 and 2.5 live here.** CSwFP presents lambda calculus and types before the programming language, as preliminaries justifying a language the reader has not seen. With Lean already on screen the direction inverts: `#check fun x => x * x` exhibits the lambda abstraction of 2.4, `#check (Nat → Nat)` exhibits the type BNF `τ ::= b | (τ → τ)` of 2.5, and `#reduce` exhibits β-reduction happening. The chapter becomes the theory of what the reader has just written. +**2.3 and 2.4 live here; 2.5 does not.** CSwFP presents lambda calculus and types before the programming language, as preliminaries justifying a language the reader has not seen. With Lean already on screen the direction inverts: `#check fun x => x * x` exhibits the lambda abstraction of 2.4, and the closing section gives the term BNF of the lambda calculus with `Lam` as the inductive that mirrors it. The chapter becomes the theory of what the reader has just written. + +2.5 — the type BNF `τ ::= b | (τ → τ)` and the three typing rules — was here and is now `English.lean`'s, with the rest of 2.5 that chapter already carries. See the note in its section below. Two consequences of moving 2.3 here: - CSwFP defines a *function as a special kind of relation* (2.3), so 2.3 depends on 2.2. In Lean `A → B` is primitive, so the definition is not needed to introduce functions. When relations arrive in `Sets.lean`, "a function is a functional relation" stops being a definition and becomes a statement to prove — new text that CSwFP does not have. -- The linguistic examples of 2.4 (lambda abstraction for word formation) and 2.5 are not presented here: word formation collides with `Morphology.lean`, and the natural-language semantics example is premature. They are in `English.lean`. -- One example had to change in the move. The demonstration of the typing rules read `opaque happy : Entity → Prop`, and `Entity` is declared in `Sets.lean`, which now comes after this chapter. It is `opaque restful : Day → Prop`, over the inductive this chapter declares itself. +- The linguistic example of 2.4 — lambda abstraction for word formation — is not presented here, because word formation collides with `Morphology.lean`. It is in `English.lean`. +- The demonstration of the typing rules went with 2.5. It had already been adapted once — it read `opaque happy : Entity → Prop`, and `Entity` is declared in `Sets.lean`, which now comes after this chapter, so it became `opaque restful : Day → Prop` over the inductive this chapter declares itself. Whichever entity the rules are demonstrated over in `English.lean`, that constraint no longer applies there. -**`instance` is presented here.** The chapter's "Classes de tipos" section shows type classes only from the *use* side — the `[BEq α]` in a signature, and the difference between `BEq` and `DecidableEq`. But instances are declared from `Logic.lean` onwards: `PL.lean` gives `ToString Form`, `FOL.lean` three more, and `English.lean` fifteen, all of them `ToString`. Declaring an instance is a small step from the section already there, and it is the last piece of type classes the book actually needs — no chapter declares a `class` of its own. +**`instance` is presented here.** The chapter's "Classes de tipos" section shows type classes only from the *use* side — the `[BEq α]` in a signature, and the difference between `BEq` and `DecidableEq`. But instances are declared from `Logic.lean` onwards: `FOL.lean` gives three, `Repr` for `Variable`, `Term` and `Formula`, and `English.lean` fifteen, all of them `ToString`. Declaring an instance is a small step from the section already there, and it is the last piece of type classes the book actually needs — no chapter declares a `class` of its own. -**`Prop` is presented here, minimally.** Not by choice: inductive types bring `deriving DecidableEq`, `decide` and `#check 1 = 1`, all of which display `Prop`. `SeaBattle.lean` already derives `DecidableEq` on its enumerations and `Answer` in its first code block, two chapters before any logic. The student sees `Prop` whether or not it is introduced. So the chapter presents proposition-as-type, proof-as-term, and `rfl`, `intro`, `exact`, `decide` — and leaves natural deduction and quantifiers to `Logic.lean`. Without this, `IntroL.lean`, `Morphology.lean` and `SeaBattle.lean` are `Bool` and `#eval` throughout, which is the original book with Lean as a costume. +**`Prop` is no longer presented here.** It once was, minimally — proposition-as-type, proof-as-term, and `rfl`, `intro`, `exact`, `decide` — and the argument was one of necessity rather than preference: inductive types bring `deriving DecidableEq` and `decide`, which display `Prop`, and `SeaBattle.lean` derives `DecidableEq` in its first code block. The student would see `Prop` whether or not it was introduced. + +That argument fell with the chapter reordering. `Proof.lean` is now part of `Logic.lean`, the third chapter, and every chapter that displays `Prop` — `Sets.lean`, `SeaBattle.lean`, `Morphology.lean` — comes after it. Nothing meets `Prop` unintroduced any more, so the minimal presentation had no work left to do and went out with the revision. The chapter is left deliberately pre-proof, as the `Logic.lean` section below records. + +**Three sections left the chapter in the revision of 2026-09-14.** It had grown to fifteen sections and roughly a thousand lines, and what went out was the material that theorised rather than taught the language: + +- *As duas leituras de uma função* — the extensional/intensional distinction, with `funext`. Moved to `Proof.lean`, as the section "Extensionalidade de Funções": `funext` is a tactic, and a chapter that presents no tactics is the wrong home for it. +- *Tipos na gramática e na computação* — the type BNF and the three typing rules, which is 2.5, moved to `English.lean` as recorded above. +- *Tipos como disciplina* — a closing page arguing that "this combination of words is not well formed" and "this program does not typecheck" become the same sentence. It is the book's thesis, not this chapter's content, and it reads better where a grammar is actually being typed. + +Only the third was dropped outright; the other two moved. Gone with them: the `leanOutput` blocks throughout, and `#reduce`. The surviving *Lean e o Cálculo Lambda* keeps the term BNF, the `Lam` inductive, β-reduction as what `#eval` does, and variable capture — the part the later chapters draw on. It also keeps `λx ↦ x x`, but as the observation that its reduction loops and that Lean rejects it, without the step-by-step derivation of `σ = σ → τ` that the removed section gave. Recorded because a reader tracing CSwFP/2.4 and 2.5 through this book would otherwise look for these sections and find no note of where they went. 2.6 (Functional Programming) exists in CSwFP to motivate its chapter 3 from its chapter 2 — "functional programming languages actually are lambda calculi". With the order inverted, that bridge is not needed and the section is absorbed. @@ -357,7 +367,7 @@ Three divergences. The least fixed point is computed with a fuel bound rather th ### 8. `English.lean` — CSwFP/4.2, 5.6 -The fragment of English (4.2) and its semantics (5.6), plus the natural-language fragments that CSwFP scatters earlier and that `CSwL` deliberately does not present in place: the `likes` example of 2.4, the `S → NP VP` of 2.5, and the `Subject`/`Predicate` example of 3.13. CSwFP introduces a slightly larger grammar in 4.2 and then, in 5.6, sketches the semantics of an initial vocabulary largely disconnected from it. Gathering these fragments in one place is the point of this chapter. +The fragment of English (4.2) and its semantics (5.6), plus the natural-language fragments that CSwFP scatters earlier and that `CSwL` deliberately does not present in place: the `likes` example of 2.4, the `Subject`/`Predicate` example of 3.13, and all of 2.5 — the `S → NP VP` this chapter always carried, and now the type BNF `τ ::= b | (τ → τ)` and the three typing rules as well. CSwFP introduces a slightly larger grammar in 4.2 and then, in 5.6, sketches the semantics of an initial vocabulary largely disconnected from it. Gathering these fragments in one place is the point of this chapter. **What this chapter must deliver to `ModelChecking.lean`.** Gathering the fragments is the editorial goal, but the chapter also has a hard obligation: CSwFP/6 translates the 4.2 grammar category by category, so every category it destructures has to exist by the end of this chapter. `MCWPL.hs` defines one translation function per category — `lfSent`, `lfNP`, `lfDET`, `lfCN`, `lfRCN`, `lfVP`, `lfTV`, `lfDV` — so the required inventory is: @@ -365,6 +375,8 @@ The fragment of English (4.2) and its semantics (5.6), plus the natural-language **Semantic types are named `e` and `t`, and that is not cosmetic.** The categorial section that arrived from CSwFP/2.5 originally wrote the semantic types as `NP`, `S`, `VP` and `TV` — the category names. In this chapter those names are already taken, by the `inductive`s that *are* the fragment's syntactic categories: two meanings for one name in one namespace, which does not compile and would not be worth compiling. The types now carry Montague's letters, `e` for entities and `t` for truth values, which the prose was already naming as the conventional choice; the categories keep `NP`/`VP` where they belong, in the prose. So "the VP has type `e → t`" now says two different things with two different notations, which is exactly the distinction the section is about. +This is where the rest of 2.5 lands. The type BNF `τ ::= b | (τ → τ)` says that `e` and `t` are the two basic types and that everything else is built from them by `→`, and the three typing rules — a variable has the type it is declared with, `(λx ↦ E) : δ → τ` when `x : δ` and `E : τ`, `(E₁ E₂) : τ` when `E₁ : δ → τ` and `E₂ : δ` — are what licenses the composition the section then performs. They were in `IntroL.lean`, demonstrated over a `Day → Prop` predicate for want of anything better so early in the book; here they are demonstrated over the fragment's own types, which is what 2.5 was about in CSwFP. In Lean `t` is `Prop`. + `INF` is part of the 4.2 grammar but has no translation in CSwFP/6; it is not required by `ModelChecking.lean`. ### 9. `ModelChecking.lean` — CSwFP/6