Homework 6
The established rules apply. Do this work on your own.
This homework will test and strengthen your understanding of inductive data types, including Nat and List α, and the use of recursive functions to process and construct values of such types.
The second part will test and develop your knowledge and understanding of formal languages, and propositional logic, (PL) in particular, including the syntax and semantics of PL, and translations between formal statements in PL and corresponding concrete English-language examples.
Part 1: Inductive Types and Recursive Functions
#1: Iterated Function Application
Here are two functions, each of which takes a function, f, as an argument, and an argument, a, to that function, and returns the result of applying f to a one or more times. The first function just applies f to a once. The second function applies f to a twice. Be sure you fully understand these definitions before proceeding.
defapply {apply: {α : Sort u_1} → {a : α} → (α → α) → α → α:α} : (α: Sort u_1α →α: Sort u_1α) →α: Sort u_1α →α: Sort u_1α |α: Sort u_1f,f: α → αa =>a: αff: α → αa defa: αapply_twice {apply_twice: {α : Sort u_1} → {a : α} → (α → α) → α → α:α} : (α: Sort u_1α →α: Sort u_1α) →α: Sort u_1α →α: Sort u_1α |α: Sort u_1f,f: α → αa =>a: αf (f: α → αff: α → αa)a: α
Your job now is to define a function, apply_n, that takes a function, f, a natural number, n, and an argument, a, and that returns the result of applying f to a n times. Define the result of applying f to a zero times as just a. Hint: recursion on n. That is, you will have two cases: where n is 0; and where n is greater than 0, and can thus be written as (1 + n') for some smaller natural number, n'.
-- Answer here defapply_n {apply_n: {α : Type} → (α → α) → α → Nat → αα :α: TypeType} : (Type: Type 1α →α: Typeα) →α: Typeα →α: TypeNat →Nat: Typeα |α: Type,a,a: α0 =>0: Nata |a: αf,f: α → αa, (a: αn' + 1) =>n': Natf (f: α → αapply_napply_n: {α : Type} → (α → α) → α → Nat → αff: α → αaa: αn') -- Test cases: confirm that expectations are correct -- apply Nat.succ to zero four timesn': Natapply_napply_n: {α : Type} → (α → α) → α → Nat → αNat.succNat.succ: Nat → Nat00: Nat4 -- expect 4 -- apply "double" to 2 four times4: Natapply_n (λapply_n: {α : Type} → (α → α) → α → Nat → αn =>n: Nat2*2: Natn)n: Nat22: Nat4 -- expect 32 -- apply "square" to 2 four times4: Natapply_n (λapply_n: {α : Type} → (α → α) → α → Nat → αn =>n: Natn^n: Nat2)2: Nat22: Nat4 -- expect 655364: Nat
A Short Introduction to Lists
The polymorphic data type, List α, is used to represent lists of values of any type, α. The List type builder provides two constructors: one to create and empty list of α values, and one to construct a new non-empty list from a new element (head, of type α) and a one smaller list (tail, of type List α). Here's how the List type builder is defined (simplied just a tad).
namespace cs2120
inductive List: Type → Type
List (α: Type
α : Type: Type 1
Type): Type: Type 1
Type
| nil: {α : Type} → List α
nil : List: Type → Type
List α: Type
α
| cons: {α : Type} → α → List α → List α
cons (h: α
h : α: Type
α) (t: List α
t : List: Type → Type
List α: Type
α) : List: Type → Type
List α: Type
α
end cs2120
Lean defines three useful notations for creating and destructuring lists.
- [] means List.nil
- h::t means cons h t
- [1, 2, 3] means the list, 1::[2,3]
- which means 1::2::[3]
- which means 1::2::3::[]
- which means cons 1 (cons 2 (cons 3 nil))
([] :[]: List NatListList: Type → TypeNat)Nat: Type1::[1: Nat2,2: Nat3]3: Nat[1,1: Nat2,2: Nat3]3: Nat
You can use these notations when pattern matching to analyze arguments. Here we show how this work by defining a function that takes a list and returns either (using a sum type) unit to represent the case where there is no first element, or the value at the head of the list.
deffirst_elt :first_elt: List Nat → Unit ⊕ NatListList: Type → TypeNat →Nat: TypeUnit ⊕Unit: TypeNat | [] =>Nat: TypeSum.inlSum.inl: {α β : Type} → α → α ⊕ βUnit.unit |Unit.unit: Unith::_ =>h: NatSum.inrSum.inr: {α β : Type} → β → α ⊕ βhh: Natfirst_eltfirst_elt: List Nat → Unit ⊕ Nat[] -- expect Sum.inl unit[]: List Natfirst_elt [first_elt: List Nat → Unit ⊕ Nat1,1: Nat2] -- expect 1 (left)2: Nat
#2: List length function
Lists are defined inductively in Lean. A list of values of some type α is either the empty list of α values, denoted [], or an α value followed by a shorter list of α values, denoted h::t, where h (the head of the list) is a single value of type α, and t is a shorter list of α values. The base case is of course the empty list. Define a function called len that takes a list of values of any type, α, and that returns the length of the list.
deflen {len: {α : Type} → List α → Natα :α: TypeType} :Type: Type 1ListList: Type → Typeα →α: TypeNat | [] =>Nat: Type0 |0: Nat::t =>t: List α1 +1: Natlenlen: {α : Type} → List α → Nattt: List α@lenlen: {α : Type} → List α → NatNatNat: Type[] -- expect 0[]: List Natlen [len: {α : Type} → List α → Nat0,0: Nat1,1: Nat2] -- expect 32: Natlen [len: {α : Type} → List α → Nat"I","I": String"Love","Love": String"Logic!"] -- expect 3"Logic!": String
#3: Reduce a List of Bool to a Bool
Define a function that takes a list of Boolean values and that "reduces" it to a single Boolean value, which it returns, where the return value is true if all elements are true and otherwise is false. Call your function reduce_and.
Hint: the answer is the result of applying and to two arguments: (1) the first element, and (2) the result of recursively reducing the rest of the list. You will have to figure out what the return value for the base case of an empty list needs to be for your function to work in all cases.
defreduce_and :reduce_and: List Bool → BoolListList: Type → TypeBool →Bool: TypeBool | [] =>Bool: Typetrue |true: Boolh::h: Boolt =>t: List Boolandand: Bool → Bool → Boolh (h: Boolreduce_andreduce_and: List Bool → Boolt) -- Test casest: List Boolreduce_and [reduce_and: List Bool → Booltrue] -- expect truetrue: Boolreduce_and [reduce_and: List Bool → Boolfalse] -- expect falsefalse: Boolreduce_and [reduce_and: List Bool → Booltrue,true: Booltrue] -- expect truetrue: Boolreduce_and [reduce_and: List Bool → Boolfalse,false: Booltrue] -- expect falsetrue: Bool
#4 Negate a List of Booleans
Define a function, call it (map_not) that takes a list of Boolean values and returns a list of Boolean values, where each entry in the returned list is the negation of the corresonding element in the given list of Booleans. For example, map_not [true, false] should return [false, true].
defmap_not :map_not: List Bool → List BoolListList: Type → TypeBool →Bool: TypeListList: Type → TypeBool | [] =>Bool: Type[] |[]: List Boolh::h: Boolt =>t: List Boolnotnot: Bool → Boolh ::h: Boolmap_notmap_not: List Bool → List Boolt -- hint: use :: to construct answer -- test casest: List Boolmap_notmap_not: List Bool → List Bool[] -- exect [][]: List Boolmap_not [map_not: List Bool → List Booltrue,true: Boolfalse] -- expect [false, true]false: Bool
#5 List the First n Natural Numbers
Define a function called countdown that takes a natural number argument, n, and that returns a list of all the natural numbers from n to 0, inclusive.
-- Your answer here defcountdown :countdown: Nat → List NatNat →Nat: TypeListList: Type → TypeNat |Nat: Type0 => [0: Nat0] |0: Natn' + 1 => (n': Natn' +n': Nat1)::1: Natcountdowncountdown: Nat → List Natn' -- test casesn': Natcountdowncountdown: Nat → List Nat0 -- expect [0]0: Natcountdowncountdown: Nat → List Nat5 -- expect [5,4,3,2,1,0]5: Nat
#6: List concatenation
Suppose Lean didn't provide the List.append function, denoted ++. Write your own list append function. Call it concat. For any type α, it takes two arguments of type List α and returns a result of type List α, the result of appending the second list to the first. Hint: do case analysis on the first argument.
-- Here defconcat {concat: {α : Type} → List α → List α → List αα :α: TypeType} :Type: Type 1ListList: Type → Typeα →α: TypeListList: Type → Typeα →α: TypeListList: Type → Typeα | [],α: Typem =>m: List αm |m: List αh::h: αt,t: List αm =>m: List αh::h: αconcatconcat: {α : Type} → List α → List α → List αtt: List αm -- Test casesm: List αconcat [concat: {α : Type} → List α → List α → List α1,1: Nat2,2: Nat3]3: Nat[] -- expect [1,2,3][]: List Natconcatconcat: {α : Type} → List α → List α → List α[] [[]: List Nat1,1: Nat2,2: Nat3] -- expect [1,2,3]3: Natconcat [concat: {α : Type} → List α → List α → List α1,1: Nat2] [2: Nat3,3: Nat4] -- expect [1,2,3,4]4: Nat
#7: Lift Element to List
Write a function, pure', that takes a value, a, of any type α, and that returns a value of type List α containing just that one element.
-- Here defpure' :pure': String → List StringString →String: TypeListList: Type → TypeString |String: Types => [s: Strings]s: Stringpure'pure': String → List String"Hi" -- expect ["Hi"]"Hi": String
Challenge: List Reverse
Define a function, list_rev, that takes a list of values of any type and that returns it in reverse order. Hint: you can't use :: with a single value on the right; it needs a list on the right. Instead, consider using concat.
-- Answer here:
def rev: {α : Type} → List α → List α
rev {α: Type
α : Type: Type 1
Type}: List: Type → Type
List α: Type
α → List: Type → Type
List α: Type
α
| [] => []: List α
[]
| h: α
h::t: List α
t => t: List α
t++[h: α
h]