Exercícios adicionais

Many common functions on lists can be implemented in terms of Fold. For example, here's an alternative definition of length:

Fold_length <x> (xs: List x) : Nat
Fold_length xs = Fold (x => y Nat.succ y) xs 0n
Test_fold_length1 : Equal (Fold_length [4, 7, 0]) 3n
Test_fold_length1 = Equal.refl

Prove the validity of Fold_length:

Fold_length_correct <x> (xs: List x) : Equal (Fold_length xs) (List.length xs)
Fold_length_correct xs = ?

We can also define Map in terms of Fold. Complete the function:

Fold_map <x> <y> (f: x -> y) (xs: List x) : List y
Fold_map f xs = ?

Write a theorem fold_map_correct in Kind stating that Fold_map is correct and prove it:

Fold_map_correct : ?

In Kind, a function f: a -> b -> c actually has type a -> (b -> c). That is, if you give f a value of type a, it will provide the function g: b -> c. If you give g a value of type b, it will return a value of type c. This allows for partial application, as in Plus3. Processing a list of arguments with functions that return functions is called currying, in honor of the logician Haskell Curry.

On the other hand, we can reinterpret the function f: a -> b -> c as (Pair a b) -> c. This is called uncurrying. With a binary uncurry function, both arguments must be provided at the same time as a pair; there is no partial application.

We can define curry as follows:

Pair_curry <x> <y> <z> (f: (Pair x y) -> z) (x_val: x) (y_val: y) : z 
Pair_curry f x_val y_val = ?

As an exercise, define its inverse, Pair_uncurry. Then prove the theorems below to show that the two are inverses.

Pair_uncurry <x> <y> <z> (f: x -> y -> z) (p: Pair x y) : z
Pair_uncurry f p = ?

As a (trivial) example of the usefulness of curry, we can use it to shorten one of the examples we saw above:

Test_map2 : Equal (Map (x => Plus 3n x) [2n,0n,2n]) [5n,3n,5n]
Test_map2 = Equal.refl

Reflection exercise: before executing the following commands, can you calculate the types of Pair_curry and Pair_uncurry?

Uncurry_curry <x> <y> <z> (f: x -> y -> z) (x_val: x) (y_val: y) : 
   Equal z (Pair_curry (p => Pair_uncurry f p) x_val y_val) (f x_val y_val)
Uncurry_curry f x_val y_val = ?
Curry_uncurry <x> <y> <z> (f:(Pair x y) -> z) (p: Pair x y) : 
   Equal z (Pair_uncurry (x => y => Pair_curry f x y) p) (f p)
Curry_uncurry f p = ?

Remember the definition of the function Nth_error(Nth_error_informal):

Nth_error_informal <x> (l: List x) (n: Nat) : Maybe x
Nth_error_informal List.nil n = Maybe.none 
Nth_error_informal (List.cons head tail) Nat.zero = Maybe.some head 
Nth_error_informal (List.cons head tail) (Nat.succ n) = Nth_error tail n

Write an informal proof of the following theorem:

Nat -> List -> (Equal (Length List) Nat) : Equal (Nth_error_informal List Nat) Maybe.none

We can explore an alternative way of defining natural numbers, using Church numerals, named after the mathematician Alonzo Church. We can represent a natural number n as a function that takes a function s as a parameter and returns s iterated n times.

Num <x> : Type
Num x = (x -> x) -> x -> x

Let's see how to write some numbers with this notation. Iterating a function once should be the same as just applying it. In this way:

One : Num
One = s => z => s z 

Note that the function applies an s to a z, if we read the s as successor and the z as zero, we have that One is equal to the successor of zero.

Similarly, Two applies the function s twice to z:

Two : Num
Two = s => z => s (s z)

Defining zero is a bit more complicated: how can we "apply a function zero times"? The answer is actually simple: just return the argument untouched.

Zero : Num
Zero = s => z => z

More generally, a number n can be written as s => z => s (s ... (s z) ...), with n occurrences of s. Note in particular how the function doit3times that we defined earlier is actually just the Church representation of 3.

Three : Num
Three = s => z => Doit3times s z

Complete the definitions of the following functions. Make sure the corresponding unit tests pass, proving them with Equal.refl.

Successor

Successor of a natural number:

Succ (n: Num) : Num
Succ n = ?

Succ_1 : Equal (Succ Zero) (One)
Succ_1 = ?

Succ_2 : Equal (Succ One) (Two)
Succ_2 = ?

Succ_3 : Equal (Succ Two) (Three)
Succ_3 = ?

Addition

Addition of two natural numbers:

Plus (n: Num) (n: Num) : Num
Plus n m = ?

Plus_1 : Equal (Plus One Zero) (One)
Plus_1 = ?

Plus_2 : Equal (Plus Two One) (Plus One Two)
Plus_2 = ?

Plus_3 : Equal (Plus (Plus Two Two) Three) (Plus One (Plus Three Three))
Plus_3 = ?

Multiplication

Mult (n: Num) (m: Num) : Num
Mult n m = ?

Mult_1 : Equal (Mult One One) One
Mult_1 = ?

Mult_2 : Equal (Mult Zero (Plus Three Three)) Zero
Mult_2 = ?

Mult_3 : Equal (Mult Two Three) (Plus Three Three)
Mult_3 = ?

Exponentiation

It is not possible to make it work with Exp (n: Num) (m: Num) : Num. Polymorphism plays a crucial role here. However, choosing the right type to iterate over can be tricky. If you encounter an "inconsistency" error, try iterating over a different type: Num itself is usually problematic.

Exp (n: Num) (m: Num -> Num) : Num
Exp n m = ?

Exp_1 : Equal (Exp Two Two) (Plus Two Two)
Exp_1 = ?

Exp_2 : Equal (Exp Three Two) (Plus (Mult Two (Mult Two Two)) One)
Exp_2 = ?

Exp_3 : Equal (Exp Three Zero) One
Exp_3 = ?

Predecessor

Pred (n: Num -> Num) : Num
Pred n = ?

Pred_1 : Equal (Pred Zero) (Zero)
Pred_1 = ?

Pred_2 : Equal (Pred Two) (One)
Pred_2 = ?

Pred_3 : Equal (Pred Three) (Two)
Pred_3 = ?

Subtraction

Sub (n: Num) (m: Num) : Num
Sub n m = ?

Sub_1 : Equal (Sub One Zero) (One)
Sub_1 = ?

Sub_2 : Equal (Sub Two Two) (Sub One One)
Sub_2 = ?

Sub_3 : Equal (Sub Three One) Two
Sub_3 = ?