Higherorder
Higher-Order List Functions
Fixpoint filter {X:Type} (test: X→bool) (l:list X)
: (list X) :=
match l with
| [] ⇒ []
| h :: t ⇒ if test h then h :: (filter test t)
else filter test t
end.
: (list X) :=
match l with
| [] ⇒ []
| h :: t ⇒ if test h then h :: (filter test t)
else filter test t
end.
For example, if we apply filter to the predicate evenb
and a list of numbers l, it returns a list containing just the
even members of l.
Compute (filter evenb [1;2;3;4]). (* ==> 2;4 *)
Definition length_is_1 {X : Type} (l : list X) : bool :=
eqb (length l) 1.
Compute (filter length_is_1 [ [1; 2]; [3]; [4]; [5;6;7]; []; [8] ]). (* ==> [3]; [4]; [8] *)
Definition length_is_1 {X : Type} (l : list X) : bool :=
eqb (length l) 1.
Compute (filter length_is_1 [ [1; 2]; [3]; [4]; [5;6;7]; []; [8] ]). (* ==> [3]; [4]; [8] *)
Definition countoddmembers' (l:list nat) : nat :=
length (filter oddb l).
Compute (countoddmembers' [1;0;3;1;4;5]). (* ==> 4 *)
Compute (countoddmembers' [0;2;4]). (* ==> 0 *)
Compute (countoddmembers' nil). (* ==> 0 *)
length (filter oddb l).
Compute (countoddmembers' [1;0;3;1;4;5]). (* ==> 4 *)
Compute (countoddmembers' [0;2;4]). (* ==> 0 *)
Compute (countoddmembers' nil). (* ==> 0 *)
Higher-order Functions and Anonymous Functions: Together Forever
Compute (filter (fun l ⇒ eqb (length l) 1) [ [1; 2]; [3]; [4]; [5;6;7]; []; [8] ]).
(* ==> [3]; [4]; [8] *)
(* ==> [3]; [4]; [8] *)
Exercise: 2 stars, standard (filter_even_gt7)
Use filter (instead of Fixpoint) to write a Coq function filter_even_gt7 that takes a list of natural numbers as input and returns a list of just those that are even and greater than 7.
Definition filter_even_gt7 (l : list nat) : list nat
(* REPLACE THIS LINE WITH ":= _your_definition_ ." *). Admitted.
☐
(* REPLACE THIS LINE WITH ":= _your_definition_ ." *). Admitted.
☐
Compute (filter_even_gt7 [1;2;6;7;9;10;3;12;8]). (* ==> 10;12;8 *)
Compute (filter_even_gt7 [5;2;6;7;19;129]). (* ==> *)
Compute (filter_even_gt7 [5;2;6;7;19;129]). (* ==> *)
Exercise: 3 stars, standard (partition)
Use filter to write a Coq function partition:partition : ∀ X : Type,
(X → bool) → list X → list X × list X Given a set X, a predicate of type X → bool and a list X, partition should return a pair of lists. The first member of the pair is the sublist of the original list containing the elements that satisfy the test, and the second is the sublist containing those that fail the test. The order of elements in the two sublists should be the same as their order in the original list.
Definition partition {X : Type}
(test : X → bool)
(l : list X)
: list X × list X
(* REPLACE THIS LINE WITH ":= _your_definition_ ." *). Admitted.
Compute (partition oddb [1;2;3;4;5]). (* ==> (1;3;5, 2;4) *)
Compute (partition (fun x ⇒ false) [5;9;0]). (* ==> (, 5;9;0) *)
☐
(test : X → bool)
(l : list X)
: list X × list X
(* REPLACE THIS LINE WITH ":= _your_definition_ ." *). Admitted.
Compute (partition oddb [1;2;3;4;5]). (* ==> (1;3;5, 2;4) *)
Compute (partition (fun x ⇒ false) [5;9;0]). (* ==> (, 5;9;0) *)
☐
Definition enumerate {X : Type} (l : list X) : list (nat × X) :=
combine (zero_to_n (length l)) l.
Compute enumerate [true; false; true]. (* ==> (1,true); (2, false); (3, true) *)
combine (zero_to_n (length l)) l.
Compute enumerate [true; false; true]. (* ==> (1,true); (2, false); (3, true) *)
Map
Fixpoint map {X Y: Type} (f:X→Y) (l:list X) : (list Y) :=
match l with
| [] ⇒ []
| h :: t ⇒ (f h) :: (map f t)
end.
match l with
| [] ⇒ []
| h :: t ⇒ (f h) :: (map f t)
end.
It takes a function f and a list l = [n1, n2, n3, ...]
and returns the list [f n1, f n2, f n3,...] , where f has
been applied to each element of l in turn. For example:
The element types of the input and output lists need not be
the same, since map takes two type arguments, X and Y; it
can thus be applied to a list of numbers and a function from
numbers to booleans to yield a list of booleans:
It can even be applied to a list of numbers and
a function from numbers to lists of booleans to
yield a list of lists of booleans:
Compute (map (fun n ⇒ [evenb n;oddb n]) [2;1;2;5]).
(* ==> [true;false];[false;true];[true;false];[false;true] *)
(* ==> [true;false];[false;true];[true;false];[false;true] *)
Exercise: 2 stars, standard (complementary')
Define a different version of complementary that doesn't use recursion; you may of course call a recursive function. (Hint: try using map!)
Definition complementary' (dna1 dna2 : strand) : bool
(* REPLACE THIS LINE WITH ":= _your_definition_ ." *). Admitted.
☐
(* REPLACE THIS LINE WITH ":= _your_definition_ ." *). Admitted.
☐
Exercise: 2 stars, standard (map_option)
We can also use lists and options together. Define a function map_option that takes a function f : X → option Y and a list of Xs and returns a list of those Ys for which f didn't return None. Take X and Y as implicit arguments.
Fixpoint map_option {X Y : Type} (f : X → option Y) (l : list X) : list Y
(* REPLACE THIS LINE WITH ":= _your_definition_ ." *). Admitted.
☐
(* REPLACE THIS LINE WITH ":= _your_definition_ ." *). Admitted.
☐
Fixpoint min_by {X : Type} (cost : X → nat) (l : list X) : option X (* REPLACE THIS LINE WITH ":= _your_definition_ ." *). Admitted.
Compute min_by (fun x ⇒ x) [1;2]. (* ===> Some 1 *)
Compute min_by (fun x ⇒ x) [2;1]. (* ===> Some 1 *)
Compute min_by length [[1; 2; 3]; [1]; []; [1;2;3;4;5]]. (* ===> Some *)
Compute min_by (fun x ⇒ x) [1;2]. (* ===> Some 1 *)
Compute min_by (fun x ⇒ x) [2;1]. (* ===> Some 1 *)
Compute min_by length [[1; 2; 3]; [1]; []; [1;2;3;4;5]]. (* ===> Some *)
Exercise: 2 stars, standard (flat_map)
The function map maps a list X to a list Y using a function of type X → Y. We can define a similar function, flat_map, which maps a list X to a list Y using a function f of type X → list Y. Your definition should work by 'flattening' the results of f, like so:flat_map (fun n ⇒ [n;n+1;n+2]) [1;5;10]
= [1; 2; 3; 5; 6; 7; 10; 11; 12].
Fixpoint flat_map {X Y: Type} (f: X → list Y) (l: list X)
: (list Y)
(* REPLACE THIS LINE WITH ":= _your_definition_ ." *). Admitted.
Compute (flat_map (fun n ⇒ [n;n;n]) [1;5;4]).
(* ==> 1; 1; 1; 5; 5; 5; 4; 4; 4 *)
☐
: (list Y)
(* REPLACE THIS LINE WITH ":= _your_definition_ ." *). Admitted.
Compute (flat_map (fun n ⇒ [n;n;n]) [1;5;4]).
(* ==> 1; 1; 1; 5; 5; 5; 4; 4; 4 *)
☐
Exercise: 2 stars, standard, optional (map_option')
The flat_map function defined above is a little bit more general than map_option. Define a function map_option' that has map_option's type, but uses flat_map to do the work. You'll need to somehow take f : X → option Y and produce some other function of type X → list Y.
Definition map_option' {X Y : Type} (f: X → option Y) (l : list X) : list Y
(* REPLACE THIS LINE WITH ":= _your_definition_ ." *). Admitted.
☐
(* REPLACE THIS LINE WITH ":= _your_definition_ ." *). Admitted.
☐
Fixpoint fold_right {X Y : Type} (f: X → Y → Y) (seed : Y) (l : list X) : Y :=
match l with
| [] ⇒ seed
| a :: l' ⇒ (f a (fold_right f seed l'))
end.
Compute fold_right plus 0 [1; 2; 3; 4; 5]. (* Adds up the numbers in the list. *)
Compute fold_right andb true [true; false; false; true]. (* True if all the elements of the list are true. *)
Compute fold_right (fun (x:bool) (y:nat) ⇒ if x then y + 1 else y) 0 [true; false; false; true]. (* Counts the number of trues in the list *)
match l with
| [] ⇒ seed
| a :: l' ⇒ (f a (fold_right f seed l'))
end.
Compute fold_right plus 0 [1; 2; 3; 4; 5]. (* Adds up the numbers in the list. *)
Compute fold_right andb true [true; false; false; true]. (* True if all the elements of the list are true. *)
Compute fold_right (fun (x:bool) (y:nat) ⇒ if x then y + 1 else y) 0 [true; false; false; true]. (* Counts the number of trues in the list *)
fold_left is like fold_right but applies the function starting from the seed and first element:
Fixpoint fold_left {X Y : Type} (f : X → Y → Y) (seed : Y) (l : list X) : Y :=
match l with
| [] ⇒ seed
| a::l' ⇒ fold_left f (f a seed) l'
end.
Compute fold_left plus 0 [1; 2; 3]. (* ===> 6 *)
Compute fold_left minus 10 [1; 2; 3]. (* ===> 3 - (2 - (1 - 10)) = 1 *)
Compute fold_right minus 10 [1; 2; 3]. (* ===> 1 - (2 - (3 - 10)) = 0 *)
match l with
| [] ⇒ seed
| a::l' ⇒ fold_left f (f a seed) l'
end.
Compute fold_left plus 0 [1; 2; 3]. (* ===> 6 *)
Compute fold_left minus 10 [1; 2; 3]. (* ===> 3 - (2 - (1 - 10)) = 1 *)
Compute fold_right minus 10 [1; 2; 3]. (* ===> 1 - (2 - (3 - 10)) = 0 *)
What's the first day I like?
Compute fold_left
(fun x best ⇒ match best with
| None ⇒ if is_weekday x then Some x else None
| Some day ⇒ Some day
end)
None
[sunday; monday; tuesday; wednesday; thursday; friday; saturday].
(* ===> monday *)
(fun x best ⇒ match best with
| None ⇒ if is_weekday x then Some x else None
| Some day ⇒ Some day
end)
None
[sunday; monday; tuesday; wednesday; thursday; friday; saturday].
(* ===> monday *)
What's the last day I like?
Compute fold_right
(fun x best ⇒ match best with
| None ⇒ if is_weekday x then Some x else None
| Some day ⇒ Some day
end)
None
[sunday; monday; tuesday; wednesday; thursday; friday; saturday].
(* ===> friday *)
(fun x best ⇒ match best with
| None ⇒ if is_weekday x then Some x else None
| Some day ⇒ Some day
end)
None
[sunday; monday; tuesday; wednesday; thursday; friday; saturday].
(* ===> friday *)
Can we do it with a friendly combinator?
Definition option_or {X:Type} (o : option X) (or : option X) : option X :=
match o with
| None ⇒ or
| Some _ ⇒ o
end.
Compute fold_left
(fun x best ⇒ if is_weekday x then option_or best (Some x) else best)
None
[sunday; monday; tuesday; wednesday; thursday; friday; saturday].
match o with
| None ⇒ or
| Some _ ⇒ o
end.
Compute fold_left
(fun x best ⇒ if is_weekday x then option_or best (Some x) else best)
None
[sunday; monday; tuesday; wednesday; thursday; friday; saturday].
We can go combinator-crazy:
Definition option_lift {X:Type} (f : X → bool) (x : X) : option X :=
if (f x) then Some x else None.
Compute fold_left
if (f x) then Some x else None.
Compute fold_left
Ahh, so concise!
(fun x best ⇒ option_or best (option_lift is_weekday x))
None
[sunday; monday; tuesday; wednesday; thursday; friday; saturday].
None
[sunday; monday; tuesday; wednesday; thursday; friday; saturday].
We can pack it into a handy helper:
Definition first_satisfying {X : Type} (f : X → bool) (l : list X) : option X :=
fold_left (fun x best ⇒ option_or best (option_lift f x)) None l.
Compute first_satisfying
is_weekday
[sunday; monday; tuesday; wednesday; thursday; friday; saturday].
fold_left (fun x best ⇒ option_or best (option_lift f x)) None l.
Compute first_satisfying
is_weekday
[sunday; monday; tuesday; wednesday; thursday; friday; saturday].
And now we're done with this weekday picking example, we've
reached the apotheosis of day selection.
Fold is extremely generic, and many of our list functions can be built on top of it.
map via fold_right:
Definition map' {X Y : Type} (f: X → Y) (l : list X) : list Y :=
fold_right (fun x out ⇒ (f x)::out) [] l.
Compute map' (mult 2) [1; 2; 3]. (* ===> 2;4;6 *)
fold_right (fun x out ⇒ (f x)::out) [] l.
Compute map' (mult 2) [1; 2; 3]. (* ===> 2;4;6 *)
length:
Definition length' {X:Type} (l : list X) : nat :=
fold_right (fun x out ⇒ out+1) 0 l.
Compute length' [1; 2; 3]. (* ===> 3 *)
fold_right (fun x out ⇒ out+1) 0 l.
Compute length' [1; 2; 3]. (* ===> 3 *)
How is it so general? Well, look at its definition---fold_right
and fold_left are pretty much just our list fixpoint template! Take
map as an example, and compare it with fold_right (renaming f in fold_right to g for clarity):
match l with
| [] ⇒ []
| h :: t ⇒ cons (f h) (map f t)
end
match l with
| [] ⇒ seed
| a :: l' ⇒ g a (fold_right g seed l')
end.
We can rearrange the terms of map like so:
let g := (fun a rest ⇒ cons (f a) rest) in
match l with
| [] ⇒ []
| h :: t ⇒ g h (map f t)
end
fold_right just makes g and the base case output into parameters!
match l with
| [] ⇒ acc (* for map, this is --- the base case *)
| a::l' ⇒ g a (fold_right g seed l') (* if g does cons (f a) rest then this is just map'! *)
end
Hints: For filter', look at map'! For flat_map', also look
at map' but think about how you'll be adding elements onto the
list.
match l with
| [] ⇒ []
| h :: t ⇒ cons (f h) (map f t)
end
match l with
| [] ⇒ seed
| a :: l' ⇒ g a (fold_right g seed l')
end.
let g := (fun a rest ⇒ cons (f a) rest) in
match l with
| [] ⇒ []
| h :: t ⇒ g h (map f t)
end
match l with
| [] ⇒ acc (* for map, this is --- the base case *)
| a::l' ⇒ g a (fold_right g seed l') (* if g does cons (f a) rest then this is just map'! *)
end
Exercise: 2 stars, standard (fold_practice)
Can you implement filter, flat_map, and min_by using folds? No fixpoints allowed!
Definition filter' {X : Type} (f : X → bool) (l : list X) : list X (* REPLACE THIS LINE WITH ":= _your_definition_ ." *). Admitted.
Compute filter' evenb [1;2;3;4]. (* ===> 2;4 *)
Compute filter' (flip evenb) [1;2;3;4]. (* ===> 1;3 *)
Definition flat_map' {X : Type} (f : X → list X) (l : list X) : list X (* REPLACE THIS LINE WITH ":= _your_definition_ ." *). Admitted.
Compute flat_map' zero_to_n [1;3;5].
Compute filter' evenb [1;2;3;4]. (* ===> 2;4 *)
Compute filter' (flip evenb) [1;2;3;4]. (* ===> 1;3 *)
Definition flat_map' {X : Type} (f : X → list X) (l : list X) : list X (* REPLACE THIS LINE WITH ":= _your_definition_ ." *). Admitted.
Compute flat_map' zero_to_n [1;3;5].
This one is tricky. Check out the weekday example earlier. unwrap_or may be convenient!
Definition min_by' {X : Type} (f : X → nat) (l : list X) : option X (* REPLACE THIS LINE WITH ":= _your_definition_ ." *). Admitted.
Compute min_by' (fun x ⇒ x) [1;2]. (* ===> Some 1 *)
Compute min_by' (fun x ⇒ x) [2;1]. (* ===> Some 1 *)
Compute min_by' length [[1; 2; 3]; [1]; []; [1;2;3;4;5]]. (* ===> Some *)
(* Do not modify the following line: *)
Definition manual_grade_for_fold_practice : option (nat×string) := None.
☐
Compute min_by' (fun x ⇒ x) [1;2]. (* ===> Some 1 *)
Compute min_by' (fun x ⇒ x) [2;1]. (* ===> Some 1 *)
Compute min_by' length [[1; 2; 3]; [1]; []; [1;2;3;4;5]]. (* ===> Some *)
(* Do not modify the following line: *)
Definition manual_grade_for_fold_practice : option (nat×string) := None.
☐
(* 2022-01-12 10:20 *)