{-# OPTIONS --allow-unsolved-metas #-} {- AGDA IN PADOVA 2024 Exercise sheet 1 ┌─ SHORTCUTS ───────────────────────┐ ┌─ BACKSLASH CHARACTERS ─┐ │ C-c C-l = load file │ │ \bN = ℕ │ │ C-c C-c = case split │ │ \alpha = α │ │ C-c C-SPC = check hole │ │ \to = → │ │ C-c C-, = see context │ │ \cdot = · │ │ C-c C-. = see context and goal │ │ \:: = ∷ │ │ C-c C-d = display type │ │ \== = ≡ │ │ C-c C-v = evaluate expression │ └────────────────────────┘ │ C-z = enable Vi keybindings │ Use M-x describe-char to │ C-x C-+ = increase font size │ lookup input method for └───────────────────────────────────┘ symbol under cursor. You can open this file in an Agdapad session by pressing C-x C-f ("find file") and then entering the path to this file: ~/Padova2024/exercise01.agda. As this file is read-only, you can then save a copy of this file to your personal directory and edit that one: File > Save As... For instance, you can save this file as ~/solutions01.agda. -} -- ──────────────────── -- ────[ BOOLEANS ]──── -- ──────────────────── data Bool : Set where false : Bool true : Bool _&&_ : Bool → Bool → Bool false && b = false true && false = false true && true = true -- EXERCISE: Implement boolean "or". -- "false || true" should evaluate to "true". -- "true || true" should evaluate to "true". _||_ : Bool → Bool → Bool a || b = {!!} -- EXERCISE: Implement a function "is-tautology₁?" which checks whether -- a given input function is constantly true. For instance, if f x = x, -- then "is-tautology₁? f" should evaluate to "false". is-tautology₁? : (Bool → Bool) → Bool is-tautology₁? f = f false && f true -- EXERCISE: Implement a function "is-tautology₂?" which checks whether -- a given input function of two arguments is constantly true. For -- instance, if f x y = true, then "is-tautology₂ f" should evaluate to "true". is-tautology₂ : (Bool → Bool → Bool) → Bool is-tautology₂ f = {!!} -- ─────────────────────────────────────── -- ────[ NATURAL NUMBERS PROGRAMMING ]──── -- ─────────────────────────────────────── data ℕ : Set where zero : ℕ succ : ℕ → ℕ _+_ : ℕ → ℕ → ℕ zero + b = b succ a + b = succ (a + b) -- EXERCISE: Define the function "half" which divides its input by two. -- For instance "half (succ (succ (succ (succ zero))))" should be "succ (succ zero)" -- and "half (succ (succ (succ zero)))" should be "succ zero" (so we round down). half : ℕ → ℕ half zero = zero half (succ zero) = zero half (succ (succ n)) = succ (half n) -- EXERCISE: Define (cut-off) subtraction. For instance "succ zero - succ zero" -- and "succ zero - succ (succ zero)" should both be "zero". _-_ : ℕ → ℕ → ℕ a - b = {!!} -- EXERCISE: Define multiplication and exponentiation. -- EXERCISE: Define a function "max" which returns the maximum of two inputs. -- For instance "max (succ zero) zero" should be "succ zero". -- EXERCISE: Define a function "eq?" which determines whether its two -- input numbers are equal. For instance, "eq? zero zero" should evaluate -- to "true" while "eq? zero (succ zero)" should evaluate to "false". eq? : ℕ → ℕ → Bool eq? a b = {!!} -- EXERCISE: Define a function "≤?" which determines whether its first -- argument is less than or equal to its second. For instance, "≤? -- zero zero" should evaluate to "true" while "≤? (succ zero) zero" -- should evaluate to "false". ≤? : ℕ → ℕ → Bool ≤? a b = {!!} {- -- EXERCISE: Define a function "even?" which determines whether its input is even. -- For instance, "even? zero" and "even? (succ (succ zero))" should both evaluate to "true", -- while "even? (succ zero)" should evaluate to "false". even? : ℕ → Bool even? n = {!!} -} -- EXERCISE: Define a function "odd?" which determines whether its input is odd. -- You may use the "even?" function from before. odd? : ℕ → Bool odd? n = {!!} -- ───────────────── -- ────[ TYPES ]──── -- ───────────────── -- EXERCISE: Describe the following type in simple terms. What are its values? data Weird : Set where foo : Weird → Weird -- This type doesn't have any inhabitants at all, because the only -- constructor, "foo", is not useful. ex : Weird ex = foo (foo (foo (foo (foo {!!})))) {- loop : Weird loop = foo loop -} id : Weird → Weird id x = x -- EXERCISE: Define a manifestly empty type called "Empty". data Empty : Set where -- No constructors, so no ways to construct inhabitants of type "Empty", -- so the type "Empty" does not contain any inhabitants. -- Can you define a function Empty → ℕ? from-empty : Empty → ℕ from-empty x = zero from-empty' : Empty → ℕ from-empty' x = succ (succ zero) from-empty'' : Empty → ℕ from-empty'' () -- Can you define a function in the other direction, so ℕ → Empty? to-empty : ℕ → Empty to-empty x = {!!} -- this hole cannot be filled to-empty' : ℕ → Empty to-empty' zero = {!()!} to-empty' (succ x) = {!()!} unicorn : Empty → Empty unicorn x = x {- To summarize: There is a function of type X → Empty only in the case that X doesn't contain any inhabitants. -} riddle : Weird → Empty riddle (foo x) = riddle x -- EXERCISE: Write a function "Endo" which takes as input a type X and returns -- as output the type of functions X → X. Endo : Set → Set Endo X = X → X Bar : Set → Set Bar X = ℕ Baz : ℕ → Set Baz zero = Bool Baz (succ zero) = ℕ Baz (succ (succ n)) = Bool → ℕ grtz : Baz (succ zero) grtz = succ (succ (succ (succ (succ zero)))) -- ───────────────────────────────────────────── -- ────[ FIRST PROOFS WITH NATURAL NUMBERS ]──── -- ───────────────────────────────────────────── -- The following function determines whether a given number is zero. is-zero? : ℕ → Bool is-zero? zero = true is-zero? (succ n) = false -- For every natural number "n", there should be a type "IsZero n" -- of witnesses that "n" is zero. For instance, the type "IsZero (succ zero)" -- should be empty, because there shouldn't be any witnesses that "succ zero" -- is zero. But the type "IsZero zero" should be inhabited. data IsZero : ℕ → Set where case-zero : IsZero zero -- In contrast, the function IsZero : ℕ → Set does not carry out any (nontrivial) computation. -- Instead, the function IsZero reads a number "n" as input and outputs the type -- of witnesses that "n" is zero. For most inputs, the resulting output types will be empty. -- For instance, the type "IsZero (succ zero)" will be empty. -- For every number "n", the type "IsNonzero n" is the type of witnesses that "n" is nonzero. data IsNonzero : ℕ → Set where case-succ : {n : ℕ} → IsNonzero (succ n) data ⊥ : Set where IsNonzero' : ℕ → Set IsNonzero' n = IsZero n → ⊥ -- logical reading: a number "n" is nonzero if and only if the assumption that it would be zero -- implies a contradiction. -- computational reading: a witness of a number "n" being nonzero is a function which -- reads a witness that "n" is zero as input and outputs a witness of a contradiction. -- Proposition: The number zero is zero. proposition : IsZero zero proposition = case-zero -- Theorem: If a number "n" is zero, then it is zero. -- Logical reading: For every number n, if n is zero, then n is zero. -- Type-theoretic reading: "first-theorem" is a function which reads two inputs, -- namely a number "n" and a witness that "n" is zero, and outputs a witness that "n" is zero. first-theorem : (n : ℕ) → IsZero n → IsZero n first-theorem n p = {!!} -- EXERCISE: Prove that the sum of two numbers, both of which are zero, is zero again. -- Computational reading: "sum-zero" is a function which reads as input -- (1) a natural number "x", -- (2) a natural number "y", -- (3) a witness that "x" is zero, and -- (4) a witness that "y" is zero, -- and produces as output a witness that "x + y" is zero. sum-zero : (x : ℕ) → (y : ℕ) → IsZero x → IsZero y → IsZero (x + y) sum-zero zero zero case-zero case-zero = case-zero -- sum-zero zero zero case-zero case-zero = proposition -- sum-zero zero y case-zero q = q -- EXERCISE: State and prove: The sum of two numbers, the first of which is nonzero, is nonzero. -- Computational reading: "sum-nonzero" is a function which reads as input -- (1) a natural number "x", -- (2) a natural number "y", -- (3) a witness that "x" is nonzero, and -- produces as output a witness that "x + y" is nonzero. sum-nonzero : {x y : ℕ} → IsNonzero x → IsNonzero (x + y) sum-nonzero case-succ = case-succ -- EXERCISE: Prove that the (contradictory) assumption that zero is nonzero implies -- the (also contradictory) statement that succ zero is zero. zero-is-not-nonzero : IsNonzero zero → IsZero (succ zero) zero-is-not-nonzero () -- computational reading: "absurd" is a function which reads as input a type "A" -- and a witness of a contradiction and outputs a value of type "A". -- logical reading: For every A, if contradiction, then A. absurd : {A : Set} → ⊥ → A absurd {A} () if-isnonzero-zero-then-bot : IsNonzero zero → ⊥ if-isnonzero-zero-then-bot () zero-is-not-nonzero' : IsNonzero zero → IsZero (succ zero) zero-is-not-nonzero' p = absurd (if-isnonzero-zero-then-bot p) -- For every number "n", the type "Even n" should be the type of witnesses that "n" is even. data Even : ℕ → Set where base : Even zero step : {n : ℕ} → Even n → Even (succ (succ n)) -- logical reading: For every number n, if n is even, then succ (succ n) is even. -- computational reading: "step" is a function which reads a number "n" and a witness that "n" is even -- as input and outputs a new, freshly-minted witness that "succ (succ n)" is even. four-is-even : Even (succ (succ (succ (succ zero)))) four-is-even = step {succ (succ zero)} (step {zero} base) -- four-is-even = step (step base) even? : ℕ → Bool even? zero = true even? (succ zero) = false even? (succ (succ n)) = even? n !_ : Bool → Bool ! false = true ! true = false even?' : ℕ → Bool even?' zero = true even?' (succ n) = ! (even?' n) data Maybe (A : Set) : Set where nothing : Maybe A just : A → Maybe A -- For every type "A", there is a new type "Maybe A". -- Its inhabitants are: -- (1) "nothing" -- (2) for every value "x" of type "A", we have the inhabitant "just x" _/_ : ℕ → ℕ → Maybe ℕ x / zero = nothing x / succ y = {!!} even?'' : (n : ℕ) → Maybe (Even n) even?'' zero = just base even?'' (succ zero) = nothing even?'' (succ (succ n)) with even?'' n ... | nothing = nothing ... | just p = just (step p) -- EXERCISE: Show that the sum of even numbers is even. lemma-sum-even : {a b : ℕ} → Even a → Even b → Even (a + b) lemma-sum-even base q = q lemma-sum-even (step p) q = step (lemma-sum-even p q) {- lemma-+-zero : (a : ℕ) → (a + zero) ≡ a lemma-+-zero a = ? -} lemma-sum-even' : {a b : ℕ} → Even a → Even b → Even (a + b) lemma-sum-even' p base = {!p!} lemma-sum-even' p (step q) = {!!}